Skip to content

Commit 2ad2024

Browse files
committed
Merge branch 'feat/web-capability-menu'
# Conflicts: # apps/pythinker-web/src/i18n/locales/index.ts
2 parents 9041a7a + 27880c3 commit 2ad2024

10 files changed

Lines changed: 939 additions & 3 deletions

File tree

.changeset/web-capability-menu.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": minor
3+
---
4+
5+
Add a capability menu to the composer. It picks which tools and MCP servers the current session may use, lists the session's skills, and turns plugins on or off. Each group states how far its change reaches, because the three differ: tool and MCP changes apply to this session at once, skills are read-only here, and plugin changes are global to the daemon. Selected tools and servers appear as chips beside the composer controls.

apps/pythinker-web/src/api/daemon/client.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
AppConnector,
1515
AppPlugin,
1616
AppSkill,
17+
AppTool,
1718
AppSubagent,
1819
AppSessionCursor,
1920
AppSessionRuntimeStatus,
@@ -130,6 +131,14 @@ interface WireSkillDescriptor {
130131
disable_model_invocation?: boolean;
131132
}
132133

134+
interface WireToolDescriptor {
135+
name: string;
136+
description: string;
137+
input_schema: unknown;
138+
source: 'builtin' | 'skill' | 'mcp';
139+
mcp_server_id?: string;
140+
}
141+
133142
interface WireMcpServer {
134143
id: string;
135144
name: string;
@@ -381,6 +390,8 @@ export class DaemonPythinkerWebApi implements PythinkerWebApi {
381390
goalObjective?: string;
382391
goalControl?: 'pause' | 'resume' | 'cancel';
383392
thinking?: string;
393+
tools?: string[];
394+
mcpServers?: string[];
384395
},
385396
): Promise<AppSession> {
386397
const body: Record<string, unknown> = {};
@@ -394,6 +405,8 @@ export class DaemonPythinkerWebApi implements PythinkerWebApi {
394405
if (input.goalObjective !== undefined) agentConfig['goal_objective'] = input.goalObjective;
395406
if (input.goalControl !== undefined) agentConfig['goal_control'] = input.goalControl;
396407
if (input.thinking !== undefined) agentConfig['thinking'] = input.thinking;
408+
if (input.tools !== undefined) agentConfig['tools'] = input.tools;
409+
if (input.mcpServers !== undefined) agentConfig['mcp_servers'] = input.mcpServers;
397410
if (Object.keys(agentConfig).length > 0) body['agent_config'] = agentConfig;
398411
const data = await this.http.post<WireSession>(
399412
`/sessions/${encodeURIComponent(sessionId)}/profile`,
@@ -729,6 +742,23 @@ export class DaemonPythinkerWebApi implements PythinkerWebApi {
729742
}
730743

731744
// -------------------------------------------------------------------------
745+
// Tools — session-visible tool descriptors
746+
// GET /tools?session_id={id} → { tools: WireToolDescriptor[] }
747+
// -------------------------------------------------------------------------
748+
749+
async listTools(sessionId: string): Promise<AppTool[]> {
750+
const data = await this.http.get<{ tools: WireToolDescriptor[] }>('/tools', {
751+
session_id: sessionId,
752+
});
753+
return (data.tools ?? []).map((tool) => ({
754+
name: tool.name,
755+
description: tool.description,
756+
inputSchema: tool.input_schema,
757+
source: tool.source,
758+
mcpServerId: tool.mcp_server_id,
759+
}));
760+
}
761+
732762
// Skills — session-scoped slash-invocable skills
733763
// GET /sessions/{id}/skills → { skills: WireSkillDescriptor[] }
734764
// POST /sessions/{id}/skills/{name}:activate body { args? } → { activated, skill_name }

apps/pythinker-web/src/api/daemon/mappers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ export function toAppSession(wire: WireSession): AppSession {
101101
currentPromptId: wire.current_prompt_id,
102102
cwd: wire.metadata.cwd,
103103
model: wire.agent_config.model,
104+
tools: wire.agent_config.tools,
105+
mcpServers: wire.agent_config.mcp_servers,
104106
usage: toAppSessionUsage(wire.usage),
105107
messageCount: wire.message_count,
106108
lastSeq: wire.last_seq,

apps/pythinker-web/src/api/types.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ export interface AppSession {
6969
currentPromptId?: string;
7070
cwd: string;
7171
model: string;
72+
/** Exact tool names configured for this session, when explicitly set. */
73+
tools?: string[];
74+
/** MCP server ids configured for this session, when explicitly set. */
75+
mcpServers?: string[];
7276
usage: AppSessionUsage;
7377
messageCount: number;
7478
lastSeq: number;
@@ -651,6 +655,15 @@ export interface AppConnector {
651655
lastError?: string;
652656
}
653657

658+
/** One tool available to the current session. */
659+
export interface AppTool {
660+
name: string;
661+
description: string;
662+
inputSchema: unknown;
663+
source: 'builtin' | 'skill' | 'mcp';
664+
mcpServerId?: string;
665+
}
666+
654667
// ---------------------------------------------------------------------------
655668
// PythinkerWebApi — the app-facing interface
656669
// ---------------------------------------------------------------------------
@@ -662,7 +675,7 @@ export interface PythinkerWebApi {
662675
createSession(input: { title?: string; cwd?: string; model?: string; workspaceId?: string }): Promise<AppSession>;
663676
/** Fetch one session by id (deep links beyond the first listSessions page). */
664677
getSession(sessionId: string): Promise<AppSession>;
665-
updateSession(sessionId: string, input: { title?: string; cwd?: string; model?: string; permissionMode?: string; planMode?: boolean; dynamicWorkflowMode?: boolean; goalObjective?: string; goalControl?: 'pause' | 'resume' | 'cancel'; thinking?: string }): Promise<AppSession>;
678+
updateSession(sessionId: string, input: { title?: string; cwd?: string; model?: string; permissionMode?: string; planMode?: boolean; dynamicWorkflowMode?: boolean; goalObjective?: string; goalControl?: 'pause' | 'resume' | 'cancel'; thinking?: string; tools?: string[]; mcpServers?: string[] }): Promise<AppSession>;
666679
getSessionStatus(sessionId: string): Promise<AppSessionRuntimeStatus>;
667680
archiveSession(sessionId: string): Promise<{ archived: true }>;
668681
listMessages(sessionId: string, input?: PageRequest & { role?: AppMessageRole }): Promise<Page<AppMessage>>;
@@ -687,6 +700,7 @@ export interface PythinkerWebApi {
687700
respondQuestion(sessionId: string, questionId: string, response: QuestionResponse): Promise<{ resolved: true; resolvedAt: string }>;
688701
dismissQuestion(sessionId: string, questionId: string): Promise<{ dismissed: true; dismissedAt: string }>;
689702
listSkills(sessionId: string): Promise<AppSkill[]>;
703+
listTools(sessionId: string): Promise<AppTool[]>;
690704
activateSkill(sessionId: string, skillName: string, args?: string): Promise<{ activated: true; skillName: string }>;
691705
/** Configured MCP servers — GET /mcp/servers. */
692706
listConnectors(): Promise<AppConnector[]>;

0 commit comments

Comments
 (0)