Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/web-capability-menu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": minor
---

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.
5 changes: 5 additions & 0 deletions .changeset/web-composer-shell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Widen the chat reading column to 928px and restyle the composer card: a 24px radius, a translucent blurred surface, a border that strengthens on hover and focus, and an input that grows to 384px before it scrolls. The toolbar controls are 30px circles with a divider after the attachment button.
30 changes: 30 additions & 0 deletions apps/pythinker-web/src/api/daemon/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
AppConnector,
AppPlugin,
AppSkill,
AppTool,
AppSubagent,
AppSessionCursor,
AppSessionRuntimeStatus,
Expand Down Expand Up @@ -130,6 +131,14 @@ interface WireSkillDescriptor {
disable_model_invocation?: boolean;
}

interface WireToolDescriptor {
name: string;
description: string;
input_schema: unknown;
source: 'builtin' | 'skill' | 'mcp';
mcp_server_id?: string;
}

interface WireMcpServer {
id: string;
name: string;
Expand Down Expand Up @@ -381,6 +390,8 @@ export class DaemonPythinkerWebApi implements PythinkerWebApi {
goalObjective?: string;
goalControl?: 'pause' | 'resume' | 'cancel';
thinking?: string;
tools?: string[];
mcpServers?: string[];
},
): Promise<AppSession> {
const body: Record<string, unknown> = {};
Expand All @@ -394,6 +405,8 @@ export class DaemonPythinkerWebApi implements PythinkerWebApi {
if (input.goalObjective !== undefined) agentConfig['goal_objective'] = input.goalObjective;
if (input.goalControl !== undefined) agentConfig['goal_control'] = input.goalControl;
if (input.thinking !== undefined) agentConfig['thinking'] = input.thinking;
if (input.tools !== undefined) agentConfig['tools'] = input.tools;
if (input.mcpServers !== undefined) agentConfig['mcp_servers'] = input.mcpServers;
if (Object.keys(agentConfig).length > 0) body['agent_config'] = agentConfig;
const data = await this.http.post<WireSession>(
`/sessions/${encodeURIComponent(sessionId)}/profile`,
Expand Down Expand Up @@ -729,6 +742,23 @@ export class DaemonPythinkerWebApi implements PythinkerWebApi {
}

// -------------------------------------------------------------------------
// Tools — session-visible tool descriptors
// GET /tools?session_id={id} → { tools: WireToolDescriptor[] }
// -------------------------------------------------------------------------

async listTools(sessionId: string): Promise<AppTool[]> {
const data = await this.http.get<{ tools: WireToolDescriptor[] }>('/tools', {
session_id: sessionId,
});
return (data.tools ?? []).map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.input_schema,
source: tool.source,
mcpServerId: tool.mcp_server_id,
}));
}

// Skills — session-scoped slash-invocable skills
// GET /sessions/{id}/skills → { skills: WireSkillDescriptor[] }
// POST /sessions/{id}/skills/{name}:activate body { args? } → { activated, skill_name }
Expand Down
2 changes: 2 additions & 0 deletions apps/pythinker-web/src/api/daemon/mappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ export function toAppSession(wire: WireSession): AppSession {
currentPromptId: wire.current_prompt_id,
cwd: wire.metadata.cwd,
model: wire.agent_config.model,
tools: wire.agent_config.tools,
mcpServers: wire.agent_config.mcp_servers,
usage: toAppSessionUsage(wire.usage),
messageCount: wire.message_count,
lastSeq: wire.last_seq,
Expand Down
16 changes: 15 additions & 1 deletion apps/pythinker-web/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export interface AppSession {
currentPromptId?: string;
cwd: string;
model: string;
/** Exact tool names configured for this session, when explicitly set. */
tools?: string[];
/** MCP server ids configured for this session, when explicitly set. */
mcpServers?: string[];
usage: AppSessionUsage;
messageCount: number;
lastSeq: number;
Expand Down Expand Up @@ -651,6 +655,15 @@ export interface AppConnector {
lastError?: string;
}

/** One tool available to the current session. */
export interface AppTool {
name: string;
description: string;
inputSchema: unknown;
source: 'builtin' | 'skill' | 'mcp';
mcpServerId?: string;
}

// ---------------------------------------------------------------------------
// PythinkerWebApi — the app-facing interface
// ---------------------------------------------------------------------------
Expand All @@ -662,7 +675,7 @@ export interface PythinkerWebApi {
createSession(input: { title?: string; cwd?: string; model?: string; workspaceId?: string }): Promise<AppSession>;
/** Fetch one session by id (deep links beyond the first listSessions page). */
getSession(sessionId: string): Promise<AppSession>;
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>;
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>;
getSessionStatus(sessionId: string): Promise<AppSessionRuntimeStatus>;
archiveSession(sessionId: string): Promise<{ archived: true }>;
listMessages(sessionId: string, input?: PageRequest & { role?: AppMessageRole }): Promise<Page<AppMessage>>;
Expand All @@ -687,6 +700,7 @@ export interface PythinkerWebApi {
respondQuestion(sessionId: string, questionId: string, response: QuestionResponse): Promise<{ resolved: true; resolvedAt: string }>;
dismissQuestion(sessionId: string, questionId: string): Promise<{ dismissed: true; dismissedAt: string }>;
listSkills(sessionId: string): Promise<AppSkill[]>;
listTools(sessionId: string): Promise<AppTool[]>;
activateSkill(sessionId: string, skillName: string, args?: string): Promise<{ activated: true; skillName: string }>;
/** Configured MCP servers — GET /mcp/servers. */
listConnectors(): Promise<AppConnector[]>;
Expand Down
Loading
Loading