diff --git a/AGENTS.md b/AGENTS.md index a5ad6cc9..ef4b2b69 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,13 +13,13 @@ primitives such as read, edit, write, grep, find, ls, and bash. DevSpace wraps those primitives behind a remote Streamable HTTP MCP interface, suitable for use through a Cloudflare Tunnel. -The model-facing workflow is workspace based. MCP clients should call -`open_workspace` once per local project directory or worktree, then reuse the -returned `workspaceId` for subsequent tool calls in that same folder. Do not -call `open_workspace` again for the same folder unless the `workspaceId` is +The model-facing workflow is thread based. MCP clients should call +`open_thread` once per local project directory or worktree, then reuse the +returned `thread_id` for subsequent tool calls in that same folder. Do not +call `open_thread` again for the same folder unless the `thread_id` is rejected as unknown, the client switches folders/worktrees or checkout/worktree mode, or the user explicitly asks to reopen. `AGENTS.md` files are returned -automatically by `open_workspace` and by later tool calls when the requested path +automatically by `open_thread` and by later tool calls when the requested path enters a directory with instructions that have not been loaded for that workspace. diff --git a/docs/agent-profile-schema.md b/docs/agent-profile-schema.md index 0dc3db95..9609482e 100644 --- a/docs/agent-profile-schema.md +++ b/docs/agent-profile-schema.md @@ -52,7 +52,7 @@ Use lowercase kebab-case names. If omitted, DevSpace uses the filename without ### `description` -Required short purpose. This is exposed by `open_workspace` so the supervising +Required short purpose. This is exposed by `open_thread` so the supervising model can choose the right profile. ### `provider` @@ -118,7 +118,7 @@ disabled: true ## Markdown body The body is the profile prompt prefix DevSpace prepends when launching that -profile. It is not included in `open_workspace` by default. +profile. It is not included in `open_thread` by default. Recommended body content: @@ -137,7 +137,7 @@ devspace agents run "" devspace agents show ``` -`open_workspace` exposes compact profile metadata: +`open_thread` exposes compact profile metadata: ```json { diff --git a/docs/chatgpt-coding-workflow.md b/docs/chatgpt-coding-workflow.md index 66062621..c1ea199c 100644 --- a/docs/chatgpt-coding-workflow.md +++ b/docs/chatgpt-coding-workflow.md @@ -4,9 +4,9 @@ DevSpace brings a Codex-style coding-agent loop to ChatGPT and other MCP hosts: inspect the repo, follow local instructions, make scoped edits, run verification, and show the user what changed. -## Open One Workspace +## Open One Thread -ChatGPT should call `open_workspace` once for a project folder: +ChatGPT should call `open_thread` once for a project folder: ```json { @@ -14,12 +14,12 @@ ChatGPT should call `open_workspace` once for a project folder: } ``` -The result includes a `workspaceId`. All later file, search, edit, show-changes, -and shell calls should reuse that same `workspaceId`. +The result includes a `thread_id`. All later file, search, edit, show-changes, +and shell calls should reuse that same `thread_id`. Do not reopen the same folder unless: -- the `workspaceId` is rejected as unknown +- the `thread_id` is rejected as unknown - the user switches to another folder - the user switches between checkout and worktree mode - the user explicitly asks to reopen @@ -93,7 +93,7 @@ It also keeps compatibility with: When Subagents are enabled, DevSpace discovers agent profiles from `~/.devspace/agents/*.md` and project `.devspace/agents/*.md`. -`open_workspace` exposes a compact catalog with profile names, descriptions, +`open_thread` exposes a compact catalog with profile names, descriptions, providers, and optional models/thinking levels so the model can choose a configured agent without seeing provider-specific launch details. @@ -103,7 +103,7 @@ before use. Legacy project paths such as `.pi/skills` can be added through `DEVSPACE_SKILL_PATHS` when needed. -When `open_workspace` returns matching skills, the model should read the +When `open_thread` returns matching skills, the model should read the advertised `SKILL.md` before following that skill. Skill paths may be outside the workspace. DevSpace only permits reading: @@ -115,14 +115,14 @@ Set `DEVSPACE_SKILLS=0` to hide skills from workspace output. Set `DEVSPACE_SUBAGENTS=1` to expose the experimental subagent catalog and `subagent-delegation` skill. That skill teaches the minimal `devspace agents ls`, `devspace agents run`, and `devspace agents show` -workflow. The catalog comes from `open_workspace`; `devspace agents ls` lists +workflow. The catalog comes from `open_thread`; `devspace agents ls` lists existing subagent sessions for that workspace. ## Tool Names DevSpace exposes these tool names: -- `open_workspace` +- `open_thread` - `read` - `write` - `edit` @@ -137,7 +137,7 @@ Use `DEVSPACE_TOOL_MODE=full` to restore dedicated search and directory tools. The experimental Codex-style surface is enabled with `DEVSPACE_TOOL_MODE=codex`. It exposes: -- `open_workspace` +- `open_thread` - `read` - `apply_patch` - `exec_command` @@ -160,7 +160,7 @@ to expose the aggregate show-changes flow. When `show_changes` is exposed, models should call it exactly once after the final file modification in any turn that changes files. The tool only requires -the `workspaceId`; DevSpace automatically compares against the last shown +the `thread_id`; DevSpace automatically compares against the last shown checkpoint and advances that checkpoint after rendering the aggregate diff. ## Shell Use diff --git a/docs/configuration.md b/docs/configuration.md index 4c02eb1a..b2fcbdba 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -63,9 +63,9 @@ MCP clients discover metadata from: | Value | Behavior | | --- | --- | -| `minimal` | Default. Exposes `open_workspace`, `read`, `write`, `edit`, and `bash`. Clients use `bash` with tools such as `rg`, `find`, and `ls` for inspection. | +| `minimal` | Default. Exposes `open_thread`, `read`, `write`, `edit`, and `bash`. Clients use `bash` with tools such as `rg`, `find`, and `ls` for inspection. | | `full` | Exposes the minimal tools plus dedicated `grep`, `glob`, and `ls` tools. | -| `codex` | Experimental. Exposes `open_workspace`, `read`, `apply_patch`, `exec_command`, and `write_stdin`. Existing mutation and shell tools are hidden. | +| `codex` | Experimental. Exposes `open_thread`, `read`, `apply_patch`, `exec_command`, and `write_stdin`. Existing mutation and shell tools are hidden. | `DEVSPACE_MINIMAL_TOOLS` remains a backward-compatible alias when `DEVSPACE_TOOL_MODE` is unset: `1` selects `minimal` and `0` selects `full`. @@ -84,7 +84,7 @@ sessions. | Value | Behavior | | --- | --- | | `full` | Default. Widget UI is attached to exposed workspace, file, edit, and shell tools. | -| `changes` | Enables the aggregate `show_changes` tool and attaches widget UI to `open_workspace` and `show_changes`. | +| `changes` | Enables the aggregate `show_changes` tool and attaches widget UI to `open_thread` and `show_changes`. | | `off` | Disables widget UI. | ## Skills @@ -114,7 +114,7 @@ from: - `~/.devspace/agents/*.md` - project `.devspace/agents/*.md` -`open_workspace` returns a compact catalog containing profile names, +`open_thread` returns a compact catalog containing profile names, descriptions, providers, and optional models/thinking levels so the host model can choose an agent without reading provider-specific launch details. `devspace agents ls` lists existing subagent sessions for the current workspace, scoped by the diff --git a/docs/gotchas.md b/docs/gotchas.md index 779e37ef..df8bff0f 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -130,14 +130,18 @@ To regenerate setup: npx @waishnav/devspace init --force ``` -## Unknown `workspaceId` +## ChatGPT "workspace" vs DevSpace `open_thread` -`workspaceId` values are session identifiers. If the server restarts and the -client receives an unknown workspace error, call `open_workspace` again for that +Some MCP hosts have their own workspace concept. DevSpace uses **`open_thread`** and **`thread_id`** for a bound local project folder. That handle is **not** the ChatGPT conversation thread. After upgrading, start a **new chat** if the model still calls old tool names. + +## Unknown `thread_id` + +`thread_id` values are DevSpace session identifiers for a local project root. If the server restarts and the +client receives an unknown thread_id error, call `open_thread` again for that project. -Workspace session metadata is persisted, but clients should still treat -`open_workspace` as the way to begin a fresh working session. +Thread session metadata is persisted, but clients should still treat +`open_thread` as the way to begin a fresh working session. ## Workspace Path Rejected @@ -207,7 +211,7 @@ It also checks compatibility and custom paths: When `DEVSPACE_SUBAGENTS=1`, DevSpace loads agent profiles from `~/.devspace/agents/*.md` and project `.devspace/agents/*.md`, then exposes a -compact profile catalog through `open_workspace`. The bundled +compact profile catalog through `open_thread`. The bundled `subagent-delegation` skill keeps the model-facing workflow to `devspace agents ls`, `devspace agents run`, and `devspace agents show`. `devspace agents ls` lists existing subagent sessions, not profile @@ -218,7 +222,7 @@ Copy or adapt them into one of the active profile directories before use. Legacy project paths such as `.pi/skills` can be added through `DEVSPACE_SKILL_PATHS` when needed. -If a skill appears in `open_workspace`, the model must read that skill's +If a skill appears in `open_thread`, the model must read that skill's `SKILL.md` before reading other files inside the skill directory. ## Review Card Does Not Appear diff --git a/package.json b/package.json index af895b37..a0c91d63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@waishnav/devspace", - "version": "1.0.4", + "version": "1.1.0", "description": "Expose a secure local coding workspace through an MCP server.", "type": "module", "main": "dist/server.js", diff --git a/skills/subagent-delegation/SKILL.md b/skills/subagent-delegation/SKILL.md index fb269df5..e41a2237 100644 --- a/skills/subagent-delegation/SKILL.md +++ b/skills/subagent-delegation/SKILL.md @@ -29,7 +29,7 @@ it automatically from the shell environment injected by the workspace tool. DevSpace agent id. `run ""` starts a raw built-in provider when no configured -profile is needed. Built-in providers are listed by `open_workspace`. +profile is needed. Built-in providers are listed by `open_thread`. `run ""` sends a follow-up to an existing agent. @@ -44,9 +44,9 @@ DevSpace agent integration. ## Choosing a profile Choose profiles from the compact subagent profile catalog returned by -`open_workspace`. Use the profile name with `devspace agents run`. If no +`open_thread`. Use the profile name with `devspace agents run`. If no profile fits and delegation is still appropriate, use a built-in provider name -from `open_workspace`. +from `open_thread`. Profiles may declare a model and optional thinking level. To override the configured/default provider model or thinking level for a run, pass `--model` diff --git a/src/mcp-thread-scope.ts b/src/mcp-thread-scope.ts new file mode 100644 index 00000000..014ac081 --- /dev/null +++ b/src/mcp-thread-scope.ts @@ -0,0 +1,14 @@ +/** MCP tool and parameter names for DevSpace local project scope (not ChatGPT workspace/thread). */ + +export const OPEN_THREAD_TOOL = "open_thread"; + +export const THREAD_ID_PARAM = "thread_id"; + +export const THREAD_ID_DESCRIBE = + "DevSpace thread identifier returned by open_thread. Identifies an open local project root on the user's machine. Not the ChatGPT conversation thread."; + +export const OPEN_THREAD_SHORT_DISCLAIMER = + "thread_id is DevSpace's local project handle from open_thread, not the ChatGPT conversation thread."; + +export const OPEN_THREAD_CALL_HINT = + "Call open_thread first and pass thread_id on every subsequent file, search, edit, shell, or show-changes tool in that folder."; \ No newline at end of file diff --git a/src/process-sessions.ts b/src/process-sessions.ts index f414df19..17a8d12a 100644 --- a/src/process-sessions.ts +++ b/src/process-sessions.ts @@ -421,7 +421,7 @@ export class ProcessSessionManager { const session = this.sessions.get(sessionId); if (!session) throw new Error(`Unknown process session: ${sessionId}`); if (session.workspaceId !== workspaceId) { - throw new Error(`Process session ${sessionId} does not belong to workspace ${workspaceId}.`); + throw new Error(`Process session ${sessionId} does not belong to thread ${workspaceId}.`); } return session; } diff --git a/src/server.ts b/src/server.ts index c72e1434..eb1a3bcd 100644 --- a/src/server.ts +++ b/src/server.ts @@ -41,6 +41,12 @@ import { createReviewCheckpointManager } from "./review-checkpoints.js"; import { formatPathForPrompt } from "./skills.js"; import { createWorkspaceStore } from "./workspace-store.js"; import { formatAgentsPath, WorkspaceRegistry } from "./workspaces.js"; +import { + OPEN_THREAD_SHORT_DISCLAIMER, + OPEN_THREAD_TOOL, + THREAD_ID_DESCRIBE, + THREAD_ID_PARAM, +} from "./mcp-thread-scope.js"; import { summarizeLocalAgentProfile } from "./local-agent-profiles.js"; import { formatLocalAgentProviderAvailabilitySummary, @@ -147,7 +153,7 @@ function toolWidgetDescriptorMeta( } const toolNames = { - openWorkspace: "open_workspace", + openThread: OPEN_THREAD_TOOL, read: "read", write: "write", edit: "edit", @@ -159,7 +165,7 @@ const toolNames = { interface ToolLogFields { tool: string; - workspaceId?: string; + thread_id?: string; path?: string; workingDirectory?: string; command?: string; @@ -172,11 +178,14 @@ interface ToolLogFields { function serverInstructions(config: ServerConfig): string { const showChangesInstruction = config.widgets === "changes" - ? " If the turn successfully modifies files by creating, editing, overwriting, deleting, moving, or applying patches, call show_changes exactly once for that workspace after the final related file change and before your final response so the user can inspect the aggregate diff for that turn. Do not call it after every individual file change; do not skip it because individual file-change tools already returned diffs." + ? " If the turn successfully modifies files by creating, editing, overwriting, deleting, moving, or applying patches, call show_changes exactly once for that thread after the final related file change and before your final response so the user can inspect the aggregate diff for that turn. Do not call it after every individual file change; do not skip it because individual file-change tools already returned diffs." : ""; + const reopen = + `do not call ${toolNames.openThread} again unless switching folders/worktrees, changing checkout/worktree mode, the ${THREAD_ID_PARAM} is rejected as unknown, or the user explicitly asks to reopen`; + if (config.toolMode === "codex") { - return `Use DevSpace as a local coding workspace. Call ${toolNames.openWorkspace} once per project folder or worktree and reuse its workspaceId. Use ${toolNames.read} for direct file reads, apply_patch for all file modifications, exec_command for inspection, tests, builds, and other commands, and write_stdin to poll or interact with running processes. Follow instructions returned by ${toolNames.openWorkspace}; read applicable instruction and skill files before working in their scope.${showChangesInstruction}`; + return `Use DevSpace on the user's machine. Call ${toolNames.openThread} once per project folder or worktree and reuse its ${THREAD_ID_PARAM}. ${OPEN_THREAD_SHORT_DISCLAIMER} Use ${toolNames.read} for direct file reads, apply_patch for all file modifications, exec_command for inspection, tests, builds, and other commands, and write_stdin to poll or interact with running processes (sessionId is only for a running exec_command process, not the project scope). Follow instructions returned by ${toolNames.openThread}; read applicable instruction and skill files before working in their scope.${showChangesInstruction}`; } const inspection = config.toolMode !== "full" @@ -184,12 +193,12 @@ function serverInstructions(config: ServerConfig): string { : `Prefer ${toolNames.read}, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} for file inspection. `; const skills = config.skillsEnabled - ? `When ${toolNames.openWorkspace} returns available skills and a task matches a skill, use ${toolNames.read} to read that skill's path before proceeding. Skill paths may be outside the workspace, but ${toolNames.read} only permits advertised SKILL.md files and files under already-loaded skill directories. ` + ? `When ${toolNames.openThread} returns available skills and a task matches a skill, use ${toolNames.read} to read that skill's path before proceeding. Skill paths may be outside the project root, but ${toolNames.read} only permits advertised SKILL.md files and files under already-loaded skill directories. ` : ""; - const agentsMd = `Follow instructions returned by ${toolNames.openWorkspace}. Before working under a path listed in availableAgentsFiles, use ${toolNames.read} to inspect that instruction file and follow it. `; + const agentsMd = `Follow instructions returned by ${toolNames.openThread}. Before working under a path listed in availableAgentsFiles, use ${toolNames.read} to inspect that instruction file and follow it. `; - return `Use DevSpace as a local coding workspace. Call ${toolNames.openWorkspace} once per project folder or worktree to obtain a workspaceId. Reuse that same workspaceId for all later file, search, edit, write, show-changes, and shell tools in that folder; do not call ${toolNames.openWorkspace} again unless switching folders/worktrees, changing checkout/worktree mode, the workspaceId is rejected as unknown, or the user explicitly asks to reopen. ${agentsMd}${skills}${inspection}Prefer ${toolNames.edit} for targeted modifications, ${toolNames.write} only for new files or complete rewrites, and ${toolNames.shell} for tests, builds, git inspection, package scripts, and commands that are better executed by the shell. Do not create or modify files with ${toolNames.shell}; avoid shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or any command whose purpose is to write project files.${showChangesInstruction}`; + return `Use DevSpace on the user's machine. Call ${toolNames.openThread} once per project folder or worktree to obtain a ${THREAD_ID_PARAM}. ${OPEN_THREAD_SHORT_DISCLAIMER} Reuse that same ${THREAD_ID_PARAM} for all later file, search, edit, write, show-changes, and shell tools in that folder; ${reopen}. ${agentsMd}${skills}${inspection}Prefer ${toolNames.edit} for targeted modifications, ${toolNames.write} only for new files or complete rewrites, and ${toolNames.shell} for tests, builds, git inspection, package scripts, and commands that are better executed by the shell. Do not create or modify files with ${toolNames.shell}; avoid shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or any command whose purpose is to write project files.${showChangesInstruction}`; } function formatVisibleAgent(agent: { @@ -512,7 +521,7 @@ function processToolResponse( _meta: { tool, card: { - workspaceId, + thread_id: workspaceId, summary: { ...summary, ...outputSummary }, payload: { content }, }, @@ -541,9 +550,9 @@ function registerCodexProcessTools( { title: "Execute command", description: - "Run a command inside an open workspace. Returns its result when it exits during the yield window, otherwise returns a sessionId for write_stdin. Use this for file inspection, tests, builds, package scripts, and long-running processes. Call open_workspace first and pass workspaceId.", + "Run a command inside an open DevSpace thread. Returns its result when it exits during the yield window, otherwise returns a sessionId for write_stdin. Use this for file inspection, tests, builds, package scripts, and long-running processes. Call open_thread first and pass thread_id.", inputSchema: { - workspaceId: z.string().describe("Workspace identifier returned by open_workspace."), + thread_id: z.string().describe(THREAD_ID_DESCRIBE), cmd: z.string().min(1).describe("Shell command to execute."), tty: z .boolean() @@ -554,7 +563,7 @@ function registerCodexProcessTools( workingDirectory: z .string() .optional() - .describe("Working directory relative to the workspace root. Defaults to the workspace root."), + .describe("Working directory relative to the project root. Defaults to the project root."), yieldTimeMs: z .number() .int() @@ -574,12 +583,12 @@ function registerCodexProcessTools( ...toolWidgetDescriptorMeta(config, "shell"), annotations: SHELL_TOOL_ANNOTATIONS, }, - async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, maxOutputTokens }) => { + async ({ thread_id, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, maxOutputTokens }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(thread_id); const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory); const snapshot = await processSessions.start({ - workspaceId, + workspaceId: thread_id, command: cmd, cwd, workspaceRoot: workspace.root, @@ -592,7 +601,7 @@ function registerCodexProcessTools( logToolCall(config, { tool: "exec_command", - workspaceId, + thread_id: thread_id, workingDirectory: workingDirectory ?? ".", command: cmd, commandLength: cmd.length, @@ -600,7 +609,7 @@ function registerCodexProcessTools( durationMs: Math.round(performance.now() - startedAt), }); - return processToolResponse("exec_command", workspaceId, snapshot, { + return processToolResponse("exec_command", thread_id, snapshot, { command: cmd, workingDirectory: workingDirectory ?? ".", running: snapshot.running, @@ -618,7 +627,7 @@ function registerCodexProcessTools( description: "Poll or write characters to a process returned by exec_command. Omit chars or pass an empty string to poll. Pass \\u0003 to send Ctrl-C.", inputSchema: { - workspaceId: z.string().describe("Workspace identifier used to start the process."), + thread_id: z.string().describe("DevSpace thread_id from open_thread (project scope for this process)."), sessionId: z.number().describe("Process session identifier returned by exec_command."), chars: z.string().optional().describe("Characters to write. Omit or pass an empty string to poll."), columns: z.number().int().min(1).max(1_000).optional().describe("Resize a PTY to this width."), @@ -642,11 +651,11 @@ function registerCodexProcessTools( ...toolWidgetDescriptorMeta(config, "shell"), annotations: SHELL_TOOL_ANNOTATIONS, }, - async ({ workspaceId, sessionId, chars, columns, rows, yieldTimeMs, maxOutputTokens }) => { + async ({ thread_id, sessionId, chars, columns, rows, yieldTimeMs, maxOutputTokens }) => { const startedAt = performance.now(); - workspaces.getWorkspace(workspaceId); + workspaces.getWorkspace(thread_id); const snapshot = await processSessions.write({ - workspaceId, + workspaceId: thread_id, sessionId, chars, columns, @@ -657,12 +666,12 @@ function registerCodexProcessTools( logToolCall(config, { tool: "write_stdin", - workspaceId, + thread_id: thread_id, success: true, durationMs: Math.round(performance.now() - startedAt), }); - return processToolResponse("write_stdin", workspaceId, snapshot, { + return processToolResponse("write_stdin", thread_id, snapshot, { sessionId, charactersWritten: chars?.length ?? 0, running: snapshot.running, @@ -726,11 +735,11 @@ function createMcpServer( registerAppTool( server, - "open_workspace", + "open_thread", { - title: "Open workspace", + title: "Open local project thread (DevSpace)", description: - "Open a local project directory as a coding workspace. Call this once per project folder or worktree before reading, editing, searching, writing, showing changes, or running commands. Reuse the returned workspaceId for later calls in the same folder; do not call open_workspace again unless switching folders/worktrees, changing checkout/worktree mode, the workspaceId is rejected as unknown, or the user explicitly asks to reopen. By default this opens the actual checkout; set mode=\"worktree\" when the user asks for an isolated or parallel coding session. Returns a workspaceId, loaded root project instructions, and nested instruction file paths the model should read before working in those directories.", + `Open a local project directory as a DevSpace coding thread on the user's machine. ${OPEN_THREAD_SHORT_DISCLAIMER} Call this once per project folder or worktree before reading, editing, searching, writing, showing changes, or running commands. Reuse the returned ${THREAD_ID_PARAM} for later calls in the same folder; do not call ${OPEN_THREAD_TOOL} again unless switching folders/worktrees, changing checkout/worktree mode, the ${THREAD_ID_PARAM} is rejected as unknown, or the user explicitly asks to reopen. By default this opens the actual checkout; set mode="worktree" when the user asks for an isolated or parallel coding session. Returns ${THREAD_ID_PARAM}, loaded root project instructions, and nested instruction file paths the model should read before working in those directories.`, inputSchema: { path: z .string() @@ -749,7 +758,7 @@ function createMcpServer( .describe("Git ref to base a worktree on. Only used with mode=\"worktree\". Defaults to HEAD."), }, outputSchema: { - workspaceId: z.string(), + thread_id: z.string(), root: z.string(), mode: z.enum(["checkout", "worktree"]), sourceRoot: z.string().optional(), @@ -808,13 +817,13 @@ function createMcpServer( path: formatAgentsPath(file.path, workspace.root), })); const instruction = config.skillsEnabled - ? "Use this workspaceId in all subsequent tool calls for this project. Do not call open_workspace again for this same folder unless this workspaceId stops working, the user asks to reopen, or you switch to a different folder/worktree. Follow loaded agentsFiles instructions. Before working under a path listed in availableAgentsFiles, read that instruction file. When a task matches an available skill in skills, read its path before proceeding." - : "Use this workspaceId in all subsequent tool calls for this project. Do not call open_workspace again for this same folder unless this workspaceId stops working, the user asks to reopen, or you switch to a different folder/worktree. Follow loaded agentsFiles instructions. Before working under a path listed in availableAgentsFiles, read that instruction file."; + ? "Use this thread_id in all subsequent tool calls for this project. Do not call open_thread again for this same folder unless this thread_id stops working, the user asks to reopen, or you switch to a different folder/worktree. Follow loaded agentsFiles instructions. Before working under a path listed in availableAgentsFiles, read that instruction file. When a task matches an available skill in skills, read its path before proceeding." + : "Use this thread_id in all subsequent tool calls for this project. Do not call open_thread again for this same folder unless this thread_id stops working, the user asks to reopen, or you switch to a different folder/worktree. Follow loaded agentsFiles instructions. Before working under a path listed in availableAgentsFiles, read that instruction file."; const resultContent: ToolContent[] = [ { type: "text" as const, text: [ - `Opened workspace ${workspace.id}`, + `Opened thread ${workspace.id}`, `Root: ${workspace.root}`, `Mode: ${workspace.mode}`, loadedAgentsFiles.length > 0 @@ -840,8 +849,8 @@ function createMcpServer( }, ]; logToolCall(config, { - tool: "open_workspace", - workspaceId: workspace.id, + tool: "open_thread", + thread_id: workspace.id, path: workspace.root, success: true, durationMs: Math.round(performance.now() - startedAt), @@ -850,9 +859,9 @@ function createMcpServer( return { content: resultContent, _meta: { - tool: "open_workspace", + tool: "open_thread", card: { - workspaceId: workspace.id, + thread_id: workspace.id, root: workspace.root, path: workspace.root, summary: { @@ -866,7 +875,7 @@ function createMcpServer( }, }, structuredContent: { - workspaceId: workspace.id, + thread_id: workspace.id, root: workspace.root, mode: workspace.mode, sourceRoot: workspace.sourceRoot, @@ -890,8 +899,8 @@ function createMcpServer( title: "Read file", description: [ - "Read a file inside an open workspace. Use this for file inspection instead of shell commands like cat or sed. Call open_workspace first and pass workspaceId.", - "Use this tool to inspect relevant AGENTS.md or CLAUDE.md files listed by open_workspace before working in nested directories.", + "Read a file inside an open DevSpace thread. Use this for file inspection instead of shell commands like cat or sed. Call open_thread first and pass thread_id.", + "Use this tool to inspect relevant AGENTS.md or CLAUDE.md files listed by open_thread before working in nested directories.", config.skillsEnabled ? "If available skills were returned and a task matches one, read that skill's path before proceeding. Skill paths may be outside the workspace; only advertised SKILL.md files and files under already-loaded skill directories are readable." : "", @@ -899,15 +908,15 @@ function createMcpServer( .filter(Boolean) .join(" "), inputSchema: { - workspaceId: z + thread_id: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe(THREAD_ID_DESCRIBE), path: z .string() .describe( config.skillsEnabled - ? "File path to read, relative to the workspace root. May also be an advertised skill path from open_workspace skills." - : "File path to read, relative to the workspace root.", + ? "File path to read, relative to the project root. May also be an advertised skill path from open_thread skills." + : "File path to read, relative to the project root.", ), offset: z .number() @@ -926,9 +935,9 @@ function createMcpServer( ...toolWidgetDescriptorMeta(config, "read"), annotations: { readOnlyHint: true }, }, - async ({ workspaceId, ...input }) => { + async ({ thread_id, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(thread_id); const readPath = workspaces.resolveReadPath(workspace, input.path); const response = await readFileTool( { ...input, path: readPath.absolutePath }, @@ -942,7 +951,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.read, - workspaceId, + thread_id: thread_id, path: input.path, }, response.content, startedAt); return response; @@ -956,7 +965,7 @@ function createMcpServer( }; logToolCall(config, { tool: toolNames.read, - workspaceId, + thread_id: thread_id, path: input.path, success: true, durationMs: Math.round(performance.now() - startedAt), @@ -967,7 +976,7 @@ function createMcpServer( _meta: { tool: toolNames.read, card: { - workspaceId, + thread_id: thread_id, path: input.path, summary, payload: { content: response.content }, @@ -987,23 +996,23 @@ function createMcpServer( { title: "Write file", description: - `Create or completely overwrite a file inside an open workspace. Prefer ${toolNames.edit} for targeted changes to existing files. Call open_workspace first and pass workspaceId.`, + `Create or completely overwrite a file inside an open DevSpace thread. Prefer ${toolNames.edit} for targeted changes to existing files. Call open_thread first and pass thread_id.`, inputSchema: { - workspaceId: z + thread_id: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe(THREAD_ID_DESCRIBE), path: z .string() - .describe("File path to write, relative to the workspace root."), + .describe("File path to write, relative to the project root."), content: z.string().describe("Complete new file content."), }, outputSchema: resultOutputSchema(), ...toolWidgetDescriptorMeta(config, "write"), annotations: WRITE_TOOL_ANNOTATIONS, }, - async ({ workspaceId, ...input }) => { + async ({ thread_id, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(thread_id); workspaces.resolvePath(workspace, input.path); const response = await writeFileTool(input, { cwd: workspace.root, @@ -1013,7 +1022,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.write, - workspaceId, + thread_id: thread_id, path: input.path, }, response.content, startedAt); return response; @@ -1028,7 +1037,7 @@ function createMcpServer( }; logToolCall(config, { tool: toolNames.write, - workspaceId, + thread_id: thread_id, path: input.path, success: true, durationMs: Math.round(performance.now() - startedAt), @@ -1039,7 +1048,7 @@ function createMcpServer( _meta: { tool: toolNames.write, card: { - workspaceId, + thread_id: thread_id, path: input.path, summary, payload: { @@ -1061,14 +1070,14 @@ function createMcpServer( { title: "Edit file", description: - `Edit one file inside an open workspace by replacing exact text blocks. Prefer this over ${toolNames.write} for targeted changes. Each oldText must match a unique, non-overlapping region of the original file; merge nearby changes into one edit and keep oldText as small as possible while still unique. Call open_workspace first and pass workspaceId.`, + `Edit one file inside an open DevSpace thread by replacing exact text blocks. Prefer this over ${toolNames.write} for targeted changes. Each oldText must match a unique, non-overlapping region of the original file; merge nearby changes into one edit and keep oldText as small as possible while still unique. Call open_thread first and pass thread_id.`, inputSchema: { - workspaceId: z + thread_id: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe(THREAD_ID_DESCRIBE), path: z .string() - .describe("File path to edit, relative to the workspace root."), + .describe("File path to edit, relative to the project root."), edits: z .array( z.object({ @@ -1088,9 +1097,9 @@ function createMcpServer( ...toolWidgetDescriptorMeta(config, "edit"), annotations: EDIT_TOOL_ANNOTATIONS, }, - async ({ workspaceId, ...input }) => { + async ({ thread_id, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(thread_id); workspaces.resolvePath(workspace, input.path); const response = await editFileTool(input, { cwd: workspace.root, @@ -1100,7 +1109,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.edit, - workspaceId, + thread_id: thread_id, path: input.path, }, response.content, startedAt); return response; @@ -1117,7 +1126,7 @@ function createMcpServer( const editContent = [textBlock(editResultText)]; logToolCall(config, { tool: toolNames.edit, - workspaceId, + thread_id: thread_id, path: input.path, success: true, durationMs: Math.round(performance.now() - startedAt), @@ -1128,7 +1137,7 @@ function createMcpServer( _meta: { tool: toolNames.edit, card: { - workspaceId, + thread_id: thread_id, path: input.path, summary, payload: { @@ -1153,11 +1162,11 @@ function createMcpServer( { title: "Apply patch", description: - "Apply one Codex-style patch inside an open workspace. Supports adding, overwriting, updating, deleting, and moving files. Use this for all file modifications. Paths must be relative to the workspace. Call open_workspace first and pass workspaceId.", + "Apply one Codex-style patch inside an open DevSpace thread. Supports adding, overwriting, updating, deleting, and moving files. Use this for all file modifications. Paths must be relative to the project root. Call open_thread first and pass thread_id.", inputSchema: { - workspaceId: z + thread_id: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe(THREAD_ID_DESCRIBE), patch: z .string() .describe("Patch text enclosed by *** Begin Patch and *** End Patch markers."), @@ -1176,9 +1185,9 @@ function createMcpServer( ...toolWidgetDescriptorMeta(config, "edit"), annotations: EDIT_TOOL_ANNOTATIONS, }, - async ({ workspaceId, patch }) => { + async ({ thread_id, patch }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(thread_id); const applied = await applyPatch(workspace.root, patch); const paths = applied.files.map((file) => file.path).join(", "); const result = `Applied patch to ${applied.files.length} file(s): ${paths}`; @@ -1189,7 +1198,7 @@ function createMcpServer( logToolCall(config, { tool: "apply_patch", - workspaceId, + thread_id: thread_id, success: true, durationMs: Math.round(performance.now() - startedAt), }); @@ -1199,7 +1208,7 @@ function createMcpServer( _meta: { tool: "apply_patch", card: { - workspaceId, + thread_id: thread_id, path: displayPath, summary: { files: applied.files.length, @@ -1229,19 +1238,19 @@ function createMcpServer( description: "Show aggregate file changes for an open workspace. If the current turn successfully modified files, call this exactly once after the final related file change and before your final response so the user can inspect the combined diff for the turn. Do not call it after every individual file change, and do not skip it because prior file-change tools already displayed per-tool diffs.", inputSchema: { - workspaceId: z + thread_id: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe(THREAD_ID_DESCRIBE), }, outputSchema: resultOutputSchema(), ...toolWidgetDescriptorMeta(config, "show_changes"), annotations: { readOnlyHint: true }, }, - async ({ workspaceId }) => { + async ({ thread_id }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(thread_id); const review = await reviewCheckpoints.reviewChanges({ - workspaceId, + workspaceId: thread_id, root: workspace.root, since: "last_shown", markReviewed: true, @@ -1250,7 +1259,7 @@ function createMcpServer( const content = [textBlock(review.result)]; logToolCall(config, { tool: "show_changes", - workspaceId, + thread_id: thread_id, success: true, durationMs: Math.round(performance.now() - startedAt), }); @@ -1260,7 +1269,7 @@ function createMcpServer( _meta: { tool: "show_changes", card: { - workspaceId, + thread_id: thread_id, summary: review.summary, files: review.files, payload: { @@ -1283,17 +1292,17 @@ function createMcpServer( { title: "Grep", description: - "Search file contents inside an open workspace. Use this before broad reads when looking for symbols, text, or usage sites. Respects project ignore rules. Call open_workspace first and pass workspaceId.", + "Search file contents inside an open DevSpace thread. Use this before broad reads when looking for symbols, text, or usage sites. Respects project ignore rules. Call open_thread first and pass thread_id.", inputSchema: { - workspaceId: z + thread_id: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe(THREAD_ID_DESCRIBE), pattern: z.string().describe("Search pattern."), path: z .string() .optional() .describe( - "Optional path or glob scope relative to the workspace root.", + "Optional path or glob scope relative to the project root.", ), include: z.string().optional().describe("Optional include glob."), }, @@ -1301,9 +1310,9 @@ function createMcpServer( ...toolWidgetDescriptorMeta(config, "search"), annotations: { readOnlyHint: true }, }, - async ({ workspaceId, ...input }) => { + async ({ thread_id, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(thread_id); if (input.path) workspaces.resolvePath(workspace, input.path); const response = await grepFilesTool(input, { cwd: workspace.root, @@ -1313,7 +1322,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.grep, - workspaceId, + thread_id: thread_id, path: input.path, }, response.content, startedAt); return response; @@ -1326,7 +1335,7 @@ function createMcpServer( }; logToolCall(config, { tool: toolNames.grep, - workspaceId, + thread_id: thread_id, path: input.path, success: true, durationMs: Math.round(performance.now() - startedAt), @@ -1337,7 +1346,7 @@ function createMcpServer( _meta: { tool: toolNames.grep, card: { - workspaceId, + thread_id: thread_id, path: input.path, summary, payload: { content: response.content }, @@ -1356,24 +1365,24 @@ function createMcpServer( { title: "Glob", description: - "Find files by glob pattern inside an open workspace. Use this to discover filenames or narrow file sets before reading. Respects project ignore rules. Call open_workspace first and pass workspaceId.", + "Find files by glob pattern inside an open DevSpace thread. Use this to discover filenames or narrow file sets before reading. Respects project ignore rules. Call open_thread first and pass thread_id.", inputSchema: { - workspaceId: z + thread_id: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe(THREAD_ID_DESCRIBE), pattern: z.string().describe("File glob pattern."), path: z .string() .optional() - .describe("Optional path scope relative to the workspace root."), + .describe("Optional path scope relative to the project root."), }, outputSchema: resultOutputSchema(), ...toolWidgetDescriptorMeta(config, "search"), annotations: { readOnlyHint: true }, }, - async ({ workspaceId, ...input }) => { + async ({ thread_id, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(thread_id); if (input.path) workspaces.resolvePath(workspace, input.path); const response = await findFilesTool(input, { cwd: workspace.root, @@ -1383,7 +1392,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.glob, - workspaceId, + thread_id: thread_id, path: input.path, }, response.content, startedAt); return response; @@ -1396,7 +1405,7 @@ function createMcpServer( }; logToolCall(config, { tool: toolNames.glob, - workspaceId, + thread_id: thread_id, path: input.path, success: true, durationMs: Math.round(performance.now() - startedAt), @@ -1407,7 +1416,7 @@ function createMcpServer( _meta: { tool: toolNames.glob, card: { - workspaceId, + thread_id: thread_id, path: input.path, summary, payload: { content: response.content }, @@ -1426,24 +1435,24 @@ function createMcpServer( { title: "Ls", description: - "List a directory inside an open workspace. Use this for directory inspection before reading files. Call open_workspace first and pass workspaceId.", + "List a directory inside an open DevSpace thread. Use this for directory inspection before reading files. Call open_thread first and pass thread_id.", inputSchema: { - workspaceId: z + thread_id: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe(THREAD_ID_DESCRIBE), path: z .string() .describe( - "Directory path to list, relative to the workspace root.", + "Directory path to list, relative to the project root.", ), }, outputSchema: resultOutputSchema(), ...toolWidgetDescriptorMeta(config, "directory"), annotations: { readOnlyHint: true }, }, - async ({ workspaceId, ...input }) => { + async ({ thread_id, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(thread_id); workspaces.resolvePath(workspace, input.path); const response = await listDirectoryTool(input, { cwd: workspace.root, @@ -1453,7 +1462,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.ls, - workspaceId, + thread_id: thread_id, path: input.path, }, response.content, startedAt); return response; @@ -1462,7 +1471,7 @@ function createMcpServer( const summary = textSummary(response.content); logToolCall(config, { tool: toolNames.ls, - workspaceId, + thread_id: thread_id, path: input.path, success: true, durationMs: Math.round(performance.now() - startedAt), @@ -1473,7 +1482,7 @@ function createMcpServer( _meta: { tool: toolNames.ls, card: { - workspaceId, + thread_id: thread_id, path: input.path, summary, payload: { content: response.content }, @@ -1494,12 +1503,12 @@ function createMcpServer( { title: "Bash", description: config.toolMode !== "full" - ? `Run a shell command inside an open workspace. Use only for tests, builds, git inspection, package scripts, search, file discovery, and directory inspection. In minimal tool mode, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} are disabled; use command-line tools such as grep, rg, find, ls, and tree for those read-only inspection actions. Do not use ${toolNames.shell} to create or modify files. Do not use shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or generated scripts to write project files; use ${toolNames.edit} for targeted changes and ${toolNames.write} for new files or full rewrites. Prefer ${toolNames.read} for direct file reads. Call open_workspace first and pass workspaceId. This is powerful local execution and should only be exposed behind strong authentication.` - : `Run a shell command inside an open workspace. Use only for tests, builds, git inspection, package scripts, and commands that are better executed by the shell. Do not use ${toolNames.shell} to create or modify files. Do not use shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or generated scripts to write project files; use ${toolNames.edit} for targeted changes and ${toolNames.write} for new files or full rewrites. Prefer ${toolNames.read}, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} for file inspection. Call open_workspace first and pass workspaceId. This is powerful local execution and should only be exposed behind strong authentication.`, + ? `Run a shell command inside an open DevSpace thread. Use only for tests, builds, git inspection, package scripts, search, file discovery, and directory inspection. In minimal tool mode, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} are disabled; use command-line tools such as grep, rg, find, ls, and tree for those read-only inspection actions. Do not use ${toolNames.shell} to create or modify files. Do not use shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or generated scripts to write project files; use ${toolNames.edit} for targeted changes and ${toolNames.write} for new files or full rewrites. Prefer ${toolNames.read} for direct file reads. Call open_thread first and pass thread_id. This is powerful local execution and should only be exposed behind strong authentication.` + : `Run a shell command inside an open DevSpace thread. Use only for tests, builds, git inspection, package scripts, and commands that are better executed by the shell. Do not use ${toolNames.shell} to create or modify files. Do not use shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or generated scripts to write project files; use ${toolNames.edit} for targeted changes and ${toolNames.write} for new files or full rewrites. Prefer ${toolNames.read}, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} for file inspection. Call open_thread first and pass thread_id. This is powerful local execution and should only be exposed behind strong authentication.`, inputSchema: { - workspaceId: z + thread_id: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe(THREAD_ID_DESCRIBE), command: z .string() .describe( @@ -1509,7 +1518,7 @@ function createMcpServer( .string() .optional() .describe( - "Optional working directory relative to the workspace root. Defaults to the workspace root.", + "Optional working directory relative to the project root. Defaults to the project root.", ), timeout: z .number() @@ -1522,9 +1531,9 @@ function createMcpServer( ...toolWidgetDescriptorMeta(config, "shell"), annotations: SHELL_TOOL_ANNOTATIONS, }, - async ({ workspaceId, workingDirectory, ...input }) => { + async ({ thread_id, workingDirectory, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(thread_id); const cwd = workspaces.resolveWorkingDirectory( workspace, workingDirectory, @@ -1537,7 +1546,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.shell, - workspaceId, + thread_id: thread_id, workingDirectory: workingDirectory ?? ".", command: input.command, commandLength: input.command.length, @@ -1552,7 +1561,7 @@ function createMcpServer( }; logToolCall(config, { tool: toolNames.shell, - workspaceId, + thread_id: thread_id, workingDirectory: workingDirectory ?? ".", command: input.command, commandLength: input.command.length, @@ -1565,7 +1574,7 @@ function createMcpServer( _meta: { tool: toolNames.shell, card: { - workspaceId, + thread_id: thread_id, path: workingDirectory, summary, payload: { content: response.content }, diff --git a/src/ui/card-types.ts b/src/ui/card-types.ts index 1e3c9409..72a4a0ac 100644 --- a/src/ui/card-types.ts +++ b/src/ui/card-types.ts @@ -1,7 +1,7 @@ import type { App } from "@modelcontextprotocol/ext-apps"; export type ToolName = - | "open_workspace" + | "open_thread" | "show_changes" | "apply_patch" | "exec_command" @@ -20,7 +20,7 @@ export type PatchOperation = "add" | "update" | "delete" | "move"; export interface ToolResultCard { tool: ToolName; - workspaceId?: string; + thread_id?: string; path?: string; root?: string; status?: string; @@ -65,7 +65,7 @@ export interface ToolPayload { export function isToolName(value: unknown): value is ToolName { return ( - value === "open_workspace" || + value === "open_thread" || value === "show_changes" || value === "apply_patch" || value === "exec_command" || @@ -133,7 +133,7 @@ export function summaryNumber( } export function isExpandableCard(card: ToolResultCard): boolean { - if (card.tool === "open_workspace") { + if (card.tool === "open_thread") { return ( Number(card.summary?.agentsFiles ?? 0) > 0 || Number(card.summary?.skills ?? 0) > 0 || diff --git a/src/ui/workspace-app.tsx b/src/ui/workspace-app.tsx index edbb620f..39d8b31a 100644 --- a/src/ui/workspace-app.tsx +++ b/src/ui/workspace-app.tsx @@ -228,8 +228,8 @@ async function renderPayloadIfNeeded(): Promise { return; } - if (card.tool === "open_workspace") { - renderPrePayload(target, workspacePayloadText(card), "open_workspace"); + if (card.tool === "open_thread") { + renderPrePayload(target, workspacePayloadText(card), "open_thread"); return; } @@ -353,7 +353,7 @@ function renderSummaryBadge(card: ToolResultCard): HTMLElement { return stats; } - if (card.tool === "open_workspace") { + if (card.tool === "open_thread") { const agentsFiles = summaryNumber(summary, "agentsFiles") ?? 0; const skills = summaryNumber(summary, "skills") ?? 0; const group = element("span", { className: "badge-group" }); @@ -465,7 +465,7 @@ function workspacePayloadText(card: ToolResultCard): string { const availableAgentsFiles = card.availableAgentsFiles ?? []; const skills = card.skills ?? []; const lines = [ - card.workspaceId ? `Workspace: ${card.workspaceId}` : undefined, + card.thread_id ? `Thread: ${card.thread_id}` : undefined, card.root ? `Root: ${card.root}` : undefined, skills.length > 0 ? `Skills: ${skills.map((skill) => skill.name ?? skill.path ?? "unnamed").join(", ")}` @@ -515,7 +515,7 @@ function getToolDisplay(card: ToolResultCard): ToolDisplay { const label = getToolLabel(card); switch (card.tool) { - case "open_workspace": + case "open_thread": return { icon: folderIcon(), title: "Workspace", label, tone: "workspace" }; case "read": return { icon: fileIcon(), title: "Read File", label, tone: "read" }; diff --git a/src/workspaces.ts b/src/workspaces.ts index 673d0823..3203ffd2 100644 --- a/src/workspaces.ts +++ b/src/workspaces.ts @@ -94,7 +94,7 @@ export class WorkspaceRegistry { const session = this.store?.getSession(workspaceId); if (!session) { - throw new Error(`Unknown workspaceId: ${workspaceId}. Call open_workspace first.`); + throw new Error(`Unknown thread_id: ${workspaceId}. Call open_thread first.`); } const root = this.assertWorkspaceRootAllowed(session.root, session.mode, session.sourceRoot);