@waishnav/devspace 1.0.2 → 1.0.3
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 +399 -0
- package/dist/cli.js +13 -0
- package/dist/config.js +11 -13
- package/dist/process-platform.js +49 -0
- package/dist/process-sessions.js +329 -0
- package/dist/roots.js +5 -2
- package/dist/server.js +439 -199
- package/dist/skills.js +23 -3
- package/dist/ui/.vite/manifest.json +280 -280
- package/dist/ui/assets/{angular-html-Dj4LysSE.js → angular-html-D6nc7PYw.js} +1 -1
- package/dist/ui/assets/{angular-ts-CxuC_FFh.js → angular-ts-BAIStLeI.js} +1 -1
- package/dist/ui/assets/{apl-RwuvNFFo.js → apl-CtEv5pop.js} +1 -1
- package/dist/ui/assets/{astro-ncoaUbsO.js → astro-CVFIMXc6.js} +1 -1
- package/dist/ui/assets/{blade-Dw8-Lzux.js → blade-BgSlZ6jY.js} +1 -1
- package/dist/ui/assets/{c-Dpbbo_f2.js → c-bxLlCV26.js} +1 -1
- package/dist/ui/assets/{cobol-4ddX3THy.js → cobol-BuZ-r51r.js} +1 -1
- package/dist/ui/assets/{coffee-Bn-a5KqK.js → coffee-_JP0Joa7.js} +1 -1
- package/dist/ui/assets/{cpp-DoRrlM_T.js → cpp-CP_eaz-H.js} +1 -1
- package/dist/ui/assets/{crystal-DiZfCxn-.js → crystal-DZ-sEZd0.js} +1 -1
- package/dist/ui/assets/{css-DiQoSjDH.js → css-CZnyti9s.js} +1 -1
- package/dist/ui/assets/{edge-wEhCUoEt.js → edge-DjsoDMTR.js} +1 -1
- package/dist/ui/assets/{elixir-C3obhm0H.js → elixir-C5b04AkY.js} +1 -1
- package/dist/ui/assets/{elm-DyTBfh1L.js → elm-Ddx-rgVY.js} +1 -1
- package/dist/ui/assets/{erb-f4TnXRuY.js → erb-cjF7oPcT.js} +1 -1
- package/dist/ui/assets/{git-rebase-vhZbWNfp.js → git-rebase-DN5exBTl.js} +1 -1
- package/dist/ui/assets/{glimmer-js-BG7puL6Y.js → glimmer-js-CEq0hNwa.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-CNCfLLL1.js → glimmer-ts-4uYGFkon.js} +1 -1
- package/dist/ui/assets/{glsl-DhT76AL5.js → glsl-DEJ9tTWm.js} +1 -1
- package/dist/ui/assets/{graphql-CJX08w8y.js → graphql-Bim5WuQh.js} +1 -1
- package/dist/ui/assets/{hack-DY4NGCCL.js → hack-BcyVIXhE.js} +1 -1
- package/dist/ui/assets/{haml-DwSkmVcG.js → haml-D9YaukDg.js} +1 -1
- package/dist/ui/assets/{handlebars-CQ_1dEmi.js → handlebars-CFaMw9zH.js} +1 -1
- package/dist/ui/assets/{heavy-payload-2rGBiitA.js → heavy-payload-B9aTqgfs.js} +1 -1
- package/dist/ui/assets/{html-B_e3Njif.js → html-CgO_zx4D.js} +1 -1
- package/dist/ui/assets/{html-derivative-_nHZ_Bk-.js → html-derivative-BxqkysJT.js} +1 -1
- package/dist/ui/assets/{http-CfcvPIru.js → http-CO5dv0RA.js} +1 -1
- package/dist/ui/assets/{hurl-9vWDQIMO.js → hurl-DRHtrK78.js} +1 -1
- package/dist/ui/assets/{java-C4S5r6L5.js → java-BvHC_DgR.js} +1 -1
- package/dist/ui/assets/{javascript-BRwqbMMC.js → javascript-C-3payQx.js} +1 -1
- package/dist/ui/assets/{jinja-DHxzMMc5.js → jinja-CAOPvEOH.js} +1 -1
- package/dist/ui/assets/{jison-B1DqeWXb.js → jison-totCB1L-.js} +1 -1
- package/dist/ui/assets/{json-DcFTkEdd.js → json-D0J9rEe1.js} +1 -1
- package/dist/ui/assets/{jsx-DgfmYa9f.js → jsx-na-XQD_B.js} +1 -1
- package/dist/ui/assets/{julia-3M8SJ8iF.js → julia-CqrTtZ4-.js} +1 -1
- package/dist/ui/assets/{just-UyH7_DNz.js → just-pmrVSkMc.js} +1 -1
- package/dist/ui/assets/{latex-wUbha6EX.js → latex-Cd_geX3E.js} +1 -1
- package/dist/ui/assets/{liquid-DqNTB9kN.js → liquid-C6ky5sJ-.js} +1 -1
- package/dist/ui/assets/{lua-DrEQ1QTW.js → lua-dgyBbeof.js} +1 -1
- package/dist/ui/assets/{marko-B3GJw9Md.js → marko-Dy5mfXY9.js} +1 -1
- package/dist/ui/assets/{mdc-BnThk82m.js → mdc-7NUvxgI1.js} +1 -1
- package/dist/ui/assets/{nginx-B5IeSsOX.js → nginx-C-KiEXbI.js} +1 -1
- package/dist/ui/assets/{nim-BJraatnS.js → nim-BBw8kfl9.js} +1 -1
- package/dist/ui/assets/{perl-BAhrR4gt.js → perl-CyxhJgbO.js} +1 -1
- package/dist/ui/assets/{php-BKRCk_wX.js → php-m4LAOpam.js} +1 -1
- package/dist/ui/assets/{pug-B8gmSV7z.js → pug-Cpl0baYI.js} +1 -1
- package/dist/ui/assets/{qml-DKrxIjHR.js → qml-BoSGNgIb.js} +1 -1
- package/dist/ui/assets/{r-YASBZvTH.js → r-C0JhqQd5.js} +1 -1
- package/dist/ui/assets/{razor-P-XFZqh6.js → razor-CntsBNoh.js} +1 -1
- package/dist/ui/assets/{regexp-CCpiH6nO.js → regexp-BNZmOCRl.js} +1 -1
- package/dist/ui/assets/{review-payload-BLYwg6t8.js → review-payload-BgdWHWiv.js} +1 -1
- package/dist/ui/assets/{rst-CFAzckbn.js → rst-BZ2XHIxM.js} +1 -1
- package/dist/ui/assets/{ruby-Dyp_Rx-S.js → ruby-BIthfCTP.js} +1 -1
- package/dist/ui/assets/{sas-YkWZIXxT.js → sas-5aT3fOla.js} +1 -1
- package/dist/ui/assets/{scss-Cf89idGl.js → scss-DZPfdkDy.js} +1 -1
- package/dist/ui/assets/{shellscript-BFMb52q8.js → shellscript-DbhKc5Xj.js} +1 -1
- package/dist/ui/assets/{shellsession-DuthW-5v.js → shellsession-X9-bcyHh.js} +1 -1
- package/dist/ui/assets/{soy-BohVVlJU.js → soy-Bd4tecrW.js} +1 -1
- package/dist/ui/assets/{sql-CnK9R2Gt.js → sql-Chf5JrDn.js} +1 -1
- package/dist/ui/assets/{stata-D-sV4Vbr.js → stata-DvMMIc_T.js} +1 -1
- package/dist/ui/assets/{surrealql-ChAy0gHv.js → surrealql-D5FMzVF5.js} +1 -1
- package/dist/ui/assets/{svelte-3HBVqG-H.js → svelte-CTiVXZrv.js} +1 -1
- package/dist/ui/assets/{templ-BixlV4Cp.js → templ-ldnfq-7w.js} +1 -1
- package/dist/ui/assets/{tex-DWoJKHvD.js → tex-QW0_7T2r.js} +1 -1
- package/dist/ui/assets/{ts-tags-DLA8eH13.js → ts-tags-CAKiWIBx.js} +1 -1
- package/dist/ui/assets/{tsx-BYWa5JUz.js → tsx-Bu9xJJmq.js} +1 -1
- package/dist/ui/assets/{twig-CnY65vAo.js → twig-Dp1752_V.js} +1 -1
- package/dist/ui/assets/{typescript-ByInUsro.js → typescript-RfO03Kmm.js} +1 -1
- package/dist/ui/assets/{useFileDiffInstance-Dg0pWcJV.js → useFileDiffInstance-DjqU4y1a.js} +3 -3
- package/dist/ui/assets/{vue-Qngt2rkz.js → vue-Dlasv57G.js} +1 -1
- package/dist/ui/assets/{vue-html-DDue9upr.js → vue-html-BHuJ7anu.js} +1 -1
- package/dist/ui/assets/{vue-vine-DBcAKsA6.js → vue-vine-CavE0PSk.js} +1 -1
- package/dist/ui/assets/{workspace-app-DuzpU4Hf.js → workspace-app-DDV_qdjx.js} +4 -4
- package/dist/ui/assets/{xml-CRLR5jiL.js → xml-Bbv64l4W.js} +1 -1
- package/dist/ui/assets/{xsl-CmcwyXbm.js → xsl-Sro4gyQY.js} +1 -1
- package/dist/ui/assets/{yaml-Dpe6Eg-k.js → yaml-Du68c8sA.js} +1 -1
- package/dist/ui/workspace-app.html +1 -1
- package/docs/chatgpt-coding-workflow.md +24 -4
- package/docs/codex-tool-mode-qa.md +73 -0
- package/docs/configuration.md +28 -5
- package/docs/gotchas.md +11 -5
- package/docs/setup.md +1 -1
- package/package.json +10 -5
- package/scripts/fix-node-pty-permissions.mjs +22 -0
package/dist/server.js
CHANGED
|
@@ -12,10 +12,12 @@ 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";
|
|
@@ -63,7 +65,7 @@ function toolWidgetDescriptorMeta(config, kind) {
|
|
|
63
65
|
};
|
|
64
66
|
}
|
|
65
67
|
function toolNamesFor(config) {
|
|
66
|
-
return config.toolNaming === "short"
|
|
68
|
+
return config.toolNaming === "short" || config.toolMode === "codex"
|
|
67
69
|
? {
|
|
68
70
|
openWorkspace: "open_workspace",
|
|
69
71
|
read: "read",
|
|
@@ -86,7 +88,10 @@ function toolNamesFor(config) {
|
|
|
86
88
|
};
|
|
87
89
|
}
|
|
88
90
|
function serverInstructions(config, toolNames) {
|
|
89
|
-
|
|
91
|
+
if (config.toolMode === "codex") {
|
|
92
|
+
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.`;
|
|
93
|
+
}
|
|
94
|
+
const inspection = config.toolMode !== "full"
|
|
90
95
|
? `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
96
|
: `Prefer ${toolNames.read}, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} for file inspection. `;
|
|
92
97
|
const skills = config.skillsEnabled
|
|
@@ -292,7 +297,170 @@ async function assertWorkspaceAppAssets() {
|
|
|
292
297
|
await access(candidate);
|
|
293
298
|
}
|
|
294
299
|
}
|
|
295
|
-
function
|
|
300
|
+
function processResult(snapshot) {
|
|
301
|
+
const status = snapshot.running
|
|
302
|
+
? `Process running with session ID ${snapshot.sessionId}.`
|
|
303
|
+
: snapshot.signal
|
|
304
|
+
? `Process exited after signal ${snapshot.signal}.`
|
|
305
|
+
: `Process exited with code ${snapshot.exitCode ?? "unknown"}.`;
|
|
306
|
+
return snapshot.output ? `${snapshot.output.replace(/\n$/, "")}\n${status}` : status;
|
|
307
|
+
}
|
|
308
|
+
function processOutputSchema() {
|
|
309
|
+
return resultOutputSchema({
|
|
310
|
+
sessionId: z.number().optional(),
|
|
311
|
+
running: z.boolean(),
|
|
312
|
+
exitCode: z.number().int().optional(),
|
|
313
|
+
signal: z.string().optional(),
|
|
314
|
+
wallTimeMs: z.number().nonnegative(),
|
|
315
|
+
outputTruncated: z.boolean(),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function processToolResponse(tool, workspaceId, snapshot, summary) {
|
|
319
|
+
const result = processResult(snapshot);
|
|
320
|
+
const content = [textBlock(result)];
|
|
321
|
+
const outputSummary = textSummary(snapshot.output ? [textBlock(snapshot.output)] : []);
|
|
322
|
+
return {
|
|
323
|
+
content,
|
|
324
|
+
_meta: {
|
|
325
|
+
tool,
|
|
326
|
+
card: {
|
|
327
|
+
workspaceId,
|
|
328
|
+
summary: { ...summary, ...outputSummary },
|
|
329
|
+
payload: { content },
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
structuredContent: {
|
|
333
|
+
result,
|
|
334
|
+
sessionId: snapshot.sessionId,
|
|
335
|
+
running: snapshot.running,
|
|
336
|
+
exitCode: snapshot.exitCode,
|
|
337
|
+
signal: snapshot.signal,
|
|
338
|
+
wallTimeMs: snapshot.wallTimeMs,
|
|
339
|
+
outputTruncated: snapshot.outputTruncated,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function registerCodexProcessTools(server, config, workspaces, processSessions) {
|
|
344
|
+
registerAppTool(server, "exec_command", {
|
|
345
|
+
title: "Execute command",
|
|
346
|
+
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.",
|
|
347
|
+
inputSchema: {
|
|
348
|
+
workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
|
|
349
|
+
cmd: z.string().min(1).describe("Shell command to execute."),
|
|
350
|
+
tty: z
|
|
351
|
+
.boolean()
|
|
352
|
+
.optional()
|
|
353
|
+
.describe("Allocate a pseudo-terminal for interactive commands. Defaults to false."),
|
|
354
|
+
columns: z.number().int().min(1).max(1_000).optional().describe("Initial PTY width. Defaults to 80."),
|
|
355
|
+
rows: z.number().int().min(1).max(1_000).optional().describe("Initial PTY height. Defaults to 24."),
|
|
356
|
+
workingDirectory: z
|
|
357
|
+
.string()
|
|
358
|
+
.optional()
|
|
359
|
+
.describe("Working directory relative to the workspace root. Defaults to the workspace root."),
|
|
360
|
+
yieldTimeMs: z
|
|
361
|
+
.number()
|
|
362
|
+
.int()
|
|
363
|
+
.min(0)
|
|
364
|
+
.max(30_000)
|
|
365
|
+
.optional()
|
|
366
|
+
.describe("Milliseconds to wait before returning a running session. Defaults to 10000."),
|
|
367
|
+
maxOutputTokens: z
|
|
368
|
+
.number()
|
|
369
|
+
.int()
|
|
370
|
+
.positive()
|
|
371
|
+
.max(100_000)
|
|
372
|
+
.optional()
|
|
373
|
+
.describe("Approximate output token budget. Defaults to 10000."),
|
|
374
|
+
},
|
|
375
|
+
outputSchema: processOutputSchema(),
|
|
376
|
+
...toolWidgetDescriptorMeta(config, "shell"),
|
|
377
|
+
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
378
|
+
}, async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, maxOutputTokens }) => {
|
|
379
|
+
const startedAt = performance.now();
|
|
380
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
381
|
+
const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
|
|
382
|
+
const snapshot = await processSessions.start({
|
|
383
|
+
workspaceId,
|
|
384
|
+
command: cmd,
|
|
385
|
+
cwd,
|
|
386
|
+
tty,
|
|
387
|
+
columns,
|
|
388
|
+
rows,
|
|
389
|
+
yieldTimeMs,
|
|
390
|
+
maxOutputTokens,
|
|
391
|
+
});
|
|
392
|
+
logToolCall(config, {
|
|
393
|
+
tool: "exec_command",
|
|
394
|
+
workspaceId,
|
|
395
|
+
workingDirectory: workingDirectory ?? ".",
|
|
396
|
+
command: cmd,
|
|
397
|
+
commandLength: cmd.length,
|
|
398
|
+
success: true,
|
|
399
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
400
|
+
});
|
|
401
|
+
return processToolResponse("exec_command", workspaceId, snapshot, {
|
|
402
|
+
command: cmd,
|
|
403
|
+
workingDirectory: workingDirectory ?? ".",
|
|
404
|
+
running: snapshot.running,
|
|
405
|
+
exitCode: snapshot.exitCode,
|
|
406
|
+
wallTimeMs: snapshot.wallTimeMs,
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
registerAppTool(server, "write_stdin", {
|
|
410
|
+
title: "Write to process",
|
|
411
|
+
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.",
|
|
412
|
+
inputSchema: {
|
|
413
|
+
workspaceId: z.string().describe("Workspace identifier used to start the process."),
|
|
414
|
+
sessionId: z.number().describe("Process session identifier returned by exec_command."),
|
|
415
|
+
chars: z.string().optional().describe("Characters to write. Omit or pass an empty string to poll."),
|
|
416
|
+
columns: z.number().int().min(1).max(1_000).optional().describe("Resize a PTY to this width."),
|
|
417
|
+
rows: z.number().int().min(1).max(1_000).optional().describe("Resize a PTY to this height."),
|
|
418
|
+
yieldTimeMs: z
|
|
419
|
+
.number()
|
|
420
|
+
.int()
|
|
421
|
+
.min(0)
|
|
422
|
+
.max(30_000)
|
|
423
|
+
.optional()
|
|
424
|
+
.describe("Milliseconds to wait for process output or completion. Defaults to 10000."),
|
|
425
|
+
maxOutputTokens: z
|
|
426
|
+
.number()
|
|
427
|
+
.int()
|
|
428
|
+
.positive()
|
|
429
|
+
.max(100_000)
|
|
430
|
+
.optional()
|
|
431
|
+
.describe("Approximate output token budget. Defaults to 10000."),
|
|
432
|
+
},
|
|
433
|
+
outputSchema: processOutputSchema(),
|
|
434
|
+
...toolWidgetDescriptorMeta(config, "shell"),
|
|
435
|
+
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
436
|
+
}, async ({ workspaceId, sessionId, chars, columns, rows, yieldTimeMs, maxOutputTokens }) => {
|
|
437
|
+
const startedAt = performance.now();
|
|
438
|
+
workspaces.getWorkspace(workspaceId);
|
|
439
|
+
const snapshot = await processSessions.write({
|
|
440
|
+
workspaceId,
|
|
441
|
+
sessionId,
|
|
442
|
+
chars,
|
|
443
|
+
columns,
|
|
444
|
+
rows,
|
|
445
|
+
yieldTimeMs,
|
|
446
|
+
maxOutputTokens,
|
|
447
|
+
});
|
|
448
|
+
logToolCall(config, {
|
|
449
|
+
tool: "write_stdin",
|
|
450
|
+
workspaceId,
|
|
451
|
+
success: true,
|
|
452
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
453
|
+
});
|
|
454
|
+
return processToolResponse("write_stdin", workspaceId, snapshot, {
|
|
455
|
+
sessionId,
|
|
456
|
+
charactersWritten: chars?.length ?? 0,
|
|
457
|
+
running: snapshot.running,
|
|
458
|
+
exitCode: snapshot.exitCode,
|
|
459
|
+
wallTimeMs: snapshot.wallTimeMs,
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function createMcpServer(config, workspaces, reviewCheckpoints, processSessions) {
|
|
296
464
|
const toolNames = toolNamesFor(config);
|
|
297
465
|
const server = new McpServer({
|
|
298
466
|
name: "devspace",
|
|
@@ -530,144 +698,209 @@ function createMcpServer(config, workspaces, reviewCheckpoints) {
|
|
|
530
698
|
},
|
|
531
699
|
};
|
|
532
700
|
});
|
|
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
|
-
|
|
701
|
+
if (config.toolMode !== "codex") {
|
|
702
|
+
registerAppTool(server, toolNames.write, {
|
|
703
|
+
title: "Write file",
|
|
704
|
+
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.`,
|
|
705
|
+
inputSchema: {
|
|
706
|
+
workspaceId: z
|
|
707
|
+
.string()
|
|
708
|
+
.describe("Workspace identifier returned by open_workspace."),
|
|
709
|
+
path: z
|
|
710
|
+
.string()
|
|
711
|
+
.describe("File path to write, relative to the workspace root."),
|
|
712
|
+
content: z.string().describe("Complete new file content."),
|
|
713
|
+
},
|
|
714
|
+
outputSchema: resultOutputSchema(),
|
|
715
|
+
...toolWidgetDescriptorMeta(config, "write"),
|
|
716
|
+
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
717
|
+
}, async ({ workspaceId, ...input }) => {
|
|
718
|
+
const startedAt = performance.now();
|
|
719
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
720
|
+
workspaces.resolvePath(workspace, input.path);
|
|
721
|
+
const response = await writeFileTool(input, {
|
|
722
|
+
cwd: workspace.root,
|
|
723
|
+
root: workspace.root,
|
|
724
|
+
});
|
|
725
|
+
if (response.isError) {
|
|
726
|
+
logFailedToolResponse(config, {
|
|
727
|
+
tool: toolNames.write,
|
|
728
|
+
workspaceId,
|
|
729
|
+
path: input.path,
|
|
730
|
+
}, response.content, startedAt);
|
|
731
|
+
return response;
|
|
732
|
+
}
|
|
733
|
+
const patch = newFilePatch(input.path, input.content);
|
|
734
|
+
const stats = countDiffStats(patch);
|
|
735
|
+
const summary = {
|
|
736
|
+
...stats,
|
|
737
|
+
lines: contentLineCount(input.content),
|
|
738
|
+
characters: input.content.length,
|
|
739
|
+
};
|
|
740
|
+
logToolCall(config, {
|
|
558
741
|
tool: toolNames.write,
|
|
559
742
|
workspaceId,
|
|
560
743
|
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,
|
|
744
|
+
success: true,
|
|
745
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
746
|
+
});
|
|
747
|
+
return {
|
|
748
|
+
...response,
|
|
749
|
+
_meta: {
|
|
750
|
+
tool: toolNames.write,
|
|
751
|
+
card: {
|
|
752
|
+
workspaceId,
|
|
753
|
+
path: input.path,
|
|
754
|
+
summary,
|
|
755
|
+
payload: {
|
|
756
|
+
content: response.content,
|
|
757
|
+
patch,
|
|
758
|
+
},
|
|
589
759
|
},
|
|
590
760
|
},
|
|
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,
|
|
761
|
+
structuredContent: {
|
|
762
|
+
result: contentText(response.content),
|
|
763
|
+
},
|
|
764
|
+
};
|
|
628
765
|
});
|
|
629
|
-
|
|
630
|
-
|
|
766
|
+
registerAppTool(server, toolNames.edit, {
|
|
767
|
+
title: "Edit file",
|
|
768
|
+
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.`,
|
|
769
|
+
inputSchema: {
|
|
770
|
+
workspaceId: z
|
|
771
|
+
.string()
|
|
772
|
+
.describe("Workspace identifier returned by open_workspace."),
|
|
773
|
+
path: z
|
|
774
|
+
.string()
|
|
775
|
+
.describe("File path to edit, relative to the workspace root."),
|
|
776
|
+
edits: z
|
|
777
|
+
.array(z.object({
|
|
778
|
+
oldText: z
|
|
779
|
+
.string()
|
|
780
|
+
.describe("Exact text to replace. Must match uniquely in the original file."),
|
|
781
|
+
newText: z.string().describe("Replacement text."),
|
|
782
|
+
}))
|
|
783
|
+
.min(1),
|
|
784
|
+
},
|
|
785
|
+
outputSchema: resultOutputSchema({
|
|
786
|
+
status: z.literal("applied"),
|
|
787
|
+
}),
|
|
788
|
+
...toolWidgetDescriptorMeta(config, "edit"),
|
|
789
|
+
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
790
|
+
}, async ({ workspaceId, ...input }) => {
|
|
791
|
+
const startedAt = performance.now();
|
|
792
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
793
|
+
workspaces.resolvePath(workspace, input.path);
|
|
794
|
+
const response = await editFileTool(input, {
|
|
795
|
+
cwd: workspace.root,
|
|
796
|
+
root: workspace.root,
|
|
797
|
+
});
|
|
798
|
+
if (response.isError) {
|
|
799
|
+
logFailedToolResponse(config, {
|
|
800
|
+
tool: toolNames.edit,
|
|
801
|
+
workspaceId,
|
|
802
|
+
path: input.path,
|
|
803
|
+
}, response.content, startedAt);
|
|
804
|
+
return response;
|
|
805
|
+
}
|
|
806
|
+
const stats = countDiffStats(response.details?.patch ?? response.details?.diff);
|
|
807
|
+
const summary = {
|
|
808
|
+
...stats,
|
|
809
|
+
editCount: input.edits.length,
|
|
810
|
+
};
|
|
811
|
+
const editResultText = `Edited ${input.path} (+${stats.additions} -${stats.removals}).`;
|
|
812
|
+
const editContent = [textBlock(editResultText)];
|
|
813
|
+
logToolCall(config, {
|
|
631
814
|
tool: toolNames.edit,
|
|
632
815
|
workspaceId,
|
|
633
816
|
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,
|
|
817
|
+
success: true,
|
|
818
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
819
|
+
});
|
|
820
|
+
return {
|
|
821
|
+
content: editContent,
|
|
822
|
+
_meta: {
|
|
823
|
+
tool: toolNames.edit,
|
|
824
|
+
card: {
|
|
825
|
+
workspaceId,
|
|
826
|
+
path: input.path,
|
|
827
|
+
summary,
|
|
828
|
+
payload: {
|
|
829
|
+
diff: response.details?.diff,
|
|
830
|
+
patch: response.details?.patch,
|
|
831
|
+
},
|
|
662
832
|
},
|
|
663
833
|
},
|
|
834
|
+
structuredContent: {
|
|
835
|
+
status: "applied",
|
|
836
|
+
result: contentText(editContent),
|
|
837
|
+
},
|
|
838
|
+
};
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
if (config.toolMode === "codex") {
|
|
842
|
+
registerAppTool(server, "apply_patch", {
|
|
843
|
+
title: "Apply patch",
|
|
844
|
+
description: "Apply one Codex-style patch inside an open workspace. Supports adding, overwriting, updating, deleting, and moving files. Earlier successful file changes remain if a later patch action fails. Use this for all file modifications. Paths must be relative to the workspace. Call open_workspace first and pass workspaceId.",
|
|
845
|
+
inputSchema: {
|
|
846
|
+
workspaceId: z
|
|
847
|
+
.string()
|
|
848
|
+
.describe("Workspace identifier returned by open_workspace."),
|
|
849
|
+
patch: z
|
|
850
|
+
.string()
|
|
851
|
+
.describe("Patch text enclosed by *** Begin Patch and *** End Patch markers."),
|
|
664
852
|
},
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
853
|
+
outputSchema: resultOutputSchema({
|
|
854
|
+
additions: z.number(),
|
|
855
|
+
removals: z.number(),
|
|
856
|
+
files: z.array(z.object({
|
|
857
|
+
path: z.string(),
|
|
858
|
+
previousPath: z.string().optional(),
|
|
859
|
+
operation: z.enum(["add", "update", "delete", "move"]),
|
|
860
|
+
})),
|
|
861
|
+
}),
|
|
862
|
+
...toolWidgetDescriptorMeta(config, "edit"),
|
|
863
|
+
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
864
|
+
}, async ({ workspaceId, patch }) => {
|
|
865
|
+
const startedAt = performance.now();
|
|
866
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
867
|
+
const applied = await applyPatch(workspace.root, patch);
|
|
868
|
+
const paths = applied.files.map((file) => file.path).join(", ");
|
|
869
|
+
const result = `Applied patch to ${applied.files.length} file(s): ${paths}`;
|
|
870
|
+
const content = [textBlock(result)];
|
|
871
|
+
const displayPath = applied.files.length === 1
|
|
872
|
+
? applied.files[0]?.path
|
|
873
|
+
: `${applied.files.length} files`;
|
|
874
|
+
logToolCall(config, {
|
|
875
|
+
tool: "apply_patch",
|
|
876
|
+
workspaceId,
|
|
877
|
+
success: true,
|
|
878
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
879
|
+
});
|
|
880
|
+
return {
|
|
881
|
+
content,
|
|
882
|
+
_meta: {
|
|
883
|
+
tool: "apply_patch",
|
|
884
|
+
card: {
|
|
885
|
+
workspaceId,
|
|
886
|
+
path: displayPath,
|
|
887
|
+
summary: {
|
|
888
|
+
files: applied.files.length,
|
|
889
|
+
additions: applied.additions,
|
|
890
|
+
removals: applied.removals,
|
|
891
|
+
},
|
|
892
|
+
payload: { patch: applied.patch },
|
|
893
|
+
},
|
|
894
|
+
},
|
|
895
|
+
structuredContent: {
|
|
896
|
+
result,
|
|
897
|
+
additions: applied.additions,
|
|
898
|
+
removals: applied.removals,
|
|
899
|
+
files: applied.files,
|
|
900
|
+
},
|
|
901
|
+
};
|
|
902
|
+
});
|
|
903
|
+
}
|
|
671
904
|
if (config.widgets === "changes") {
|
|
672
905
|
registerAppTool(server, "show_changes", {
|
|
673
906
|
title: "Show changes",
|
|
@@ -723,7 +956,7 @@ function createMcpServer(config, workspaces, reviewCheckpoints) {
|
|
|
723
956
|
};
|
|
724
957
|
});
|
|
725
958
|
}
|
|
726
|
-
if (
|
|
959
|
+
if (config.toolMode === "full") {
|
|
727
960
|
registerAppTool(server, toolNames.grep, {
|
|
728
961
|
title: config.toolNaming === "short" ? "Grep" : "Grep files",
|
|
729
962
|
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.",
|
|
@@ -902,80 +1135,85 @@ function createMcpServer(config, workspaces, reviewCheckpoints) {
|
|
|
902
1135
|
};
|
|
903
1136
|
});
|
|
904
1137
|
}
|
|
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
|
-
|
|
1138
|
+
if (config.toolMode !== "codex") {
|
|
1139
|
+
registerAppTool(server, toolNames.shell, {
|
|
1140
|
+
title: config.toolNaming === "short" ? "Bash" : "Run shell",
|
|
1141
|
+
description: config.toolMode !== "full"
|
|
1142
|
+
? `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.`
|
|
1143
|
+
: `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.`,
|
|
1144
|
+
inputSchema: {
|
|
1145
|
+
workspaceId: z
|
|
1146
|
+
.string()
|
|
1147
|
+
.describe("Workspace identifier returned by open_workspace."),
|
|
1148
|
+
command: z
|
|
1149
|
+
.string()
|
|
1150
|
+
.describe(`Shell command to run. Must not create or modify project files; use ${toolNames.edit} or ${toolNames.write} for file changes.`),
|
|
1151
|
+
workingDirectory: z
|
|
1152
|
+
.string()
|
|
1153
|
+
.optional()
|
|
1154
|
+
.describe("Optional working directory relative to the workspace root. Defaults to the workspace root."),
|
|
1155
|
+
timeout: z
|
|
1156
|
+
.number()
|
|
1157
|
+
.positive()
|
|
1158
|
+
.max(300)
|
|
1159
|
+
.optional()
|
|
1160
|
+
.describe("Timeout in seconds. Defaults to 30, max 300."),
|
|
1161
|
+
},
|
|
1162
|
+
outputSchema: resultOutputSchema(),
|
|
1163
|
+
...toolWidgetDescriptorMeta(config, "shell"),
|
|
1164
|
+
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
1165
|
+
}, async ({ workspaceId, workingDirectory, ...input }) => {
|
|
1166
|
+
const startedAt = performance.now();
|
|
1167
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1168
|
+
const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
|
|
1169
|
+
const response = await runShellTool(input, {
|
|
1170
|
+
cwd,
|
|
1171
|
+
root: workspace.root,
|
|
1172
|
+
});
|
|
1173
|
+
if (response.isError) {
|
|
1174
|
+
logFailedToolResponse(config, {
|
|
1175
|
+
tool: toolNames.shell,
|
|
1176
|
+
workspaceId,
|
|
1177
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1178
|
+
command: input.command,
|
|
1179
|
+
commandLength: input.command.length,
|
|
1180
|
+
}, response.content, startedAt);
|
|
1181
|
+
return response;
|
|
1182
|
+
}
|
|
1183
|
+
const summary = {
|
|
1184
|
+
command: input.command,
|
|
1185
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1186
|
+
...textSummary(response.content),
|
|
1187
|
+
};
|
|
1188
|
+
logToolCall(config, {
|
|
941
1189
|
tool: toolNames.shell,
|
|
942
1190
|
workspaceId,
|
|
943
1191
|
workingDirectory: workingDirectory ?? ".",
|
|
944
1192
|
command: input.command,
|
|
945
1193
|
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 },
|
|
1194
|
+
success: true,
|
|
1195
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1196
|
+
});
|
|
1197
|
+
return {
|
|
1198
|
+
...response,
|
|
1199
|
+
_meta: {
|
|
1200
|
+
tool: toolNames.shell,
|
|
1201
|
+
card: {
|
|
1202
|
+
workspaceId,
|
|
1203
|
+
path: workingDirectory,
|
|
1204
|
+
summary,
|
|
1205
|
+
payload: { content: response.content },
|
|
1206
|
+
},
|
|
972
1207
|
},
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
}
|
|
977
|
-
};
|
|
978
|
-
}
|
|
1208
|
+
structuredContent: {
|
|
1209
|
+
result: contentText(response.content),
|
|
1210
|
+
},
|
|
1211
|
+
};
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
if (config.toolMode === "codex") {
|
|
1215
|
+
registerCodexProcessTools(server, config, workspaces, processSessions);
|
|
1216
|
+
}
|
|
979
1217
|
return server;
|
|
980
1218
|
}
|
|
981
1219
|
export function createServer(config = loadConfig()) {
|
|
@@ -998,6 +1236,7 @@ export function createServer(config = loadConfig()) {
|
|
|
998
1236
|
const workspaceStore = createWorkspaceStore(config.stateDir);
|
|
999
1237
|
const workspaces = new WorkspaceRegistry(config, workspaceStore);
|
|
1000
1238
|
const reviewCheckpoints = createReviewCheckpointManager();
|
|
1239
|
+
const processSessions = new ProcessSessionManager();
|
|
1001
1240
|
if (config.logging.trustProxy) {
|
|
1002
1241
|
app.set("trust proxy", true);
|
|
1003
1242
|
}
|
|
@@ -1106,7 +1345,7 @@ export function createServer(config = loadConfig()) {
|
|
|
1106
1345
|
});
|
|
1107
1346
|
}
|
|
1108
1347
|
};
|
|
1109
|
-
const server = createMcpServer(config, workspaces, reviewCheckpoints);
|
|
1348
|
+
const server = createMcpServer(config, workspaces, reviewCheckpoints, processSessions);
|
|
1110
1349
|
await server.connect(transport);
|
|
1111
1350
|
}
|
|
1112
1351
|
else {
|
|
@@ -1133,6 +1372,7 @@ export function createServer(config = loadConfig()) {
|
|
|
1133
1372
|
if (closed)
|
|
1134
1373
|
return;
|
|
1135
1374
|
closed = true;
|
|
1375
|
+
processSessions.shutdown();
|
|
1136
1376
|
oauthProvider.close();
|
|
1137
1377
|
workspaceStore.close?.();
|
|
1138
1378
|
},
|