From abb5908305e8e175cfa0feba1a583ffe2cffc003 Mon Sep 17 00:00:00 2001 From: Waishnav Date: Tue, 7 Jul 2026 00:33:52 +0530 Subject: [PATCH 1/4] Shorten opened project ids --- src/workspaces.test.ts | 1 + src/workspaces.ts | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/workspaces.test.ts b/src/workspaces.test.ts index 87f6b35a..5a820742 100644 --- a/src/workspaces.test.ts +++ b/src/workspaces.test.ts @@ -47,6 +47,7 @@ try { const registry = new WorkspaceRegistry(config); const { workspace, agentsFiles, availableAgentsFiles } = await registry.openWorkspace(root); + assert.match(workspace.id, /^proj_[a-f0-9]{8}$/); assert.equal(workspace.mode, "checkout"); assert.deepEqual( agentsFiles.map((file) => file.content), diff --git a/src/workspaces.ts b/src/workspaces.ts index 673d0823..3daa22cf 100644 --- a/src/workspaces.ts +++ b/src/workspaces.ts @@ -1,4 +1,4 @@ -import { randomUUID } from "node:crypto"; +import { randomBytes } from "node:crypto"; import type { WorkspaceMode, WorkspaceStore } from "./workspace-store.js"; import { mkdir, opendir, stat } from "node:fs/promises"; import { dirname, join, relative, resolve, sep } from "node:path"; @@ -200,7 +200,7 @@ export class WorkspaceRegistry { worktree?: WorkspaceWorktree; }): Promise { const workspace: Workspace = { - id: `ws_${randomUUID()}`, + id: this.createProjectId(), root: input.root, mode: input.mode, sourceRoot: input.sourceRoot, @@ -226,6 +226,14 @@ export class WorkspaceRegistry { return { workspace, agentsFiles, availableAgentsFiles }; } + private createProjectId(): string { + let id: string; + do { + id = `proj_${randomBytes(4).toString("hex")}`; + } while (this.workspaces.has(id) || Boolean(this.store?.getSession(id))); + return id; + } + private loadSkillsForWorkspace(root: string): Pick { const result = loadWorkspaceSkills(this.config, root); return { From 32afe0b065fcdc36beffa02e48a7147d5a6c587f Mon Sep 17 00:00:00 2001 From: Waishnav Date: Tue, 7 Jul 2026 00:38:49 +0530 Subject: [PATCH 2/4] Rename workspace tool surface to project --- src/server.ts | 228 +++++++++++++++++++-------------------- src/ui/card-types.ts | 9 +- src/ui/workspace-app.tsx | 14 +-- src/workspaces.ts | 2 +- 4 files changed, 131 insertions(+), 122 deletions(-) diff --git a/src/server.ts b/src/server.ts index c72e1434..81734057 100644 --- a/src/server.ts +++ b/src/server.ts @@ -147,7 +147,7 @@ function toolWidgetDescriptorMeta( } const toolNames = { - openWorkspace: "open_workspace", + openProject: "open_project", read: "read", write: "write", edit: "edit", @@ -176,7 +176,7 @@ function serverInstructions(config: ServerConfig): string { : ""; 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 as a local coding project session. Call ${toolNames.openProject} once per project folder or worktree and reuse its projectId. 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.openProject}; read applicable instruction and skill files before working in their scope.${showChangesInstruction}`; } const inspection = config.toolMode !== "full" @@ -184,12 +184,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.openProject} 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 opened project, 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.openProject}. 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 as a local coding project session. Call ${toolNames.openProject} once per project folder or worktree to obtain a projectId. Reuse that same projectId for all later file, search, edit, write, show-changes, and shell tools in that folder; do not call ${toolNames.openProject} again unless switching folders/worktrees, changing checkout/worktree mode, the projectId 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}`; } function formatVisibleAgent(agent: { @@ -500,7 +500,7 @@ function processOutputSchema(): z.ZodRawShape { function processToolResponse( tool: "exec_command" | "write_stdin", - workspaceId: string, + projectId: string, snapshot: ProcessSnapshot, summary: Record, ) { @@ -512,7 +512,7 @@ function processToolResponse( _meta: { tool, card: { - workspaceId, + projectId, summary: { ...summary, ...outputSummary }, payload: { content }, }, @@ -541,9 +541,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 opened DevSpace project. 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_project first and pass projectId.", inputSchema: { - workspaceId: z.string().describe("Workspace identifier returned by open_workspace."), + projectId: z.string().describe("Project identifier returned by open_project."), cmd: z.string().min(1).describe("Shell command to execute."), tty: z .boolean() @@ -554,7 +554,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 opened project root. Defaults to the project root."), yieldTimeMs: z .number() .int() @@ -574,12 +574,12 @@ function registerCodexProcessTools( ...toolWidgetDescriptorMeta(config, "shell"), annotations: SHELL_TOOL_ANNOTATIONS, }, - async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, maxOutputTokens }) => { + async ({ projectId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, maxOutputTokens }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(projectId); const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory); const snapshot = await processSessions.start({ - workspaceId, + workspaceId: projectId, command: cmd, cwd, workspaceRoot: workspace.root, @@ -592,7 +592,7 @@ function registerCodexProcessTools( logToolCall(config, { tool: "exec_command", - workspaceId, + workspaceId: projectId, workingDirectory: workingDirectory ?? ".", command: cmd, commandLength: cmd.length, @@ -600,7 +600,7 @@ function registerCodexProcessTools( durationMs: Math.round(performance.now() - startedAt), }); - return processToolResponse("exec_command", workspaceId, snapshot, { + return processToolResponse("exec_command", projectId, snapshot, { command: cmd, workingDirectory: workingDirectory ?? ".", running: snapshot.running, @@ -618,7 +618,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."), + projectId: z.string().describe("Project identifier used to start the 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 +642,11 @@ function registerCodexProcessTools( ...toolWidgetDescriptorMeta(config, "shell"), annotations: SHELL_TOOL_ANNOTATIONS, }, - async ({ workspaceId, sessionId, chars, columns, rows, yieldTimeMs, maxOutputTokens }) => { + async ({ projectId, sessionId, chars, columns, rows, yieldTimeMs, maxOutputTokens }) => { const startedAt = performance.now(); - workspaces.getWorkspace(workspaceId); + workspaces.getWorkspace(projectId); const snapshot = await processSessions.write({ - workspaceId, + workspaceId: projectId, sessionId, chars, columns, @@ -657,12 +657,12 @@ function registerCodexProcessTools( logToolCall(config, { tool: "write_stdin", - workspaceId, + workspaceId: projectId, success: true, durationMs: Math.round(performance.now() - startedAt), }); - return processToolResponse("write_stdin", workspaceId, snapshot, { + return processToolResponse("write_stdin", projectId, snapshot, { sessionId, charactersWritten: chars?.length ?? 0, running: snapshot.running, @@ -726,11 +726,11 @@ function createMcpServer( registerAppTool( server, - "open_workspace", + toolNames.openProject, { - title: "Open workspace", + title: "Open project", 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 folder as a DevSpace coding project. Call this once per project folder or worktree before reading, editing, searching, writing, showing changes, or running commands. Reuse the returned projectId for later calls in the same folder; do not call open_project again unless switching folders/worktrees, changing checkout/worktree mode, the projectId 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 projectId, loaded root project instructions, and nested instruction file paths the model should read before working in those directories.", inputSchema: { path: z .string() @@ -749,7 +749,7 @@ function createMcpServer( .describe("Git ref to base a worktree on. Only used with mode=\"worktree\". Defaults to HEAD."), }, outputSchema: { - workspaceId: z.string(), + projectId: z.string(), root: z.string(), mode: z.enum(["checkout", "worktree"]), sourceRoot: z.string().optional(), @@ -808,13 +808,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 projectId in all subsequent DevSpace tool calls for this project. Do not call open_project again for this same folder unless this projectId 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 projectId in all subsequent DevSpace tool calls for this project. Do not call open_project again for this same folder unless this projectId 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 project ${workspace.id}`, `Root: ${workspace.root}`, `Mode: ${workspace.mode}`, loadedAgentsFiles.length > 0 @@ -840,7 +840,7 @@ function createMcpServer( }, ]; logToolCall(config, { - tool: "open_workspace", + tool: toolNames.openProject, workspaceId: workspace.id, path: workspace.root, success: true, @@ -850,9 +850,9 @@ function createMcpServer( return { content: resultContent, _meta: { - tool: "open_workspace", + tool: toolNames.openProject, card: { - workspaceId: workspace.id, + projectId: workspace.id, root: workspace.root, path: workspace.root, summary: { @@ -866,7 +866,7 @@ function createMcpServer( }, }, structuredContent: { - workspaceId: workspace.id, + projectId: workspace.id, root: workspace.root, mode: workspace.mode, sourceRoot: workspace.sourceRoot, @@ -890,24 +890,24 @@ 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 opened DevSpace project. Use this for file inspection instead of shell commands like cat or sed. Call open_project first and pass projectId.", + "Use this tool to inspect relevant AGENTS.md or CLAUDE.md files listed by open_project 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." + ? "If available skills were returned and a task matches one, read that skill's path before proceeding. Skill paths may be outside the opened project; only advertised SKILL.md files and files under already-loaded skill directories are readable." : "", ] .filter(Boolean) .join(" "), inputSchema: { - workspaceId: z + projectId: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe("Project identifier returned by open_project."), 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 opened project root. May also be an advertised skill path from open_project skills." + : "File path to read, relative to the opened project root.", ), offset: z .number() @@ -926,9 +926,9 @@ function createMcpServer( ...toolWidgetDescriptorMeta(config, "read"), annotations: { readOnlyHint: true }, }, - async ({ workspaceId, ...input }) => { + async ({ projectId, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(projectId); const readPath = workspaces.resolveReadPath(workspace, input.path); const response = await readFileTool( { ...input, path: readPath.absolutePath }, @@ -942,7 +942,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.read, - workspaceId, + workspaceId: projectId, path: input.path, }, response.content, startedAt); return response; @@ -956,7 +956,7 @@ function createMcpServer( }; logToolCall(config, { tool: toolNames.read, - workspaceId, + workspaceId: projectId, path: input.path, success: true, durationMs: Math.round(performance.now() - startedAt), @@ -967,7 +967,7 @@ function createMcpServer( _meta: { tool: toolNames.read, card: { - workspaceId, + projectId, path: input.path, summary, payload: { content: response.content }, @@ -987,23 +987,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 opened DevSpace project. Prefer ${toolNames.edit} for targeted changes to existing files. Call open_project first and pass projectId.`, inputSchema: { - workspaceId: z + projectId: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe("Project identifier returned by open_project."), path: z .string() - .describe("File path to write, relative to the workspace root."), + .describe("File path to write, relative to the opened project root."), content: z.string().describe("Complete new file content."), }, outputSchema: resultOutputSchema(), ...toolWidgetDescriptorMeta(config, "write"), annotations: WRITE_TOOL_ANNOTATIONS, }, - async ({ workspaceId, ...input }) => { + async ({ projectId, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(projectId); workspaces.resolvePath(workspace, input.path); const response = await writeFileTool(input, { cwd: workspace.root, @@ -1013,7 +1013,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.write, - workspaceId, + workspaceId: projectId, path: input.path, }, response.content, startedAt); return response; @@ -1028,7 +1028,7 @@ function createMcpServer( }; logToolCall(config, { tool: toolNames.write, - workspaceId, + workspaceId: projectId, path: input.path, success: true, durationMs: Math.round(performance.now() - startedAt), @@ -1039,7 +1039,7 @@ function createMcpServer( _meta: { tool: toolNames.write, card: { - workspaceId, + projectId, path: input.path, summary, payload: { @@ -1061,14 +1061,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 opened DevSpace project 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_project first and pass projectId.`, inputSchema: { - workspaceId: z + projectId: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe("Project identifier returned by open_project."), path: z .string() - .describe("File path to edit, relative to the workspace root."), + .describe("File path to edit, relative to the opened project root."), edits: z .array( z.object({ @@ -1088,9 +1088,9 @@ function createMcpServer( ...toolWidgetDescriptorMeta(config, "edit"), annotations: EDIT_TOOL_ANNOTATIONS, }, - async ({ workspaceId, ...input }) => { + async ({ projectId, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(projectId); workspaces.resolvePath(workspace, input.path); const response = await editFileTool(input, { cwd: workspace.root, @@ -1100,7 +1100,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.edit, - workspaceId, + workspaceId: projectId, path: input.path, }, response.content, startedAt); return response; @@ -1117,7 +1117,7 @@ function createMcpServer( const editContent = [textBlock(editResultText)]; logToolCall(config, { tool: toolNames.edit, - workspaceId, + workspaceId: projectId, path: input.path, success: true, durationMs: Math.round(performance.now() - startedAt), @@ -1128,7 +1128,7 @@ function createMcpServer( _meta: { tool: toolNames.edit, card: { - workspaceId, + projectId, path: input.path, summary, payload: { @@ -1153,11 +1153,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 opened DevSpace project. Supports adding, overwriting, updating, deleting, and moving files. Use this for all file modifications. Paths must be relative to the opened project. Call open_project first and pass projectId.", inputSchema: { - workspaceId: z + projectId: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe("Project identifier returned by open_project."), patch: z .string() .describe("Patch text enclosed by *** Begin Patch and *** End Patch markers."), @@ -1176,9 +1176,9 @@ function createMcpServer( ...toolWidgetDescriptorMeta(config, "edit"), annotations: EDIT_TOOL_ANNOTATIONS, }, - async ({ workspaceId, patch }) => { + async ({ projectId, patch }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(projectId); 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 +1189,7 @@ function createMcpServer( logToolCall(config, { tool: "apply_patch", - workspaceId, + workspaceId: projectId, success: true, durationMs: Math.round(performance.now() - startedAt), }); @@ -1199,7 +1199,7 @@ function createMcpServer( _meta: { tool: "apply_patch", card: { - workspaceId, + projectId, path: displayPath, summary: { files: applied.files.length, @@ -1227,21 +1227,21 @@ function createMcpServer( { title: "Show changes", 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.", + "Show aggregate file changes for an opened DevSpace project. 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 + projectId: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe("Project identifier returned by open_project."), }, outputSchema: resultOutputSchema(), ...toolWidgetDescriptorMeta(config, "show_changes"), annotations: { readOnlyHint: true }, }, - async ({ workspaceId }) => { + async ({ projectId }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(projectId); const review = await reviewCheckpoints.reviewChanges({ - workspaceId, + workspaceId: projectId, root: workspace.root, since: "last_shown", markReviewed: true, @@ -1250,7 +1250,7 @@ function createMcpServer( const content = [textBlock(review.result)]; logToolCall(config, { tool: "show_changes", - workspaceId, + workspaceId: projectId, success: true, durationMs: Math.round(performance.now() - startedAt), }); @@ -1260,7 +1260,7 @@ function createMcpServer( _meta: { tool: "show_changes", card: { - workspaceId, + projectId, summary: review.summary, files: review.files, payload: { @@ -1283,17 +1283,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 opened DevSpace project. Use this before broad reads when looking for symbols, text, or usage sites. Respects project ignore rules. Call open_project first and pass projectId.", inputSchema: { - workspaceId: z + projectId: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe("Project identifier returned by open_project."), 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 opened project root.", ), include: z.string().optional().describe("Optional include glob."), }, @@ -1301,9 +1301,9 @@ function createMcpServer( ...toolWidgetDescriptorMeta(config, "search"), annotations: { readOnlyHint: true }, }, - async ({ workspaceId, ...input }) => { + async ({ projectId, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(projectId); if (input.path) workspaces.resolvePath(workspace, input.path); const response = await grepFilesTool(input, { cwd: workspace.root, @@ -1313,7 +1313,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.grep, - workspaceId, + workspaceId: projectId, path: input.path, }, response.content, startedAt); return response; @@ -1326,7 +1326,7 @@ function createMcpServer( }; logToolCall(config, { tool: toolNames.grep, - workspaceId, + workspaceId: projectId, path: input.path, success: true, durationMs: Math.round(performance.now() - startedAt), @@ -1337,7 +1337,7 @@ function createMcpServer( _meta: { tool: toolNames.grep, card: { - workspaceId, + projectId, path: input.path, summary, payload: { content: response.content }, @@ -1356,24 +1356,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 opened DevSpace project. Use this to discover filenames or narrow file sets before reading. Respects project ignore rules. Call open_project first and pass projectId.", inputSchema: { - workspaceId: z + projectId: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe("Project identifier returned by open_project."), 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 opened project root."), }, outputSchema: resultOutputSchema(), ...toolWidgetDescriptorMeta(config, "search"), annotations: { readOnlyHint: true }, }, - async ({ workspaceId, ...input }) => { + async ({ projectId, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(projectId); if (input.path) workspaces.resolvePath(workspace, input.path); const response = await findFilesTool(input, { cwd: workspace.root, @@ -1383,7 +1383,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.glob, - workspaceId, + workspaceId: projectId, path: input.path, }, response.content, startedAt); return response; @@ -1396,7 +1396,7 @@ function createMcpServer( }; logToolCall(config, { tool: toolNames.glob, - workspaceId, + workspaceId: projectId, path: input.path, success: true, durationMs: Math.round(performance.now() - startedAt), @@ -1407,7 +1407,7 @@ function createMcpServer( _meta: { tool: toolNames.glob, card: { - workspaceId, + projectId, path: input.path, summary, payload: { content: response.content }, @@ -1426,24 +1426,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 opened DevSpace project. Use this for directory inspection before reading files. Call open_project first and pass projectId.", inputSchema: { - workspaceId: z + projectId: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe("Project identifier returned by open_project."), path: z .string() .describe( - "Directory path to list, relative to the workspace root.", + "Directory path to list, relative to the opened project root.", ), }, outputSchema: resultOutputSchema(), ...toolWidgetDescriptorMeta(config, "directory"), annotations: { readOnlyHint: true }, }, - async ({ workspaceId, ...input }) => { + async ({ projectId, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(projectId); workspaces.resolvePath(workspace, input.path); const response = await listDirectoryTool(input, { cwd: workspace.root, @@ -1453,7 +1453,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.ls, - workspaceId, + workspaceId: projectId, path: input.path, }, response.content, startedAt); return response; @@ -1462,7 +1462,7 @@ function createMcpServer( const summary = textSummary(response.content); logToolCall(config, { tool: toolNames.ls, - workspaceId, + workspaceId: projectId, path: input.path, success: true, durationMs: Math.round(performance.now() - startedAt), @@ -1473,7 +1473,7 @@ function createMcpServer( _meta: { tool: toolNames.ls, card: { - workspaceId, + projectId, path: input.path, summary, payload: { content: response.content }, @@ -1494,12 +1494,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 opened DevSpace project. 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_project first and pass projectId. This is powerful local execution and should only be exposed behind strong authentication.` + : `Run a shell command inside an opened DevSpace project. 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_project first and pass projectId. This is powerful local execution and should only be exposed behind strong authentication.`, inputSchema: { - workspaceId: z + projectId: z .string() - .describe("Workspace identifier returned by open_workspace."), + .describe("Project identifier returned by open_project."), command: z .string() .describe( @@ -1509,7 +1509,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 opened project root. Defaults to the project root.", ), timeout: z .number() @@ -1522,9 +1522,9 @@ function createMcpServer( ...toolWidgetDescriptorMeta(config, "shell"), annotations: SHELL_TOOL_ANNOTATIONS, }, - async ({ workspaceId, workingDirectory, ...input }) => { + async ({ projectId, workingDirectory, ...input }) => { const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); + const workspace = workspaces.getWorkspace(projectId); const cwd = workspaces.resolveWorkingDirectory( workspace, workingDirectory, @@ -1537,7 +1537,7 @@ function createMcpServer( if (response.isError) { logFailedToolResponse(config, { tool: toolNames.shell, - workspaceId, + workspaceId: projectId, workingDirectory: workingDirectory ?? ".", command: input.command, commandLength: input.command.length, @@ -1552,7 +1552,7 @@ function createMcpServer( }; logToolCall(config, { tool: toolNames.shell, - workspaceId, + workspaceId: projectId, workingDirectory: workingDirectory ?? ".", command: input.command, commandLength: input.command.length, @@ -1565,7 +1565,7 @@ function createMcpServer( _meta: { tool: toolNames.shell, card: { - workspaceId, + projectId, path: workingDirectory, summary, payload: { content: response.content }, diff --git a/src/ui/card-types.ts b/src/ui/card-types.ts index 1e3c9409..cc076a99 100644 --- a/src/ui/card-types.ts +++ b/src/ui/card-types.ts @@ -1,6 +1,7 @@ import type { App } from "@modelcontextprotocol/ext-apps"; export type ToolName = + | "open_project" | "open_workspace" | "show_changes" | "apply_patch" @@ -20,6 +21,7 @@ export type PatchOperation = "add" | "update" | "delete" | "move"; export interface ToolResultCard { tool: ToolName; + projectId?: string; workspaceId?: string; path?: string; root?: string; @@ -66,6 +68,7 @@ export interface ToolPayload { export function isToolName(value: unknown): value is ToolName { return ( value === "open_workspace" || + value === "open_project" || value === "show_changes" || value === "apply_patch" || value === "exec_command" || @@ -84,6 +87,10 @@ export function isReadTool(tool: ToolName): boolean { return tool === "read"; } +export function isOpenProjectTool(tool: ToolName): boolean { + return tool === "open_project" || tool === "open_workspace"; +} + export function isWriteTool(tool: ToolName): boolean { return tool === "write"; } @@ -133,7 +140,7 @@ export function summaryNumber( } export function isExpandableCard(card: ToolResultCard): boolean { - if (card.tool === "open_workspace") { + if (isOpenProjectTool(card.tool)) { 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..e345ac13 100644 --- a/src/ui/workspace-app.tsx +++ b/src/ui/workspace-app.tsx @@ -8,6 +8,7 @@ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { isEditTool, isExpandableCard, + isOpenProjectTool, isPatchTool, isReadTool, isReviewTool, @@ -228,8 +229,8 @@ async function renderPayloadIfNeeded(): Promise { return; } - if (card.tool === "open_workspace") { - renderPrePayload(target, workspacePayloadText(card), "open_workspace"); + if (isOpenProjectTool(card.tool)) { + renderPrePayload(target, workspacePayloadText(card), "open_project"); return; } @@ -353,11 +354,11 @@ function renderSummaryBadge(card: ToolResultCard): HTMLElement { return stats; } - if (card.tool === "open_workspace") { + if (isOpenProjectTool(card.tool)) { const agentsFiles = summaryNumber(summary, "agentsFiles") ?? 0; const skills = summaryNumber(summary, "skills") ?? 0; const group = element("span", { className: "badge-group" }); - group.setAttribute("aria-label", "Workspace summary"); + group.setAttribute("aria-label", "Project summary"); const agentsBadge = element("span", { className: `badge ${agentsFiles > 0 ? "success" : "muted"}`, @@ -465,7 +466,7 @@ function workspacePayloadText(card: ToolResultCard): string { const availableAgentsFiles = card.availableAgentsFiles ?? []; const skills = card.skills ?? []; const lines = [ - card.workspaceId ? `Workspace: ${card.workspaceId}` : undefined, + card.projectId || card.workspaceId ? `Project: ${card.projectId ?? card.workspaceId}` : undefined, card.root ? `Root: ${card.root}` : undefined, skills.length > 0 ? `Skills: ${skills.map((skill) => skill.name ?? skill.path ?? "unnamed").join(", ")}` @@ -515,8 +516,9 @@ function getToolDisplay(card: ToolResultCard): ToolDisplay { const label = getToolLabel(card); switch (card.tool) { + case "open_project": case "open_workspace": - return { icon: folderIcon(), title: "Workspace", label, tone: "workspace" }; + return { icon: folderIcon(), title: "Project", label, tone: "workspace" }; case "read": return { icon: fileIcon(), title: "Read File", label, tone: "read" }; case "write": diff --git a/src/workspaces.ts b/src/workspaces.ts index 3daa22cf..9dceb202 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 projectId: ${workspaceId}. Call open_project first.`); } const root = this.assertWorkspaceRootAllowed(session.root, session.mode, session.sourceRoot); From f7565ad1c6b9935c4c03cffe546327286e243d64 Mon Sep 17 00:00:00 2001 From: Waishnav Date: Tue, 7 Jul 2026 00:40:23 +0530 Subject: [PATCH 3/4] Document open project workflow --- AGENTS.md | 17 ++++++++--------- docs/agent-profile-schema.md | 6 +++--- docs/chatgpt-coding-workflow.md | 22 +++++++++++----------- docs/configuration.md | 8 ++++---- docs/gotchas.md | 12 ++++++------ skills/subagent-delegation/SKILL.md | 6 +++--- 6 files changed, 35 insertions(+), 36 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a5ad6cc9..dc3a3c72 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,15 +13,14 @@ 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 -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 -enters a directory with instructions that have not been loaded for that -workspace. +The model-facing workflow is project based. MCP clients should call +`open_project` once per local project directory or worktree, then reuse the +returned `projectId` for subsequent tool calls in that same folder. Do not call +`open_project` again for the same folder unless the `projectId` 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_project` and by later tool calls when the requested path enters a +directory with instructions that have not been loaded for that project. Core constraints: diff --git a/docs/agent-profile-schema.md b/docs/agent-profile-schema.md index 0dc3db95..58476c50 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_project` 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_project` by default. Recommended body content: @@ -137,7 +137,7 @@ devspace agents run "" devspace agents show ``` -`open_workspace` exposes compact profile metadata: +`open_project` exposes compact profile metadata: ```json { diff --git a/docs/chatgpt-coding-workflow.md b/docs/chatgpt-coding-workflow.md index 66062621..421c866f 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 Project -ChatGPT should call `open_workspace` once for a project folder: +ChatGPT should call `open_project` 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 `projectId`. All later file, search, edit, show-changes, +and shell calls should reuse that same `projectId`. Do not reopen the same folder unless: -- the `workspaceId` is rejected as unknown +- the `projectId` 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_project` 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_project` 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_project`; `devspace agents ls` lists existing subagent sessions for that workspace. ## Tool Names DevSpace exposes these tool names: -- `open_workspace` +- `open_project` - `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_project` - `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 `projectId`; 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..3c84e164 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_project`, `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_project`, `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_project` 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_project` 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..8b7990a9 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -130,14 +130,14 @@ To regenerate setup: npx @waishnav/devspace init --force ``` -## Unknown `workspaceId` +## Unknown `projectId` -`workspaceId` values are session identifiers. If the server restarts and the -client receives an unknown workspace error, call `open_workspace` again for that +`projectId` values are opened-project identifiers. If the server restarts and the +client receives an unknown project error, call `open_project` 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. +`open_project` as the way to begin a fresh working session. ## Workspace Path Rejected @@ -207,7 +207,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_project`. 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 +218,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_project`, 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/skills/subagent-delegation/SKILL.md b/skills/subagent-delegation/SKILL.md index fb269df5..091fa164 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_project`. `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_project`. 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_project`. Profiles may declare a model and optional thinking level. To override the configured/default provider model or thinking level for a run, pass `--model` From 3933ff0fd3c603ffea794af8a291dea86bdd4f6f Mon Sep 17 00:00:00 2001 From: Waishnav Date: Tue, 7 Jul 2026 00:44:53 +0530 Subject: [PATCH 4/4] Remove legacy workspace card support --- src/ui/card-types.ts | 9 +-------- src/ui/workspace-app.tsx | 8 +++----- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/ui/card-types.ts b/src/ui/card-types.ts index cc076a99..e913d6d2 100644 --- a/src/ui/card-types.ts +++ b/src/ui/card-types.ts @@ -2,7 +2,6 @@ import type { App } from "@modelcontextprotocol/ext-apps"; export type ToolName = | "open_project" - | "open_workspace" | "show_changes" | "apply_patch" | "exec_command" @@ -22,7 +21,6 @@ export type PatchOperation = "add" | "update" | "delete" | "move"; export interface ToolResultCard { tool: ToolName; projectId?: string; - workspaceId?: string; path?: string; root?: string; status?: string; @@ -67,7 +65,6 @@ export interface ToolPayload { export function isToolName(value: unknown): value is ToolName { return ( - value === "open_workspace" || value === "open_project" || value === "show_changes" || value === "apply_patch" || @@ -87,10 +84,6 @@ export function isReadTool(tool: ToolName): boolean { return tool === "read"; } -export function isOpenProjectTool(tool: ToolName): boolean { - return tool === "open_project" || tool === "open_workspace"; -} - export function isWriteTool(tool: ToolName): boolean { return tool === "write"; } @@ -140,7 +133,7 @@ export function summaryNumber( } export function isExpandableCard(card: ToolResultCard): boolean { - if (isOpenProjectTool(card.tool)) { + if (card.tool === "open_project") { 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 e345ac13..cbd1f3bf 100644 --- a/src/ui/workspace-app.tsx +++ b/src/ui/workspace-app.tsx @@ -8,7 +8,6 @@ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { isEditTool, isExpandableCard, - isOpenProjectTool, isPatchTool, isReadTool, isReviewTool, @@ -229,7 +228,7 @@ async function renderPayloadIfNeeded(): Promise { return; } - if (isOpenProjectTool(card.tool)) { + if (card.tool === "open_project") { renderPrePayload(target, workspacePayloadText(card), "open_project"); return; } @@ -354,7 +353,7 @@ function renderSummaryBadge(card: ToolResultCard): HTMLElement { return stats; } - if (isOpenProjectTool(card.tool)) { + if (card.tool === "open_project") { const agentsFiles = summaryNumber(summary, "agentsFiles") ?? 0; const skills = summaryNumber(summary, "skills") ?? 0; const group = element("span", { className: "badge-group" }); @@ -466,7 +465,7 @@ function workspacePayloadText(card: ToolResultCard): string { const availableAgentsFiles = card.availableAgentsFiles ?? []; const skills = card.skills ?? []; const lines = [ - card.projectId || card.workspaceId ? `Project: ${card.projectId ?? card.workspaceId}` : undefined, + card.projectId ? `Project: ${card.projectId}` : undefined, card.root ? `Root: ${card.root}` : undefined, skills.length > 0 ? `Skills: ${skills.map((skill) => skill.name ?? skill.path ?? "unnamed").join(", ")}` @@ -517,7 +516,6 @@ function getToolDisplay(card: ToolResultCard): ToolDisplay { switch (card.tool) { case "open_project": - case "open_workspace": return { icon: folderIcon(), title: "Project", label, tone: "workspace" }; case "read": return { icon: fileIcon(), title: "Read File", label, tone: "read" };