@waishnav/devspace 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -7
- package/dist/apply-patch.js +402 -0
- package/dist/cli.js +256 -4
- package/dist/config.js +17 -22
- package/dist/db/migrations.js +34 -0
- package/dist/db/schema.js +19 -0
- package/dist/local-agent-adapters.js +653 -0
- package/dist/local-agent-availability.js +119 -0
- package/dist/local-agent-path.js +24 -0
- package/dist/local-agent-profiles.js +135 -0
- package/dist/local-agent-runtime.js +48 -0
- package/dist/local-agent-store.js +145 -0
- package/dist/local-agent-targets.js +80 -0
- package/dist/process-platform.js +49 -0
- package/dist/process-sessions.js +337 -0
- package/dist/roots.js +5 -2
- package/dist/server.js +521 -243
- package/dist/skills.js +48 -4
- package/dist/ui/.vite/manifest.json +281 -281
- package/dist/ui/assets/{angular-html-Dj4LysSE.js → angular-html-CKeFzaPY.js} +1 -1
- package/dist/ui/assets/{angular-ts-CxuC_FFh.js → angular-ts-0wcM_Zxf.js} +1 -1
- package/dist/ui/assets/{apl-RwuvNFFo.js → apl-C7UdkNb2.js} +1 -1
- package/dist/ui/assets/{astro-ncoaUbsO.js → astro-DMqju4MJ.js} +1 -1
- package/dist/ui/assets/{blade-Dw8-Lzux.js → blade-DHanNgN5.js} +1 -1
- package/dist/ui/assets/{c-Dpbbo_f2.js → c-_iPKYPcx.js} +1 -1
- package/dist/ui/assets/{cobol-4ddX3THy.js → cobol-CagdLJsE.js} +1 -1
- package/dist/ui/assets/{coffee-Bn-a5KqK.js → coffee-CkNGriPi.js} +1 -1
- package/dist/ui/assets/{cpp-DoRrlM_T.js → cpp-CqmKOWIQ.js} +1 -1
- package/dist/ui/assets/{crystal-DiZfCxn-.js → crystal-DbAbDEO1.js} +1 -1
- package/dist/ui/assets/{css-DiQoSjDH.js → css-xwljL6Kw.js} +1 -1
- package/dist/ui/assets/{edge-wEhCUoEt.js → edge-CX3sYz15.js} +1 -1
- package/dist/ui/assets/{elixir-C3obhm0H.js → elixir-B3qL9hqd.js} +1 -1
- package/dist/ui/assets/{elm-DyTBfh1L.js → elm-CH4vrjBp.js} +1 -1
- package/dist/ui/assets/{erb-f4TnXRuY.js → erb-CupHqynN.js} +1 -1
- package/dist/ui/assets/{git-rebase-vhZbWNfp.js → git-rebase-CDxsU2LC.js} +1 -1
- package/dist/ui/assets/{glimmer-js-BG7puL6Y.js → glimmer-js-emkga3g1.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-CNCfLLL1.js → glimmer-ts-D8kQDnUw.js} +1 -1
- package/dist/ui/assets/{glsl-DhT76AL5.js → glsl-BtfTaPhi.js} +1 -1
- package/dist/ui/assets/{graphql-CJX08w8y.js → graphql-DEus8iSO.js} +1 -1
- package/dist/ui/assets/{hack-DY4NGCCL.js → hack-DI8qpBqE.js} +1 -1
- package/dist/ui/assets/{haml-DwSkmVcG.js → haml-Br1i1gmi.js} +1 -1
- package/dist/ui/assets/{handlebars-CQ_1dEmi.js → handlebars-Ce23mu9x.js} +1 -1
- package/dist/ui/assets/{heavy-payload-2rGBiitA.js → heavy-payload-BcZ_nGfF.js} +1 -1
- package/dist/ui/assets/{html-B_e3Njif.js → html-RXVmfixf.js} +1 -1
- package/dist/ui/assets/{html-derivative-_nHZ_Bk-.js → html-derivative-D1aqeobb.js} +1 -1
- package/dist/ui/assets/{http-CfcvPIru.js → http-BebG00CE.js} +1 -1
- package/dist/ui/assets/{hurl-9vWDQIMO.js → hurl-kHMuvenL.js} +1 -1
- package/dist/ui/assets/{java-C4S5r6L5.js → java-DYc2V5RJ.js} +1 -1
- package/dist/ui/assets/{javascript-BRwqbMMC.js → javascript-BcfzTzr3.js} +1 -1
- package/dist/ui/assets/{jinja-DHxzMMc5.js → jinja-D2RhihUZ.js} +1 -1
- package/dist/ui/assets/{jison-B1DqeWXb.js → jison-BuoFEePN.js} +1 -1
- package/dist/ui/assets/{json-DcFTkEdd.js → json-BIDkCuJA.js} +1 -1
- package/dist/ui/assets/{jsx-DgfmYa9f.js → jsx-DHCL2HA0.js} +1 -1
- package/dist/ui/assets/{julia-3M8SJ8iF.js → julia-CvUQXscF.js} +1 -1
- package/dist/ui/assets/{just-UyH7_DNz.js → just-etc9R2qw.js} +1 -1
- package/dist/ui/assets/{latex-wUbha6EX.js → latex-BB3zRnxC.js} +1 -1
- package/dist/ui/assets/{liquid-DqNTB9kN.js → liquid-CIi5Mvpv.js} +1 -1
- package/dist/ui/assets/{lua-DrEQ1QTW.js → lua-Cox7XDiW.js} +1 -1
- package/dist/ui/assets/{marko-B3GJw9Md.js → marko-ruBIRV0n.js} +1 -1
- package/dist/ui/assets/{mdc-BnThk82m.js → mdc-CcbvB9U5.js} +1 -1
- package/dist/ui/assets/{nginx-B5IeSsOX.js → nginx-DtWz6ZYE.js} +1 -1
- package/dist/ui/assets/{nim-BJraatnS.js → nim-BV5BR4P7.js} +1 -1
- package/dist/ui/assets/{perl-BAhrR4gt.js → perl-BvzoSaJ4.js} +1 -1
- package/dist/ui/assets/{php-BKRCk_wX.js → php-C-clrAOy.js} +1 -1
- package/dist/ui/assets/{pug-B8gmSV7z.js → pug-E07_GXAr.js} +1 -1
- package/dist/ui/assets/{qml-DKrxIjHR.js → qml-dqt33dti.js} +1 -1
- package/dist/ui/assets/{r-YASBZvTH.js → r-BcVQxsAw.js} +1 -1
- package/dist/ui/assets/{razor-P-XFZqh6.js → razor-BW5T_UZ-.js} +1 -1
- package/dist/ui/assets/{regexp-CCpiH6nO.js → regexp-Ed_DEsuU.js} +1 -1
- package/dist/ui/assets/{review-payload-BLYwg6t8.js → review-payload-Dsmx33pY.js} +1 -1
- package/dist/ui/assets/{rst-CFAzckbn.js → rst-BkcGqLs8.js} +1 -1
- package/dist/ui/assets/{ruby-Dyp_Rx-S.js → ruby-Sf_X_YID.js} +1 -1
- package/dist/ui/assets/{sas-YkWZIXxT.js → sas-COsombsA.js} +1 -1
- package/dist/ui/assets/{scss-Cf89idGl.js → scss-K6Ylfa26.js} +1 -1
- package/dist/ui/assets/{shellscript-BFMb52q8.js → shellscript-D2EdqkgV.js} +1 -1
- package/dist/ui/assets/{shellsession-DuthW-5v.js → shellsession-jRXgXrnI.js} +1 -1
- package/dist/ui/assets/{soy-BohVVlJU.js → soy-BGspKQWW.js} +1 -1
- package/dist/ui/assets/{sql-CnK9R2Gt.js → sql-C4lAmYnM.js} +1 -1
- package/dist/ui/assets/{stata-D-sV4Vbr.js → stata-DQWvRqxp.js} +1 -1
- package/dist/ui/assets/{surrealql-ChAy0gHv.js → surrealql-OmKt9XJ_.js} +1 -1
- package/dist/ui/assets/{svelte-3HBVqG-H.js → svelte-D0rUmwDI.js} +1 -1
- package/dist/ui/assets/{templ-BixlV4Cp.js → templ-B-U0RMc0.js} +1 -1
- package/dist/ui/assets/{tex-DWoJKHvD.js → tex-BIHD-btS.js} +1 -1
- package/dist/ui/assets/{ts-tags-DLA8eH13.js → ts-tags-CGuDWfPC.js} +1 -1
- package/dist/ui/assets/{tsx-BYWa5JUz.js → tsx-B0-h_HlD.js} +1 -1
- package/dist/ui/assets/{twig-CnY65vAo.js → twig-BN2qDJWN.js} +1 -1
- package/dist/ui/assets/{typescript-ByInUsro.js → typescript-BRnwPzY3.js} +1 -1
- package/dist/ui/assets/{useFileDiffInstance-Dg0pWcJV.js → useFileDiffInstance-5ivdkUYO.js} +3 -3
- package/dist/ui/assets/{vue-Qngt2rkz.js → vue-DekNU1qt.js} +1 -1
- package/dist/ui/assets/{vue-html-DDue9upr.js → vue-html-Bjlv-uvH.js} +1 -1
- package/dist/ui/assets/{vue-vine-DBcAKsA6.js → vue-vine-C2Doytjq.js} +1 -1
- package/dist/ui/assets/{workspace-app-DGD9R89D.css → workspace-app-DASlX26z.css} +1 -1
- package/dist/ui/assets/{workspace-app-DuzpU4Hf.js → workspace-app-DYi7VJam.js} +5 -5
- package/dist/ui/assets/{xml-CRLR5jiL.js → xml-pXbxxgI3.js} +1 -1
- package/dist/ui/assets/{xsl-CmcwyXbm.js → xsl-BEAlgDI2.js} +1 -1
- package/dist/ui/assets/{yaml-Dpe6Eg-k.js → yaml-C1O-To71.js} +1 -1
- package/dist/ui/workspace-app.html +2 -2
- package/dist/user-config.js +21 -1
- package/dist/workspaces.js +3 -0
- package/docs/agent-profile-schema.md +165 -0
- package/docs/chatgpt-coding-workflow.md +46 -12
- package/docs/configuration.md +47 -12
- package/docs/gotchas.md +24 -5
- package/docs/setup.md +1 -1
- package/examples/agents/claude-implementer.md +30 -0
- package/examples/agents/codex-explorer.md +27 -0
- package/examples/agents/codex-qa-tester.md +29 -0
- package/examples/agents/codex-worker.md +25 -0
- package/examples/agents/copilot-reviewer.md +25 -0
- package/examples/agents/cursor-agent-worker.md +26 -0
- package/examples/agents/opencode-explorer.md +27 -0
- package/examples/agents/pi-reviewer.md +27 -0
- package/package.json +18 -5
- package/scripts/fix-node-pty-permissions.mjs +22 -0
- package/skills/subagent-delegation/SKILL.md +132 -0
package/dist/server.js
CHANGED
|
@@ -12,14 +12,18 @@ import { checkResourceAllowed, resourceUrlFromServerUrl } from "@modelcontextpro
|
|
|
12
12
|
import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server";
|
|
13
13
|
import express from "express";
|
|
14
14
|
import * as z from "zod/v4";
|
|
15
|
+
import { applyPatch } from "./apply-patch.js";
|
|
15
16
|
import { loadConfig } from "./config.js";
|
|
16
17
|
import { logEvent, requestIp, requestPath, commandPreview, sessionIdPrefix, } from "./logger.js";
|
|
17
18
|
import { editFileTool, findFilesTool, grepFilesTool, listDirectoryTool, readFileTool, runShellTool, writeFileTool, } from "./pi-tools.js";
|
|
18
19
|
import { SingleUserOAuthProvider } from "./oauth-provider.js";
|
|
20
|
+
import { ProcessSessionManager } from "./process-sessions.js";
|
|
19
21
|
import { createReviewCheckpointManager } from "./review-checkpoints.js";
|
|
20
22
|
import { formatPathForPrompt } from "./skills.js";
|
|
21
23
|
import { createWorkspaceStore } from "./workspace-store.js";
|
|
22
24
|
import { formatAgentsPath, WorkspaceRegistry } from "./workspaces.js";
|
|
25
|
+
import { summarizeLocalAgentProfile } from "./local-agent-profiles.js";
|
|
26
|
+
import { formatLocalAgentProviderAvailabilitySummary, getLocalAgentProviderAvailabilitySnapshot, } from "./local-agent-availability.js";
|
|
23
27
|
const WORKSPACE_APP_URI = "ui://devspace/workspace-app.html";
|
|
24
28
|
const WORKSPACE_APP_MANIFEST_ENTRY = "workspace-app.html";
|
|
25
29
|
const WRITE_TOOL_ANNOTATIONS = {
|
|
@@ -62,41 +66,42 @@ function toolWidgetDescriptorMeta(config, kind) {
|
|
|
62
66
|
},
|
|
63
67
|
};
|
|
64
68
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
glob: "find_files",
|
|
84
|
-
ls: "list_directory",
|
|
85
|
-
shell: "run_shell",
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function serverInstructions(config, toolNames) {
|
|
89
|
-
const inspection = config.minimalTools
|
|
69
|
+
const toolNames = {
|
|
70
|
+
openWorkspace: "open_workspace",
|
|
71
|
+
read: "read",
|
|
72
|
+
write: "write",
|
|
73
|
+
edit: "edit",
|
|
74
|
+
grep: "grep",
|
|
75
|
+
glob: "glob",
|
|
76
|
+
ls: "ls",
|
|
77
|
+
shell: "bash",
|
|
78
|
+
};
|
|
79
|
+
function serverInstructions(config) {
|
|
80
|
+
const showChangesInstruction = config.widgets === "changes"
|
|
81
|
+
? " 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."
|
|
82
|
+
: "";
|
|
83
|
+
if (config.toolMode === "codex") {
|
|
84
|
+
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}`;
|
|
85
|
+
}
|
|
86
|
+
const inspection = config.toolMode !== "full"
|
|
90
87
|
? `In minimal tool mode, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} are disabled; use ${toolNames.shell} with command-line tools such as grep, rg, find, ls, and tree for search and directory inspection. `
|
|
91
88
|
: `Prefer ${toolNames.read}, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} for file inspection. `;
|
|
92
89
|
const skills = config.skillsEnabled
|
|
93
90
|
? `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. `
|
|
94
91
|
: "";
|
|
95
92
|
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. `;
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
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}`;
|
|
94
|
+
}
|
|
95
|
+
function formatVisibleAgent(agent) {
|
|
96
|
+
const model = agent.model ? `, model ${agent.model}` : "";
|
|
97
|
+
const thinking = agent.thinking ? `, thinking ${agent.thinking}` : "";
|
|
98
|
+
const availability = agent.providerAvailable === false
|
|
99
|
+
? `, unavailable: ${agent.providerUnavailableReason ?? "provider unavailable"}`
|
|
98
100
|
: "";
|
|
99
|
-
return
|
|
101
|
+
return `${agent.name} (${agent.provider}${model}${thinking}${availability})`;
|
|
102
|
+
}
|
|
103
|
+
function formatUnavailableAgentProvider(provider) {
|
|
104
|
+
return `${provider.name} (${provider.reason ?? "unavailable"})`;
|
|
100
105
|
}
|
|
101
106
|
function resultOutputSchema(extra = {}) {
|
|
102
107
|
return {
|
|
@@ -115,6 +120,20 @@ const workspaceAgentsFileOutputSchema = z.object({
|
|
|
115
120
|
path: z.string(),
|
|
116
121
|
content: z.string(),
|
|
117
122
|
});
|
|
123
|
+
const workspaceLocalAgentOutputSchema = z.object({
|
|
124
|
+
name: z.string(),
|
|
125
|
+
description: z.string(),
|
|
126
|
+
provider: z.string(),
|
|
127
|
+
model: z.string().optional(),
|
|
128
|
+
thinking: z.string().optional(),
|
|
129
|
+
providerAvailable: z.boolean().optional(),
|
|
130
|
+
providerUnavailableReason: z.string().optional(),
|
|
131
|
+
});
|
|
132
|
+
const workspaceLocalAgentProviderOutputSchema = z.object({
|
|
133
|
+
name: z.string(),
|
|
134
|
+
available: z.boolean(),
|
|
135
|
+
reason: z.string().optional(),
|
|
136
|
+
});
|
|
118
137
|
const workspaceAvailableAgentsFileOutputSchema = z.object({
|
|
119
138
|
path: z.string(),
|
|
120
139
|
});
|
|
@@ -292,15 +311,178 @@ async function assertWorkspaceAppAssets() {
|
|
|
292
311
|
await access(candidate);
|
|
293
312
|
}
|
|
294
313
|
}
|
|
295
|
-
function
|
|
296
|
-
const
|
|
314
|
+
function processResult(snapshot) {
|
|
315
|
+
const status = snapshot.running
|
|
316
|
+
? `Process running with session ID ${snapshot.sessionId}.`
|
|
317
|
+
: snapshot.signal
|
|
318
|
+
? `Process exited after signal ${snapshot.signal}.`
|
|
319
|
+
: `Process exited with code ${snapshot.exitCode ?? "unknown"}.`;
|
|
320
|
+
return snapshot.output ? `${snapshot.output.replace(/\n$/, "")}\n${status}` : status;
|
|
321
|
+
}
|
|
322
|
+
function processOutputSchema() {
|
|
323
|
+
return resultOutputSchema({
|
|
324
|
+
sessionId: z.number().optional(),
|
|
325
|
+
running: z.boolean(),
|
|
326
|
+
exitCode: z.number().int().optional(),
|
|
327
|
+
signal: z.string().optional(),
|
|
328
|
+
wallTimeMs: z.number().nonnegative(),
|
|
329
|
+
outputTruncated: z.boolean(),
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
function processToolResponse(tool, workspaceId, snapshot, summary) {
|
|
333
|
+
const result = processResult(snapshot);
|
|
334
|
+
const content = [textBlock(result)];
|
|
335
|
+
const outputSummary = textSummary(snapshot.output ? [textBlock(snapshot.output)] : []);
|
|
336
|
+
return {
|
|
337
|
+
content,
|
|
338
|
+
_meta: {
|
|
339
|
+
tool,
|
|
340
|
+
card: {
|
|
341
|
+
workspaceId,
|
|
342
|
+
summary: { ...summary, ...outputSummary },
|
|
343
|
+
payload: { content },
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
structuredContent: {
|
|
347
|
+
result,
|
|
348
|
+
sessionId: snapshot.sessionId,
|
|
349
|
+
running: snapshot.running,
|
|
350
|
+
exitCode: snapshot.exitCode,
|
|
351
|
+
signal: snapshot.signal,
|
|
352
|
+
wallTimeMs: snapshot.wallTimeMs,
|
|
353
|
+
outputTruncated: snapshot.outputTruncated,
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function registerCodexProcessTools(server, config, workspaces, processSessions) {
|
|
358
|
+
registerAppTool(server, "exec_command", {
|
|
359
|
+
title: "Execute command",
|
|
360
|
+
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.",
|
|
361
|
+
inputSchema: {
|
|
362
|
+
workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
|
|
363
|
+
cmd: z.string().min(1).describe("Shell command to execute."),
|
|
364
|
+
tty: z
|
|
365
|
+
.boolean()
|
|
366
|
+
.optional()
|
|
367
|
+
.describe("Allocate a pseudo-terminal for interactive commands. Defaults to false."),
|
|
368
|
+
columns: z.number().int().min(1).max(1_000).optional().describe("Initial PTY width. Defaults to 80."),
|
|
369
|
+
rows: z.number().int().min(1).max(1_000).optional().describe("Initial PTY height. Defaults to 24."),
|
|
370
|
+
workingDirectory: z
|
|
371
|
+
.string()
|
|
372
|
+
.optional()
|
|
373
|
+
.describe("Working directory relative to the workspace root. Defaults to the workspace root."),
|
|
374
|
+
yieldTimeMs: z
|
|
375
|
+
.number()
|
|
376
|
+
.int()
|
|
377
|
+
.min(0)
|
|
378
|
+
.max(30_000)
|
|
379
|
+
.optional()
|
|
380
|
+
.describe("Milliseconds to wait before returning a running session. Defaults to 10000."),
|
|
381
|
+
maxOutputTokens: z
|
|
382
|
+
.number()
|
|
383
|
+
.int()
|
|
384
|
+
.positive()
|
|
385
|
+
.max(100_000)
|
|
386
|
+
.optional()
|
|
387
|
+
.describe("Approximate output token budget. Defaults to 10000."),
|
|
388
|
+
},
|
|
389
|
+
outputSchema: processOutputSchema(),
|
|
390
|
+
...toolWidgetDescriptorMeta(config, "shell"),
|
|
391
|
+
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
392
|
+
}, async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, maxOutputTokens }) => {
|
|
393
|
+
const startedAt = performance.now();
|
|
394
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
395
|
+
const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
|
|
396
|
+
const snapshot = await processSessions.start({
|
|
397
|
+
workspaceId,
|
|
398
|
+
command: cmd,
|
|
399
|
+
cwd,
|
|
400
|
+
workspaceRoot: workspace.root,
|
|
401
|
+
tty,
|
|
402
|
+
columns,
|
|
403
|
+
rows,
|
|
404
|
+
yieldTimeMs,
|
|
405
|
+
maxOutputTokens,
|
|
406
|
+
});
|
|
407
|
+
logToolCall(config, {
|
|
408
|
+
tool: "exec_command",
|
|
409
|
+
workspaceId,
|
|
410
|
+
workingDirectory: workingDirectory ?? ".",
|
|
411
|
+
command: cmd,
|
|
412
|
+
commandLength: cmd.length,
|
|
413
|
+
success: true,
|
|
414
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
415
|
+
});
|
|
416
|
+
return processToolResponse("exec_command", workspaceId, snapshot, {
|
|
417
|
+
command: cmd,
|
|
418
|
+
workingDirectory: workingDirectory ?? ".",
|
|
419
|
+
running: snapshot.running,
|
|
420
|
+
exitCode: snapshot.exitCode,
|
|
421
|
+
wallTimeMs: snapshot.wallTimeMs,
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
registerAppTool(server, "write_stdin", {
|
|
425
|
+
title: "Write to process",
|
|
426
|
+
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.",
|
|
427
|
+
inputSchema: {
|
|
428
|
+
workspaceId: z.string().describe("Workspace identifier used to start the process."),
|
|
429
|
+
sessionId: z.number().describe("Process session identifier returned by exec_command."),
|
|
430
|
+
chars: z.string().optional().describe("Characters to write. Omit or pass an empty string to poll."),
|
|
431
|
+
columns: z.number().int().min(1).max(1_000).optional().describe("Resize a PTY to this width."),
|
|
432
|
+
rows: z.number().int().min(1).max(1_000).optional().describe("Resize a PTY to this height."),
|
|
433
|
+
yieldTimeMs: z
|
|
434
|
+
.number()
|
|
435
|
+
.int()
|
|
436
|
+
.min(0)
|
|
437
|
+
.max(30_000)
|
|
438
|
+
.optional()
|
|
439
|
+
.describe("Milliseconds to wait for process output or completion. Defaults to 10000."),
|
|
440
|
+
maxOutputTokens: z
|
|
441
|
+
.number()
|
|
442
|
+
.int()
|
|
443
|
+
.positive()
|
|
444
|
+
.max(100_000)
|
|
445
|
+
.optional()
|
|
446
|
+
.describe("Approximate output token budget. Defaults to 10000."),
|
|
447
|
+
},
|
|
448
|
+
outputSchema: processOutputSchema(),
|
|
449
|
+
...toolWidgetDescriptorMeta(config, "shell"),
|
|
450
|
+
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
451
|
+
}, async ({ workspaceId, sessionId, chars, columns, rows, yieldTimeMs, maxOutputTokens }) => {
|
|
452
|
+
const startedAt = performance.now();
|
|
453
|
+
workspaces.getWorkspace(workspaceId);
|
|
454
|
+
const snapshot = await processSessions.write({
|
|
455
|
+
workspaceId,
|
|
456
|
+
sessionId,
|
|
457
|
+
chars,
|
|
458
|
+
columns,
|
|
459
|
+
rows,
|
|
460
|
+
yieldTimeMs,
|
|
461
|
+
maxOutputTokens,
|
|
462
|
+
});
|
|
463
|
+
logToolCall(config, {
|
|
464
|
+
tool: "write_stdin",
|
|
465
|
+
workspaceId,
|
|
466
|
+
success: true,
|
|
467
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
468
|
+
});
|
|
469
|
+
return processToolResponse("write_stdin", workspaceId, snapshot, {
|
|
470
|
+
sessionId,
|
|
471
|
+
charactersWritten: chars?.length ?? 0,
|
|
472
|
+
running: snapshot.running,
|
|
473
|
+
exitCode: snapshot.exitCode,
|
|
474
|
+
wallTimeMs: snapshot.wallTimeMs,
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
function createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders) {
|
|
297
479
|
const server = new McpServer({
|
|
298
480
|
name: "devspace",
|
|
299
481
|
title: "DevSpace",
|
|
300
482
|
version: "0.1.0",
|
|
301
483
|
description: "Secure local coding workspace for MCP clients. Provides workspace-scoped file, search, edit, write, and shell tools.",
|
|
302
484
|
}, {
|
|
303
|
-
instructions: serverInstructions(config
|
|
485
|
+
instructions: serverInstructions(config),
|
|
304
486
|
});
|
|
305
487
|
registerAppResource(server, "DevSpace Diff Card", WORKSPACE_APP_URI, {
|
|
306
488
|
description: "Interactive card for viewing DevSpace file diffs.",
|
|
@@ -360,6 +542,8 @@ function createMcpServer(config, workspaces, reviewCheckpoints) {
|
|
|
360
542
|
agentsFiles: z.array(workspaceAgentsFileOutputSchema),
|
|
361
543
|
availableAgentsFiles: z.array(workspaceAvailableAgentsFileOutputSchema),
|
|
362
544
|
skills: z.array(workspaceSkillOutputSchema),
|
|
545
|
+
agentProviders: z.array(workspaceLocalAgentProviderOutputSchema),
|
|
546
|
+
agents: z.array(workspaceLocalAgentOutputSchema),
|
|
363
547
|
skillDiagnostics: z.array(z.unknown()),
|
|
364
548
|
instruction: z.string(),
|
|
365
549
|
},
|
|
@@ -381,6 +565,16 @@ function createMcpServer(config, workspaces, reviewCheckpoints) {
|
|
|
381
565
|
description: skill.description,
|
|
382
566
|
path: formatPathForPrompt(skill.filePath),
|
|
383
567
|
}));
|
|
568
|
+
const visibleAgentProviders = config.subagents ? localAgentProviders : [];
|
|
569
|
+
const visibleAgents = workspace.agentProfiles.map((profile) => {
|
|
570
|
+
const summary = summarizeLocalAgentProfile(profile);
|
|
571
|
+
const availability = visibleAgentProviders.find((provider) => provider.name === summary.provider);
|
|
572
|
+
return {
|
|
573
|
+
...summary,
|
|
574
|
+
providerAvailable: availability?.available,
|
|
575
|
+
providerUnavailableReason: availability?.reason,
|
|
576
|
+
};
|
|
577
|
+
});
|
|
384
578
|
const loadedAgentsFiles = agentsFiles.map((file) => ({
|
|
385
579
|
path: formatAgentsPath(file.path, workspace.root),
|
|
386
580
|
content: file.content,
|
|
@@ -407,6 +601,15 @@ function createMcpServer(config, workspaces, reviewCheckpoints) {
|
|
|
407
601
|
visibleSkills.length > 0
|
|
408
602
|
? `Available skills: ${visibleSkills.map((skill) => skill.name).join(", ")}`
|
|
409
603
|
: undefined,
|
|
604
|
+
visibleAgentProviders.some((provider) => provider.available)
|
|
605
|
+
? `Available subagent providers: ${visibleAgentProviders.filter((provider) => provider.available).map((provider) => provider.name).join(", ")}`
|
|
606
|
+
: undefined,
|
|
607
|
+
visibleAgentProviders.some((provider) => !provider.available)
|
|
608
|
+
? `Unavailable subagent providers: ${visibleAgentProviders.filter((provider) => !provider.available).map(formatUnavailableAgentProvider).join(", ")}`
|
|
609
|
+
: undefined,
|
|
610
|
+
visibleAgents.length > 0
|
|
611
|
+
? `Available subagent profiles: ${visibleAgents.map(formatVisibleAgent).join(", ")}`
|
|
612
|
+
: undefined,
|
|
410
613
|
instruction,
|
|
411
614
|
].filter(Boolean).join("\n"),
|
|
412
615
|
},
|
|
@@ -430,6 +633,8 @@ function createMcpServer(config, workspaces, reviewCheckpoints) {
|
|
|
430
633
|
agentsFiles: loadedAgentsFiles.length,
|
|
431
634
|
availableAgentsFiles: availableAgentsFileOutputs.length,
|
|
432
635
|
skills: visibleSkills.length,
|
|
636
|
+
agentProviders: visibleAgentProviders.length,
|
|
637
|
+
agents: visibleAgents.length,
|
|
433
638
|
skillDiagnostics: workspace.skillDiagnostics.length,
|
|
434
639
|
},
|
|
435
640
|
},
|
|
@@ -443,6 +648,8 @@ function createMcpServer(config, workspaces, reviewCheckpoints) {
|
|
|
443
648
|
agentsFiles: loadedAgentsFiles,
|
|
444
649
|
availableAgentsFiles: availableAgentsFileOutputs,
|
|
445
650
|
skills: visibleSkills,
|
|
651
|
+
agentProviders: visibleAgentProviders,
|
|
652
|
+
agents: visibleAgents,
|
|
446
653
|
skillDiagnostics: workspace.skillDiagnostics,
|
|
447
654
|
instruction,
|
|
448
655
|
},
|
|
@@ -530,172 +737,229 @@ function createMcpServer(config, workspaces, reviewCheckpoints) {
|
|
|
530
737
|
},
|
|
531
738
|
};
|
|
532
739
|
});
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
740
|
+
if (config.toolMode !== "codex") {
|
|
741
|
+
registerAppTool(server, toolNames.write, {
|
|
742
|
+
title: "Write file",
|
|
743
|
+
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.`,
|
|
744
|
+
inputSchema: {
|
|
745
|
+
workspaceId: z
|
|
746
|
+
.string()
|
|
747
|
+
.describe("Workspace identifier returned by open_workspace."),
|
|
748
|
+
path: z
|
|
749
|
+
.string()
|
|
750
|
+
.describe("File path to write, relative to the workspace root."),
|
|
751
|
+
content: z.string().describe("Complete new file content."),
|
|
752
|
+
},
|
|
753
|
+
outputSchema: resultOutputSchema(),
|
|
754
|
+
...toolWidgetDescriptorMeta(config, "write"),
|
|
755
|
+
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
756
|
+
}, async ({ workspaceId, ...input }) => {
|
|
757
|
+
const startedAt = performance.now();
|
|
758
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
759
|
+
workspaces.resolvePath(workspace, input.path);
|
|
760
|
+
const response = await writeFileTool(input, {
|
|
761
|
+
cwd: workspace.root,
|
|
762
|
+
root: workspace.root,
|
|
763
|
+
});
|
|
764
|
+
if (response.isError) {
|
|
765
|
+
logFailedToolResponse(config, {
|
|
766
|
+
tool: toolNames.write,
|
|
767
|
+
workspaceId,
|
|
768
|
+
path: input.path,
|
|
769
|
+
}, response.content, startedAt);
|
|
770
|
+
return response;
|
|
771
|
+
}
|
|
772
|
+
const patch = newFilePatch(input.path, input.content);
|
|
773
|
+
const stats = countDiffStats(patch);
|
|
774
|
+
const summary = {
|
|
775
|
+
...stats,
|
|
776
|
+
lines: contentLineCount(input.content),
|
|
777
|
+
characters: input.content.length,
|
|
778
|
+
};
|
|
779
|
+
logToolCall(config, {
|
|
558
780
|
tool: toolNames.write,
|
|
559
781
|
workspaceId,
|
|
560
782
|
path: input.path,
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
577
|
-
});
|
|
578
|
-
return {
|
|
579
|
-
...response,
|
|
580
|
-
_meta: {
|
|
581
|
-
tool: toolNames.write,
|
|
582
|
-
card: {
|
|
583
|
-
workspaceId,
|
|
584
|
-
path: input.path,
|
|
585
|
-
summary,
|
|
586
|
-
payload: {
|
|
587
|
-
content: response.content,
|
|
588
|
-
patch,
|
|
783
|
+
success: true,
|
|
784
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
785
|
+
});
|
|
786
|
+
return {
|
|
787
|
+
...response,
|
|
788
|
+
_meta: {
|
|
789
|
+
tool: toolNames.write,
|
|
790
|
+
card: {
|
|
791
|
+
workspaceId,
|
|
792
|
+
path: input.path,
|
|
793
|
+
summary,
|
|
794
|
+
payload: {
|
|
795
|
+
content: response.content,
|
|
796
|
+
patch,
|
|
797
|
+
},
|
|
589
798
|
},
|
|
590
799
|
},
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
}
|
|
595
|
-
};
|
|
596
|
-
});
|
|
597
|
-
registerAppTool(server, toolNames.edit, {
|
|
598
|
-
title: "Edit file",
|
|
599
|
-
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.`,
|
|
600
|
-
inputSchema: {
|
|
601
|
-
workspaceId: z
|
|
602
|
-
.string()
|
|
603
|
-
.describe("Workspace identifier returned by open_workspace."),
|
|
604
|
-
path: z
|
|
605
|
-
.string()
|
|
606
|
-
.describe("File path to edit, relative to the workspace root."),
|
|
607
|
-
edits: z
|
|
608
|
-
.array(z.object({
|
|
609
|
-
oldText: z
|
|
610
|
-
.string()
|
|
611
|
-
.describe("Exact text to replace. Must match uniquely in the original file."),
|
|
612
|
-
newText: z.string().describe("Replacement text."),
|
|
613
|
-
}))
|
|
614
|
-
.min(1),
|
|
615
|
-
},
|
|
616
|
-
outputSchema: resultOutputSchema({
|
|
617
|
-
status: z.literal("applied"),
|
|
618
|
-
}),
|
|
619
|
-
...toolWidgetDescriptorMeta(config, "edit"),
|
|
620
|
-
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
621
|
-
}, async ({ workspaceId, ...input }) => {
|
|
622
|
-
const startedAt = performance.now();
|
|
623
|
-
const workspace = workspaces.getWorkspace(workspaceId);
|
|
624
|
-
workspaces.resolvePath(workspace, input.path);
|
|
625
|
-
const response = await editFileTool(input, {
|
|
626
|
-
cwd: workspace.root,
|
|
627
|
-
root: workspace.root,
|
|
800
|
+
structuredContent: {
|
|
801
|
+
result: contentText(response.content),
|
|
802
|
+
},
|
|
803
|
+
};
|
|
628
804
|
});
|
|
629
|
-
|
|
630
|
-
|
|
805
|
+
registerAppTool(server, toolNames.edit, {
|
|
806
|
+
title: "Edit file",
|
|
807
|
+
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.`,
|
|
808
|
+
inputSchema: {
|
|
809
|
+
workspaceId: z
|
|
810
|
+
.string()
|
|
811
|
+
.describe("Workspace identifier returned by open_workspace."),
|
|
812
|
+
path: z
|
|
813
|
+
.string()
|
|
814
|
+
.describe("File path to edit, relative to the workspace root."),
|
|
815
|
+
edits: z
|
|
816
|
+
.array(z.object({
|
|
817
|
+
oldText: z
|
|
818
|
+
.string()
|
|
819
|
+
.describe("Exact text to replace. Must match uniquely in the original file."),
|
|
820
|
+
newText: z.string().describe("Replacement text."),
|
|
821
|
+
}))
|
|
822
|
+
.min(1),
|
|
823
|
+
},
|
|
824
|
+
outputSchema: resultOutputSchema({
|
|
825
|
+
status: z.literal("applied"),
|
|
826
|
+
}),
|
|
827
|
+
...toolWidgetDescriptorMeta(config, "edit"),
|
|
828
|
+
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
829
|
+
}, async ({ workspaceId, ...input }) => {
|
|
830
|
+
const startedAt = performance.now();
|
|
831
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
832
|
+
workspaces.resolvePath(workspace, input.path);
|
|
833
|
+
const response = await editFileTool(input, {
|
|
834
|
+
cwd: workspace.root,
|
|
835
|
+
root: workspace.root,
|
|
836
|
+
});
|
|
837
|
+
if (response.isError) {
|
|
838
|
+
logFailedToolResponse(config, {
|
|
839
|
+
tool: toolNames.edit,
|
|
840
|
+
workspaceId,
|
|
841
|
+
path: input.path,
|
|
842
|
+
}, response.content, startedAt);
|
|
843
|
+
return response;
|
|
844
|
+
}
|
|
845
|
+
const stats = countDiffStats(response.details?.patch ?? response.details?.diff);
|
|
846
|
+
const summary = {
|
|
847
|
+
...stats,
|
|
848
|
+
editCount: input.edits.length,
|
|
849
|
+
};
|
|
850
|
+
const editResultText = `Edited ${input.path} (+${stats.additions} -${stats.removals}).`;
|
|
851
|
+
const editContent = [textBlock(editResultText)];
|
|
852
|
+
logToolCall(config, {
|
|
631
853
|
tool: toolNames.edit,
|
|
632
854
|
workspaceId,
|
|
633
855
|
path: input.path,
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
650
|
-
});
|
|
651
|
-
return {
|
|
652
|
-
content: editContent,
|
|
653
|
-
_meta: {
|
|
654
|
-
tool: toolNames.edit,
|
|
655
|
-
card: {
|
|
656
|
-
workspaceId,
|
|
657
|
-
path: input.path,
|
|
658
|
-
summary,
|
|
659
|
-
payload: {
|
|
660
|
-
diff: response.details?.diff,
|
|
661
|
-
patch: response.details?.patch,
|
|
856
|
+
success: true,
|
|
857
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
858
|
+
});
|
|
859
|
+
return {
|
|
860
|
+
content: editContent,
|
|
861
|
+
_meta: {
|
|
862
|
+
tool: toolNames.edit,
|
|
863
|
+
card: {
|
|
864
|
+
workspaceId,
|
|
865
|
+
path: input.path,
|
|
866
|
+
summary,
|
|
867
|
+
payload: {
|
|
868
|
+
diff: response.details?.diff,
|
|
869
|
+
patch: response.details?.patch,
|
|
870
|
+
},
|
|
662
871
|
},
|
|
663
872
|
},
|
|
873
|
+
structuredContent: {
|
|
874
|
+
status: "applied",
|
|
875
|
+
result: contentText(editContent),
|
|
876
|
+
},
|
|
877
|
+
};
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
if (config.toolMode === "codex") {
|
|
881
|
+
registerAppTool(server, "apply_patch", {
|
|
882
|
+
title: "Apply patch",
|
|
883
|
+
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.",
|
|
884
|
+
inputSchema: {
|
|
885
|
+
workspaceId: z
|
|
886
|
+
.string()
|
|
887
|
+
.describe("Workspace identifier returned by open_workspace."),
|
|
888
|
+
patch: z
|
|
889
|
+
.string()
|
|
890
|
+
.describe("Patch text enclosed by *** Begin Patch and *** End Patch markers."),
|
|
664
891
|
},
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
892
|
+
outputSchema: resultOutputSchema({
|
|
893
|
+
additions: z.number(),
|
|
894
|
+
removals: z.number(),
|
|
895
|
+
files: z.array(z.object({
|
|
896
|
+
path: z.string(),
|
|
897
|
+
previousPath: z.string().optional(),
|
|
898
|
+
operation: z.enum(["add", "update", "delete", "move"]),
|
|
899
|
+
})),
|
|
900
|
+
}),
|
|
901
|
+
...toolWidgetDescriptorMeta(config, "edit"),
|
|
902
|
+
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
903
|
+
}, async ({ workspaceId, patch }) => {
|
|
904
|
+
const startedAt = performance.now();
|
|
905
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
906
|
+
const applied = await applyPatch(workspace.root, patch);
|
|
907
|
+
const paths = applied.files.map((file) => file.path).join(", ");
|
|
908
|
+
const result = `Applied patch to ${applied.files.length} file(s): ${paths}`;
|
|
909
|
+
const content = [textBlock(result)];
|
|
910
|
+
const displayPath = applied.files.length === 1
|
|
911
|
+
? applied.files[0]?.path
|
|
912
|
+
: `${applied.files.length} files`;
|
|
913
|
+
logToolCall(config, {
|
|
914
|
+
tool: "apply_patch",
|
|
915
|
+
workspaceId,
|
|
916
|
+
success: true,
|
|
917
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
918
|
+
});
|
|
919
|
+
return {
|
|
920
|
+
content,
|
|
921
|
+
_meta: {
|
|
922
|
+
tool: "apply_patch",
|
|
923
|
+
card: {
|
|
924
|
+
workspaceId,
|
|
925
|
+
path: displayPath,
|
|
926
|
+
summary: {
|
|
927
|
+
files: applied.files.length,
|
|
928
|
+
additions: applied.additions,
|
|
929
|
+
removals: applied.removals,
|
|
930
|
+
},
|
|
931
|
+
payload: { patch: applied.patch },
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
structuredContent: {
|
|
935
|
+
result,
|
|
936
|
+
additions: applied.additions,
|
|
937
|
+
removals: applied.removals,
|
|
938
|
+
files: applied.files,
|
|
939
|
+
},
|
|
940
|
+
};
|
|
941
|
+
});
|
|
942
|
+
}
|
|
671
943
|
if (config.widgets === "changes") {
|
|
672
944
|
registerAppTool(server, "show_changes", {
|
|
673
945
|
title: "Show changes",
|
|
674
|
-
description: "Show aggregate file changes
|
|
946
|
+
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.",
|
|
675
947
|
inputSchema: {
|
|
676
948
|
workspaceId: z
|
|
677
949
|
.string()
|
|
678
950
|
.describe("Workspace identifier returned by open_workspace."),
|
|
679
|
-
since: z
|
|
680
|
-
.enum(["last_shown", "workspace_open"])
|
|
681
|
-
.optional()
|
|
682
|
-
.describe("Defaults to last_shown. Use workspace_open to compare against the initial open_workspace checkpoint."),
|
|
683
|
-
markReviewed: z
|
|
684
|
-
.boolean()
|
|
685
|
-
.optional()
|
|
686
|
-
.describe("Defaults to true. When true, advances the last shown checkpoint to the current workspace state."),
|
|
687
951
|
},
|
|
688
952
|
outputSchema: resultOutputSchema(),
|
|
689
953
|
...toolWidgetDescriptorMeta(config, "show_changes"),
|
|
690
954
|
annotations: { readOnlyHint: true },
|
|
691
|
-
}, async ({ workspaceId
|
|
955
|
+
}, async ({ workspaceId }) => {
|
|
692
956
|
const startedAt = performance.now();
|
|
693
957
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
694
958
|
const review = await reviewCheckpoints.reviewChanges({
|
|
695
959
|
workspaceId,
|
|
696
960
|
root: workspace.root,
|
|
697
|
-
since:
|
|
698
|
-
markReviewed:
|
|
961
|
+
since: "last_shown",
|
|
962
|
+
markReviewed: true,
|
|
699
963
|
});
|
|
700
964
|
const content = [textBlock(review.result)];
|
|
701
965
|
logToolCall(config, {
|
|
@@ -723,9 +987,9 @@ function createMcpServer(config, workspaces, reviewCheckpoints) {
|
|
|
723
987
|
};
|
|
724
988
|
});
|
|
725
989
|
}
|
|
726
|
-
if (
|
|
990
|
+
if (config.toolMode === "full") {
|
|
727
991
|
registerAppTool(server, toolNames.grep, {
|
|
728
|
-
title:
|
|
992
|
+
title: "Grep",
|
|
729
993
|
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.",
|
|
730
994
|
inputSchema: {
|
|
731
995
|
workspaceId: z
|
|
@@ -787,7 +1051,7 @@ function createMcpServer(config, workspaces, reviewCheckpoints) {
|
|
|
787
1051
|
};
|
|
788
1052
|
});
|
|
789
1053
|
registerAppTool(server, toolNames.glob, {
|
|
790
|
-
title:
|
|
1054
|
+
title: "Glob",
|
|
791
1055
|
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.",
|
|
792
1056
|
inputSchema: {
|
|
793
1057
|
workspaceId: z
|
|
@@ -848,7 +1112,7 @@ function createMcpServer(config, workspaces, reviewCheckpoints) {
|
|
|
848
1112
|
};
|
|
849
1113
|
});
|
|
850
1114
|
registerAppTool(server, toolNames.ls, {
|
|
851
|
-
title:
|
|
1115
|
+
title: "Ls",
|
|
852
1116
|
description: "List a directory inside an open workspace. Use this for directory inspection before reading files. Call open_workspace first and pass workspaceId.",
|
|
853
1117
|
inputSchema: {
|
|
854
1118
|
workspaceId: z
|
|
@@ -902,80 +1166,85 @@ function createMcpServer(config, workspaces, reviewCheckpoints) {
|
|
|
902
1166
|
};
|
|
903
1167
|
});
|
|
904
1168
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1169
|
+
if (config.toolMode !== "codex") {
|
|
1170
|
+
registerAppTool(server, toolNames.shell, {
|
|
1171
|
+
title: "Bash",
|
|
1172
|
+
description: config.toolMode !== "full"
|
|
1173
|
+
? `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.`
|
|
1174
|
+
: `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.`,
|
|
1175
|
+
inputSchema: {
|
|
1176
|
+
workspaceId: z
|
|
1177
|
+
.string()
|
|
1178
|
+
.describe("Workspace identifier returned by open_workspace."),
|
|
1179
|
+
command: z
|
|
1180
|
+
.string()
|
|
1181
|
+
.describe(`Shell command to run. Must not create or modify project files; use ${toolNames.edit} or ${toolNames.write} for file changes.`),
|
|
1182
|
+
workingDirectory: z
|
|
1183
|
+
.string()
|
|
1184
|
+
.optional()
|
|
1185
|
+
.describe("Optional working directory relative to the workspace root. Defaults to the workspace root."),
|
|
1186
|
+
timeout: z
|
|
1187
|
+
.number()
|
|
1188
|
+
.positive()
|
|
1189
|
+
.max(300)
|
|
1190
|
+
.optional()
|
|
1191
|
+
.describe("Timeout in seconds. Defaults to 30, max 300."),
|
|
1192
|
+
},
|
|
1193
|
+
outputSchema: resultOutputSchema(),
|
|
1194
|
+
...toolWidgetDescriptorMeta(config, "shell"),
|
|
1195
|
+
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
1196
|
+
}, async ({ workspaceId, workingDirectory, ...input }) => {
|
|
1197
|
+
const startedAt = performance.now();
|
|
1198
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1199
|
+
const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
|
|
1200
|
+
const response = await runShellTool(input, {
|
|
1201
|
+
cwd,
|
|
1202
|
+
root: workspace.root,
|
|
1203
|
+
});
|
|
1204
|
+
if (response.isError) {
|
|
1205
|
+
logFailedToolResponse(config, {
|
|
1206
|
+
tool: toolNames.shell,
|
|
1207
|
+
workspaceId,
|
|
1208
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1209
|
+
command: input.command,
|
|
1210
|
+
commandLength: input.command.length,
|
|
1211
|
+
}, response.content, startedAt);
|
|
1212
|
+
return response;
|
|
1213
|
+
}
|
|
1214
|
+
const summary = {
|
|
1215
|
+
command: input.command,
|
|
1216
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1217
|
+
...textSummary(response.content),
|
|
1218
|
+
};
|
|
1219
|
+
logToolCall(config, {
|
|
941
1220
|
tool: toolNames.shell,
|
|
942
1221
|
workspaceId,
|
|
943
1222
|
workingDirectory: workingDirectory ?? ".",
|
|
944
1223
|
command: input.command,
|
|
945
1224
|
commandLength: input.command.length,
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
commandLength: input.command.length,
|
|
960
|
-
success: true,
|
|
961
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
962
|
-
});
|
|
963
|
-
return {
|
|
964
|
-
...response,
|
|
965
|
-
_meta: {
|
|
966
|
-
tool: toolNames.shell,
|
|
967
|
-
card: {
|
|
968
|
-
workspaceId,
|
|
969
|
-
path: workingDirectory,
|
|
970
|
-
summary,
|
|
971
|
-
payload: { content: response.content },
|
|
1225
|
+
success: true,
|
|
1226
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1227
|
+
});
|
|
1228
|
+
return {
|
|
1229
|
+
...response,
|
|
1230
|
+
_meta: {
|
|
1231
|
+
tool: toolNames.shell,
|
|
1232
|
+
card: {
|
|
1233
|
+
workspaceId,
|
|
1234
|
+
path: workingDirectory,
|
|
1235
|
+
summary,
|
|
1236
|
+
payload: { content: response.content },
|
|
1237
|
+
},
|
|
972
1238
|
},
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
}
|
|
977
|
-
};
|
|
978
|
-
}
|
|
1239
|
+
structuredContent: {
|
|
1240
|
+
result: contentText(response.content),
|
|
1241
|
+
},
|
|
1242
|
+
};
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
if (config.toolMode === "codex") {
|
|
1246
|
+
registerCodexProcessTools(server, config, workspaces, processSessions);
|
|
1247
|
+
}
|
|
979
1248
|
return server;
|
|
980
1249
|
}
|
|
981
1250
|
export function createServer(config = loadConfig()) {
|
|
@@ -998,6 +1267,10 @@ export function createServer(config = loadConfig()) {
|
|
|
998
1267
|
const workspaceStore = createWorkspaceStore(config.stateDir);
|
|
999
1268
|
const workspaces = new WorkspaceRegistry(config, workspaceStore);
|
|
1000
1269
|
const reviewCheckpoints = createReviewCheckpointManager();
|
|
1270
|
+
const processSessions = new ProcessSessionManager();
|
|
1271
|
+
const localAgentProviders = config.subagents
|
|
1272
|
+
? getLocalAgentProviderAvailabilitySnapshot()
|
|
1273
|
+
: [];
|
|
1001
1274
|
if (config.logging.trustProxy) {
|
|
1002
1275
|
app.set("trust proxy", true);
|
|
1003
1276
|
}
|
|
@@ -1106,7 +1379,7 @@ export function createServer(config = loadConfig()) {
|
|
|
1106
1379
|
});
|
|
1107
1380
|
}
|
|
1108
1381
|
};
|
|
1109
|
-
const server = createMcpServer(config, workspaces, reviewCheckpoints);
|
|
1382
|
+
const server = createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders);
|
|
1110
1383
|
await server.connect(transport);
|
|
1111
1384
|
}
|
|
1112
1385
|
else {
|
|
@@ -1129,10 +1402,12 @@ export function createServer(config = loadConfig()) {
|
|
|
1129
1402
|
return {
|
|
1130
1403
|
app,
|
|
1131
1404
|
config,
|
|
1405
|
+
localAgentProviders,
|
|
1132
1406
|
close: () => {
|
|
1133
1407
|
if (closed)
|
|
1134
1408
|
return;
|
|
1135
1409
|
closed = true;
|
|
1410
|
+
processSessions.shutdown();
|
|
1136
1411
|
oauthProvider.close();
|
|
1137
1412
|
workspaceStore.close?.();
|
|
1138
1413
|
},
|
|
@@ -1146,7 +1421,7 @@ async function isMainModule() {
|
|
|
1146
1421
|
return modulePath === entrypointPath;
|
|
1147
1422
|
}
|
|
1148
1423
|
if (await isMainModule()) {
|
|
1149
|
-
const { app, config, close } = createServer();
|
|
1424
|
+
const { app, config, close, localAgentProviders } = createServer();
|
|
1150
1425
|
const httpServer = app.listen(config.port, config.host, () => {
|
|
1151
1426
|
console.log(`devspace listening on http://${config.host}:${config.port}/mcp`);
|
|
1152
1427
|
console.log(`allowed roots: ${config.allowedRoots.join(", ")}`);
|
|
@@ -1155,6 +1430,9 @@ if (await isMainModule()) {
|
|
|
1155
1430
|
console.log(`request logging: ${config.logging.requests ? "enabled" : "disabled"}`);
|
|
1156
1431
|
console.log(`asset logging: ${config.logging.assets ? "enabled" : "disabled"}`);
|
|
1157
1432
|
console.log(`trust proxy: ${config.logging.trustProxy ? "enabled" : "disabled"}`);
|
|
1433
|
+
if (config.subagents) {
|
|
1434
|
+
console.log(`subagent providers: ${formatLocalAgentProviderAvailabilitySummary(localAgentProviders)}`);
|
|
1435
|
+
}
|
|
1158
1436
|
});
|
|
1159
1437
|
const shutdown = () => {
|
|
1160
1438
|
httpServer.close(() => {
|