@akira-tl/forgerelay 0.1.1 → 0.2.1
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/CHANGELOG.md +37 -0
- package/README.md +51 -6
- package/dist/apply-patch.js +21 -6
- package/dist/artifact-tools.js +35 -16
- package/dist/cli.js +46 -3
- package/dist/config.js +10 -6
- package/dist/db/migrations.js +8 -0
- package/dist/db/schema.js +1 -0
- package/dist/hook-cli.js +100 -0
- package/dist/hooks.js +545 -0
- package/dist/local-agent-store.js +14 -1
- package/dist/logger.js +157 -15
- package/dist/mcp/server-instructions.js +6 -5
- package/dist/pi-tools.js +14 -13
- package/dist/process-platform.js +1 -0
- package/dist/roots.js +30 -1
- package/dist/server.js +586 -446
- package/dist/user-config.js +33 -1
- package/dist/workspaces.js +64 -7
- package/docs/configuration.md +138 -5
- package/docs/debugging.md +127 -0
- package/docs/roadmap.md +16 -21
- package/docs/security.md +32 -7
- package/package.json +5 -3
- package/scripts/debug/accept.mjs +649 -0
- package/scripts/debug/config.json +40 -0
- package/scripts/debug/hook-recorder.mjs +35 -0
- package/scripts/debug/runtime.mjs +47 -0
- package/scripts/debug/serve.mjs +37 -0
- package/scripts/dev-server.mjs +1 -1
package/dist/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { access, realpath } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
7
|
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
@@ -15,9 +16,10 @@ import * as z from "zod/v4";
|
|
|
15
16
|
import { applyPatch } from "./apply-patch.js";
|
|
16
17
|
import { isArtifactDownloadSupportedPlatform, registerArtifactTools, } from "./artifact-tools.js";
|
|
17
18
|
import { loadConfig } from "./config.js";
|
|
19
|
+
import { attachHookReports, HookRunner, runToolWithHooks } from "./hooks.js";
|
|
18
20
|
import { buildServerInstructions, buildToolDescriptions, toolNames, } from "./mcp/server-instructions.js";
|
|
19
21
|
import { createOpenAIIncomingArtifactAdapter, } from "./incoming-artifacts.js";
|
|
20
|
-
import { logEvent, requestIp, requestPath, commandPreview, sessionIdPrefix, } from "./logger.js";
|
|
22
|
+
import { logEvent, requestIp, requestPath, commandPreview, sessionIdPrefix, workspaceLogLabel, } from "./logger.js";
|
|
21
23
|
import { editFileTool, findFilesTool, grepFilesTool, listDirectoryTool, readFileTool, runShellTool, writeFileTool, } from "./pi-tools.js";
|
|
22
24
|
import { SingleUserOAuthProvider } from "./oauth-provider.js";
|
|
23
25
|
import { McpSessionRegistry, } from "./mcp-sessions.js";
|
|
@@ -33,6 +35,7 @@ import { formatLocalAgentProviderAvailabilitySummary, getLocalAgentProviderAvail
|
|
|
33
35
|
// MCP clients can reconnect without closing the previous transport. Bound stale
|
|
34
36
|
// session retention so abandoned MCP servers do not accumulate for the life of the process.
|
|
35
37
|
const MCP_SESSION_IDLE_TIMEOUT_MS = 24 * 60 * 60 * 1_000;
|
|
38
|
+
const FORGERELAY_VERSION = readForgeRelayVersion();
|
|
36
39
|
const MCP_SESSION_CLEANUP_INTERVAL_MS = 5 * 60 * 1_000;
|
|
37
40
|
const WORKSPACE_APP_URI = "ui://forgerelay/workspace-app.html";
|
|
38
41
|
const WORKSPACE_APP_MANIFEST_ENTRY = "workspace-app.html";
|
|
@@ -76,6 +79,13 @@ function toolWidgetDescriptorMeta(config, kind) {
|
|
|
76
79
|
},
|
|
77
80
|
};
|
|
78
81
|
}
|
|
82
|
+
function workspaceLogContext(workspace, sessionId) {
|
|
83
|
+
return {
|
|
84
|
+
workspaceId: workspace.id,
|
|
85
|
+
workspace: workspaceLogLabel(workspace.root, workspace.id),
|
|
86
|
+
session: sessionIdPrefix(sessionId),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
79
89
|
function formatVisibleAgent(agent) {
|
|
80
90
|
const model = agent.model ? `, model ${agent.model}` : "";
|
|
81
91
|
const thinking = agent.thinking ? `, thinking ${agent.thinking}` : "";
|
|
@@ -313,6 +323,13 @@ function processOutputSchema() {
|
|
|
313
323
|
outputTruncated: z.boolean(),
|
|
314
324
|
});
|
|
315
325
|
}
|
|
326
|
+
function readForgeRelayVersion() {
|
|
327
|
+
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
328
|
+
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
329
|
+
throw new Error("Unable to read ForgeRelay package version.");
|
|
330
|
+
}
|
|
331
|
+
return packageJson.version;
|
|
332
|
+
}
|
|
316
333
|
function processToolResponse(tool, workspaceId, snapshot, summary) {
|
|
317
334
|
const result = processResult(snapshot);
|
|
318
335
|
const content = [textBlock(result)];
|
|
@@ -338,7 +355,18 @@ function processToolResponse(tool, workspaceId, snapshot, summary) {
|
|
|
338
355
|
},
|
|
339
356
|
};
|
|
340
357
|
}
|
|
341
|
-
function
|
|
358
|
+
function workspaceHookInvocation(workspace) {
|
|
359
|
+
return {
|
|
360
|
+
workspaceId: workspace.id,
|
|
361
|
+
workspaceRoot: workspace.root,
|
|
362
|
+
workspaceMode: workspace.mode,
|
|
363
|
+
sourceRoot: workspace.sourceRoot,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function toolResultIsError(result) {
|
|
367
|
+
return typeof result === "object" && result !== null && result.isError === true;
|
|
368
|
+
}
|
|
369
|
+
function registerCodexProcessTools(server, config, workspaces, processSessions, hooks) {
|
|
342
370
|
registerAppTool(server, "exec_command", {
|
|
343
371
|
title: "Execute command",
|
|
344
372
|
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.",
|
|
@@ -373,36 +401,46 @@ function registerCodexProcessTools(server, config, workspaces, processSessions)
|
|
|
373
401
|
outputSchema: processOutputSchema(),
|
|
374
402
|
...toolWidgetDescriptorMeta(config, "shell"),
|
|
375
403
|
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
376
|
-
}, async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, maxOutputTokens }) => {
|
|
377
|
-
const startedAt = performance.now();
|
|
404
|
+
}, async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, maxOutputTokens }, extra) => {
|
|
378
405
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
379
|
-
|
|
380
|
-
const snapshot = await processSessions.start({
|
|
381
|
-
workspaceId,
|
|
382
|
-
command: cmd,
|
|
383
|
-
cwd,
|
|
384
|
-
workspaceRoot: workspace.root,
|
|
385
|
-
tty,
|
|
386
|
-
columns,
|
|
387
|
-
rows,
|
|
388
|
-
yieldTimeMs,
|
|
389
|
-
maxOutputTokens,
|
|
390
|
-
});
|
|
391
|
-
logToolCall(config, {
|
|
406
|
+
return runToolWithHooks(hooks, {
|
|
392
407
|
tool: "exec_command",
|
|
393
|
-
|
|
394
|
-
workingDirectory: workingDirectory ?? ".",
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
408
|
+
invocation: workspaceHookInvocation(workspace),
|
|
409
|
+
payload: { command: cmd, workingDirectory: workingDirectory ?? "." },
|
|
410
|
+
operation: async () => {
|
|
411
|
+
const startedAt = performance.now();
|
|
412
|
+
const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
|
|
413
|
+
const snapshot = await processSessions.start({
|
|
414
|
+
workspaceId,
|
|
415
|
+
command: cmd,
|
|
416
|
+
cwd,
|
|
417
|
+
workspaceRoot: workspace.root,
|
|
418
|
+
tty,
|
|
419
|
+
columns,
|
|
420
|
+
rows,
|
|
421
|
+
yieldTimeMs,
|
|
422
|
+
maxOutputTokens,
|
|
423
|
+
});
|
|
424
|
+
logToolCall(config, {
|
|
425
|
+
tool: "exec_command",
|
|
426
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
427
|
+
workingDirectory: workingDirectory ?? ".",
|
|
428
|
+
command: cmd,
|
|
429
|
+
commandLength: cmd.length,
|
|
430
|
+
exitCode: snapshot.exitCode,
|
|
431
|
+
running: snapshot.running,
|
|
432
|
+
processSessionId: snapshot.sessionId,
|
|
433
|
+
success: snapshot.running || snapshot.exitCode === 0,
|
|
434
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
435
|
+
});
|
|
436
|
+
return processToolResponse("exec_command", workspaceId, snapshot, {
|
|
437
|
+
command: cmd,
|
|
438
|
+
workingDirectory: workingDirectory ?? ".",
|
|
439
|
+
running: snapshot.running,
|
|
440
|
+
exitCode: snapshot.exitCode,
|
|
441
|
+
wallTimeMs: snapshot.wallTimeMs,
|
|
442
|
+
});
|
|
443
|
+
},
|
|
406
444
|
});
|
|
407
445
|
});
|
|
408
446
|
registerAppTool(server, "write_stdin", {
|
|
@@ -432,39 +470,55 @@ function registerCodexProcessTools(server, config, workspaces, processSessions)
|
|
|
432
470
|
outputSchema: processOutputSchema(),
|
|
433
471
|
...toolWidgetDescriptorMeta(config, "shell"),
|
|
434
472
|
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
435
|
-
}, async ({ workspaceId, sessionId, chars, columns, rows, yieldTimeMs, maxOutputTokens }) => {
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
const snapshot = await processSessions.write({
|
|
439
|
-
workspaceId,
|
|
440
|
-
sessionId,
|
|
441
|
-
chars,
|
|
442
|
-
columns,
|
|
443
|
-
rows,
|
|
444
|
-
yieldTimeMs,
|
|
445
|
-
maxOutputTokens,
|
|
446
|
-
});
|
|
447
|
-
logToolCall(config, {
|
|
473
|
+
}, async ({ workspaceId, sessionId, chars, columns, rows, yieldTimeMs, maxOutputTokens }, extra) => {
|
|
474
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
475
|
+
return runToolWithHooks(hooks, {
|
|
448
476
|
tool: "write_stdin",
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
477
|
+
invocation: workspaceHookInvocation(workspace),
|
|
478
|
+
payload: {
|
|
479
|
+
sessionId,
|
|
480
|
+
charactersWritten: chars?.length ?? 0,
|
|
481
|
+
columns,
|
|
482
|
+
rows,
|
|
483
|
+
},
|
|
484
|
+
operation: async () => {
|
|
485
|
+
const startedAt = performance.now();
|
|
486
|
+
const snapshot = await processSessions.write({
|
|
487
|
+
workspaceId,
|
|
488
|
+
sessionId,
|
|
489
|
+
chars,
|
|
490
|
+
columns,
|
|
491
|
+
rows,
|
|
492
|
+
yieldTimeMs,
|
|
493
|
+
maxOutputTokens,
|
|
494
|
+
});
|
|
495
|
+
logToolCall(config, {
|
|
496
|
+
tool: "write_stdin",
|
|
497
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
498
|
+
exitCode: snapshot.exitCode,
|
|
499
|
+
running: snapshot.running,
|
|
500
|
+
processSessionId: snapshot.sessionId,
|
|
501
|
+
success: snapshot.running || snapshot.exitCode === 0,
|
|
502
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
503
|
+
});
|
|
504
|
+
return processToolResponse("write_stdin", workspaceId, snapshot, {
|
|
505
|
+
sessionId,
|
|
506
|
+
charactersWritten: chars?.length ?? 0,
|
|
507
|
+
running: snapshot.running,
|
|
508
|
+
exitCode: snapshot.exitCode,
|
|
509
|
+
wallTimeMs: snapshot.wallTimeMs,
|
|
510
|
+
});
|
|
511
|
+
},
|
|
459
512
|
});
|
|
460
513
|
});
|
|
461
514
|
}
|
|
462
515
|
export function createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders, incomingArtifactAdapters) {
|
|
463
516
|
const toolDescriptions = buildToolDescriptions(config);
|
|
517
|
+
const hooks = new HookRunner(config.hooks, config.logging);
|
|
464
518
|
const server = new McpServer({
|
|
465
519
|
name: "forgerelay",
|
|
466
520
|
title: "ForgeRelay",
|
|
467
|
-
version:
|
|
521
|
+
version: FORGERELAY_VERSION,
|
|
468
522
|
description: "Secure local coding workspace for MCP clients. Provides workspace-scoped file, search, edit, write, and shell tools.",
|
|
469
523
|
}, {
|
|
470
524
|
instructions: buildServerInstructions(config, {
|
|
@@ -557,9 +611,9 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
557
611
|
idempotentHint: false,
|
|
558
612
|
openWorldHint: false,
|
|
559
613
|
},
|
|
560
|
-
}, async ({ path, mode, baseRef, newWorktree }, { _meta }) => {
|
|
614
|
+
}, async ({ path, mode, baseRef, newWorktree }, { _meta, sessionId }) => {
|
|
561
615
|
const startedAt = performance.now();
|
|
562
|
-
const { workspace, agentsFiles, availableAgentsFiles, workspaceReused, includeBootstrapContext, } = await workspaces.openWorkspace({ path, mode, baseRef, newWorktree }, { conversationScopeId: openAiConversationScopeId(_meta) });
|
|
616
|
+
const { workspace, agentsFiles, availableAgentsFiles, hookReports, workspaceReused, includeBootstrapContext, } = await workspaces.openWorkspace({ path, mode, baseRef, newWorktree }, { conversationScopeId: openAiConversationScopeId(_meta) });
|
|
563
617
|
const knownWorktrees = await workspaces.listKnownWorktrees(workspace);
|
|
564
618
|
if (config.widgets === "changes") {
|
|
565
619
|
await reviewCheckpoints.initializeWorkspace({
|
|
@@ -652,12 +706,12 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
652
706
|
];
|
|
653
707
|
logToolCall(config, {
|
|
654
708
|
tool: "open_workspace",
|
|
655
|
-
|
|
709
|
+
...workspaceLogContext(workspace, sessionId),
|
|
656
710
|
path: workspace.root,
|
|
657
711
|
success: true,
|
|
658
712
|
durationMs: Math.round(performance.now() - startedAt),
|
|
659
713
|
});
|
|
660
|
-
return {
|
|
714
|
+
return attachHookReports({
|
|
661
715
|
content: resultContent,
|
|
662
716
|
_meta: {
|
|
663
717
|
tool: "open_workspace",
|
|
@@ -706,7 +760,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
706
760
|
: {}),
|
|
707
761
|
instruction,
|
|
708
762
|
},
|
|
709
|
-
};
|
|
763
|
+
}, hookReports);
|
|
710
764
|
});
|
|
711
765
|
registerAppTool(server, toolNames.closeWorktree, {
|
|
712
766
|
title: "Close worktree",
|
|
@@ -732,39 +786,48 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
732
786
|
}),
|
|
733
787
|
_meta: {},
|
|
734
788
|
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
735
|
-
}, async ({ workspaceId, commitMessage }) => {
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
const result = [
|
|
739
|
-
`Closed managed worktree ${workspaceId}.`,
|
|
740
|
-
`Merged ${closed.branch} into ${closed.targetBranch} by fast-forward.`,
|
|
741
|
-
`Source checkout: ${closed.sourceRoot}`,
|
|
742
|
-
`Commit: ${closed.commitSha}`,
|
|
743
|
-
closed.cleanupWarning
|
|
744
|
-
? `Cleanup warning: ${closed.cleanupWarning}`
|
|
745
|
-
: "The worktree directory and managed branch were removed.",
|
|
746
|
-
].join("\n");
|
|
747
|
-
logToolCall(config, {
|
|
789
|
+
}, async ({ workspaceId, commitMessage }, extra) => {
|
|
790
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
791
|
+
return runToolWithHooks(hooks, {
|
|
748
792
|
tool: toolNames.closeWorktree,
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
793
|
+
invocation: workspaceHookInvocation(workspace),
|
|
794
|
+
payload: { commitMessage },
|
|
795
|
+
afterCwd: (response) => response.structuredContent.sourceRoot,
|
|
796
|
+
operation: async () => {
|
|
797
|
+
const startedAt = performance.now();
|
|
798
|
+
const closed = await workspaces.closeWorktree(workspaceId, commitMessage);
|
|
799
|
+
const result = [
|
|
800
|
+
`Closed managed worktree ${workspaceId}.`,
|
|
801
|
+
`Merged ${closed.branch} into ${closed.targetBranch} by fast-forward.`,
|
|
802
|
+
`Source checkout: ${closed.sourceRoot}`,
|
|
803
|
+
`Commit: ${closed.commitSha}`,
|
|
804
|
+
closed.cleanupWarning
|
|
805
|
+
? `Cleanup warning: ${closed.cleanupWarning}`
|
|
806
|
+
: "The worktree directory and managed branch were removed.",
|
|
807
|
+
].join("\n");
|
|
808
|
+
logToolCall(config, {
|
|
809
|
+
tool: toolNames.closeWorktree,
|
|
810
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
811
|
+
path: closed.sourceRoot,
|
|
812
|
+
success: true,
|
|
813
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
814
|
+
});
|
|
815
|
+
return attachHookReports({
|
|
816
|
+
content: [{ type: "text", text: result }],
|
|
817
|
+
structuredContent: {
|
|
818
|
+
result,
|
|
819
|
+
workspaceId,
|
|
820
|
+
sourceRoot: closed.sourceRoot,
|
|
821
|
+
branch: closed.branch,
|
|
822
|
+
targetBranch: closed.targetBranch,
|
|
823
|
+
commitSha: closed.commitSha,
|
|
824
|
+
mergedSha: closed.mergedSha,
|
|
825
|
+
committed: closed.committed,
|
|
826
|
+
cleanupWarning: closed.cleanupWarning,
|
|
827
|
+
},
|
|
828
|
+
}, closed.hookReports);
|
|
766
829
|
},
|
|
767
|
-
};
|
|
830
|
+
});
|
|
768
831
|
});
|
|
769
832
|
registerAppTool(server, toolNames.read, {
|
|
770
833
|
title: "Read file",
|
|
@@ -776,8 +839,8 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
776
839
|
path: z
|
|
777
840
|
.string()
|
|
778
841
|
.describe(config.skillsEnabled
|
|
779
|
-
? "File path to read, relative to the workspace root. May also be an advertised skill path from open_workspace skills."
|
|
780
|
-
: "File path to read, relative to the workspace root."),
|
|
842
|
+
? "File path to read, relative to the workspace root or absolute inside the OS temp directory. May also be an advertised skill path from open_workspace skills."
|
|
843
|
+
: "File path to read, relative to the workspace root or absolute inside the OS temp directory."),
|
|
781
844
|
offset: z
|
|
782
845
|
.number()
|
|
783
846
|
.int()
|
|
@@ -794,51 +857,59 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
794
857
|
outputSchema: resultOutputSchema(),
|
|
795
858
|
...toolWidgetDescriptorMeta(config, "read"),
|
|
796
859
|
annotations: { readOnlyHint: true },
|
|
797
|
-
}, async ({ workspaceId, ...input }) => {
|
|
798
|
-
const startedAt = performance.now();
|
|
860
|
+
}, async ({ workspaceId, ...input }, extra) => {
|
|
799
861
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
800
|
-
|
|
801
|
-
const response = await readFileTool({ ...input, path: readPath.absolutePath }, {
|
|
802
|
-
cwd: workspace.root,
|
|
803
|
-
root: workspace.root,
|
|
804
|
-
readRoots: readPath.readRoots,
|
|
805
|
-
});
|
|
806
|
-
if (response.isError) {
|
|
807
|
-
logFailedToolResponse(config, {
|
|
808
|
-
tool: toolNames.read,
|
|
809
|
-
workspaceId,
|
|
810
|
-
path: input.path,
|
|
811
|
-
}, response.content, startedAt);
|
|
812
|
-
return response;
|
|
813
|
-
}
|
|
814
|
-
workspaces.markReadPathLoaded(workspace, readPath);
|
|
815
|
-
const summary = {
|
|
816
|
-
...textSummary(response.content),
|
|
817
|
-
offset: input.offset ?? 1,
|
|
818
|
-
limited: input.limit !== undefined,
|
|
819
|
-
};
|
|
820
|
-
logToolCall(config, {
|
|
862
|
+
return runToolWithHooks(hooks, {
|
|
821
863
|
tool: toolNames.read,
|
|
822
|
-
|
|
823
|
-
path: input.path,
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
864
|
+
invocation: workspaceHookInvocation(workspace),
|
|
865
|
+
payload: { path: input.path, offset: input.offset, limit: input.limit },
|
|
866
|
+
isFailure: toolResultIsError,
|
|
867
|
+
operation: async () => {
|
|
868
|
+
const startedAt = performance.now();
|
|
869
|
+
const readPath = workspaces.resolveReadPath(workspace, input.path);
|
|
870
|
+
const response = await readFileTool({ ...input, path: readPath.absolutePath }, {
|
|
871
|
+
cwd: workspace.root,
|
|
872
|
+
root: workspace.root,
|
|
873
|
+
readRoots: readPath.readRoots,
|
|
874
|
+
});
|
|
875
|
+
if (response.isError) {
|
|
876
|
+
logFailedToolResponse(config, {
|
|
877
|
+
tool: toolNames.read,
|
|
878
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
879
|
+
path: input.path,
|
|
880
|
+
}, response.content, startedAt);
|
|
881
|
+
return response;
|
|
882
|
+
}
|
|
883
|
+
workspaces.markReadPathLoaded(workspace, readPath);
|
|
884
|
+
const summary = {
|
|
885
|
+
...textSummary(response.content),
|
|
886
|
+
offset: input.offset ?? 1,
|
|
887
|
+
limited: input.limit !== undefined,
|
|
888
|
+
};
|
|
889
|
+
logToolCall(config, {
|
|
890
|
+
tool: toolNames.read,
|
|
891
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
833
892
|
path: input.path,
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
893
|
+
success: true,
|
|
894
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
895
|
+
});
|
|
896
|
+
return {
|
|
897
|
+
...response,
|
|
898
|
+
_meta: {
|
|
899
|
+
tool: toolNames.read,
|
|
900
|
+
card: {
|
|
901
|
+
workspaceId,
|
|
902
|
+
path: input.path,
|
|
903
|
+
summary,
|
|
904
|
+
payload: { content: response.content },
|
|
905
|
+
},
|
|
906
|
+
},
|
|
907
|
+
structuredContent: {
|
|
908
|
+
result: contentText(response.content),
|
|
909
|
+
},
|
|
910
|
+
};
|
|
840
911
|
},
|
|
841
|
-
};
|
|
912
|
+
});
|
|
842
913
|
});
|
|
843
914
|
if (config.toolMode !== "codex") {
|
|
844
915
|
registerAppTool(server, toolNames.write, {
|
|
@@ -850,60 +921,69 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
850
921
|
.describe("Workspace identifier returned by open_workspace."),
|
|
851
922
|
path: z
|
|
852
923
|
.string()
|
|
853
|
-
.describe("File path to write, relative to the workspace root."),
|
|
924
|
+
.describe("File path to write, relative to the workspace root or absolute inside the OS temp directory."),
|
|
854
925
|
content: z.string().describe("Complete new file content."),
|
|
855
926
|
},
|
|
856
927
|
outputSchema: resultOutputSchema(),
|
|
857
928
|
...toolWidgetDescriptorMeta(config, "write"),
|
|
858
929
|
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
859
|
-
}, async ({ workspaceId, ...input }) => {
|
|
860
|
-
const startedAt = performance.now();
|
|
930
|
+
}, async ({ workspaceId, ...input }, extra) => {
|
|
861
931
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
862
|
-
|
|
863
|
-
const response = await writeFileTool(input, {
|
|
864
|
-
cwd: workspace.root,
|
|
865
|
-
root: workspace.root,
|
|
866
|
-
});
|
|
867
|
-
if (response.isError) {
|
|
868
|
-
logFailedToolResponse(config, {
|
|
869
|
-
tool: toolNames.write,
|
|
870
|
-
workspaceId,
|
|
871
|
-
path: input.path,
|
|
872
|
-
}, response.content, startedAt);
|
|
873
|
-
return response;
|
|
874
|
-
}
|
|
875
|
-
const patch = newFilePatch(input.path, input.content);
|
|
876
|
-
const stats = countDiffStats(patch);
|
|
877
|
-
const summary = {
|
|
878
|
-
...stats,
|
|
879
|
-
lines: contentLineCount(input.content),
|
|
880
|
-
characters: input.content.length,
|
|
881
|
-
};
|
|
882
|
-
logToolCall(config, {
|
|
932
|
+
return runToolWithHooks(hooks, {
|
|
883
933
|
tool: toolNames.write,
|
|
884
|
-
|
|
885
|
-
path: input.path,
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
934
|
+
invocation: workspaceHookInvocation(workspace),
|
|
935
|
+
payload: { path: input.path },
|
|
936
|
+
isFailure: toolResultIsError,
|
|
937
|
+
changedPaths: (result) => toolResultIsError(result) ? [] : [input.path],
|
|
938
|
+
operation: async () => {
|
|
939
|
+
const startedAt = performance.now();
|
|
940
|
+
const response = await writeFileTool(input, {
|
|
941
|
+
cwd: workspace.root,
|
|
942
|
+
root: workspace.root,
|
|
943
|
+
fileRoots: workspaces.fileToolRoots(workspace),
|
|
944
|
+
});
|
|
945
|
+
if (response.isError) {
|
|
946
|
+
logFailedToolResponse(config, {
|
|
947
|
+
tool: toolNames.write,
|
|
948
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
949
|
+
path: input.path,
|
|
950
|
+
}, response.content, startedAt);
|
|
951
|
+
return response;
|
|
952
|
+
}
|
|
953
|
+
const patch = newFilePatch(input.path, input.content);
|
|
954
|
+
const stats = countDiffStats(patch);
|
|
955
|
+
const summary = {
|
|
956
|
+
...stats,
|
|
957
|
+
lines: contentLineCount(input.content),
|
|
958
|
+
characters: input.content.length,
|
|
959
|
+
};
|
|
960
|
+
logToolCall(config, {
|
|
961
|
+
tool: toolNames.write,
|
|
962
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
895
963
|
path: input.path,
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
964
|
+
success: true,
|
|
965
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
966
|
+
});
|
|
967
|
+
return {
|
|
968
|
+
...response,
|
|
969
|
+
_meta: {
|
|
970
|
+
tool: toolNames.write,
|
|
971
|
+
card: {
|
|
972
|
+
workspaceId,
|
|
973
|
+
path: input.path,
|
|
974
|
+
summary,
|
|
975
|
+
payload: {
|
|
976
|
+
content: response.content,
|
|
977
|
+
patch,
|
|
978
|
+
},
|
|
979
|
+
},
|
|
900
980
|
},
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
981
|
+
structuredContent: {
|
|
982
|
+
result: contentText(response.content),
|
|
983
|
+
},
|
|
984
|
+
};
|
|
905
985
|
},
|
|
906
|
-
};
|
|
986
|
+
});
|
|
907
987
|
});
|
|
908
988
|
registerAppTool(server, toolNames.edit, {
|
|
909
989
|
title: "Edit file",
|
|
@@ -914,7 +994,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
914
994
|
.describe("Workspace identifier returned by open_workspace."),
|
|
915
995
|
path: z
|
|
916
996
|
.string()
|
|
917
|
-
.describe("File path to edit, relative to the workspace root."),
|
|
997
|
+
.describe("File path to edit, relative to the workspace root or absolute inside the OS temp directory."),
|
|
918
998
|
edits: z
|
|
919
999
|
.array(z.object({
|
|
920
1000
|
oldText: z
|
|
@@ -929,55 +1009,64 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
929
1009
|
}),
|
|
930
1010
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
931
1011
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
932
|
-
}, async ({ workspaceId, ...input }) => {
|
|
933
|
-
const startedAt = performance.now();
|
|
1012
|
+
}, async ({ workspaceId, ...input }, extra) => {
|
|
934
1013
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
935
|
-
|
|
936
|
-
const response = await editFileTool(input, {
|
|
937
|
-
cwd: workspace.root,
|
|
938
|
-
root: workspace.root,
|
|
939
|
-
});
|
|
940
|
-
if (response.isError) {
|
|
941
|
-
logFailedToolResponse(config, {
|
|
942
|
-
tool: toolNames.edit,
|
|
943
|
-
workspaceId,
|
|
944
|
-
path: input.path,
|
|
945
|
-
}, response.content, startedAt);
|
|
946
|
-
return response;
|
|
947
|
-
}
|
|
948
|
-
const stats = countDiffStats(response.details?.patch ?? response.details?.diff);
|
|
949
|
-
const summary = {
|
|
950
|
-
...stats,
|
|
951
|
-
editCount: input.edits.length,
|
|
952
|
-
};
|
|
953
|
-
const editResultText = `Edited ${input.path} (+${stats.additions} -${stats.removals}).`;
|
|
954
|
-
const editContent = [textBlock(editResultText)];
|
|
955
|
-
logToolCall(config, {
|
|
1014
|
+
return runToolWithHooks(hooks, {
|
|
956
1015
|
tool: toolNames.edit,
|
|
957
|
-
|
|
958
|
-
path: input.path,
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1016
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1017
|
+
payload: { path: input.path, editCount: input.edits.length },
|
|
1018
|
+
isFailure: toolResultIsError,
|
|
1019
|
+
changedPaths: (result) => toolResultIsError(result) ? [] : [input.path],
|
|
1020
|
+
operation: async () => {
|
|
1021
|
+
const startedAt = performance.now();
|
|
1022
|
+
const response = await editFileTool(input, {
|
|
1023
|
+
cwd: workspace.root,
|
|
1024
|
+
root: workspace.root,
|
|
1025
|
+
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1026
|
+
});
|
|
1027
|
+
if (response.isError) {
|
|
1028
|
+
logFailedToolResponse(config, {
|
|
1029
|
+
tool: toolNames.edit,
|
|
1030
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
1031
|
+
path: input.path,
|
|
1032
|
+
}, response.content, startedAt);
|
|
1033
|
+
return response;
|
|
1034
|
+
}
|
|
1035
|
+
const stats = countDiffStats(response.details?.patch ?? response.details?.diff);
|
|
1036
|
+
const summary = {
|
|
1037
|
+
...stats,
|
|
1038
|
+
editCount: input.edits.length,
|
|
1039
|
+
};
|
|
1040
|
+
const editResultText = `Edited ${input.path} (+${stats.additions} -${stats.removals}).`;
|
|
1041
|
+
const editContent = [textBlock(editResultText)];
|
|
1042
|
+
logToolCall(config, {
|
|
1043
|
+
tool: toolNames.edit,
|
|
1044
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
968
1045
|
path: input.path,
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1046
|
+
success: true,
|
|
1047
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1048
|
+
});
|
|
1049
|
+
return {
|
|
1050
|
+
content: editContent,
|
|
1051
|
+
_meta: {
|
|
1052
|
+
tool: toolNames.edit,
|
|
1053
|
+
card: {
|
|
1054
|
+
workspaceId,
|
|
1055
|
+
path: input.path,
|
|
1056
|
+
summary,
|
|
1057
|
+
payload: {
|
|
1058
|
+
diff: response.details?.diff,
|
|
1059
|
+
patch: response.details?.patch,
|
|
1060
|
+
},
|
|
1061
|
+
},
|
|
973
1062
|
},
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1063
|
+
structuredContent: {
|
|
1064
|
+
status: "applied",
|
|
1065
|
+
result: contentText(editContent),
|
|
1066
|
+
},
|
|
1067
|
+
};
|
|
979
1068
|
},
|
|
980
|
-
};
|
|
1069
|
+
});
|
|
981
1070
|
});
|
|
982
1071
|
}
|
|
983
1072
|
if (config.toolMode === "codex") {
|
|
@@ -1003,45 +1092,55 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1003
1092
|
}),
|
|
1004
1093
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
1005
1094
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
1006
|
-
}, async ({ workspaceId, patch }) => {
|
|
1007
|
-
const startedAt = performance.now();
|
|
1095
|
+
}, async ({ workspaceId, patch }, extra) => {
|
|
1008
1096
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1009
|
-
|
|
1010
|
-
const paths = applied.files.map((file) => file.path).join(", ");
|
|
1011
|
-
const result = `Applied patch to ${applied.files.length} file(s): ${paths}`;
|
|
1012
|
-
const content = [textBlock(result)];
|
|
1013
|
-
const displayPath = applied.files.length === 1
|
|
1014
|
-
? applied.files[0]?.path
|
|
1015
|
-
: `${applied.files.length} files`;
|
|
1016
|
-
logToolCall(config, {
|
|
1097
|
+
return runToolWithHooks(hooks, {
|
|
1017
1098
|
tool: "apply_patch",
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1099
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1100
|
+
payload: { patchBytes: Buffer.byteLength(patch) },
|
|
1101
|
+
changedPaths: (response) => Array.from(new Set(response.structuredContent.files.flatMap((file) => [file.previousPath, file.path])
|
|
1102
|
+
.filter((path) => Boolean(path)))),
|
|
1103
|
+
operation: async () => {
|
|
1104
|
+
const startedAt = performance.now();
|
|
1105
|
+
const applied = await applyPatch(workspace.root, patch, [tmpdir()]);
|
|
1106
|
+
const paths = applied.files.map((file) => file.path).join(", ");
|
|
1107
|
+
const result = `Applied patch to ${applied.files.length} file(s): ${paths}`;
|
|
1108
|
+
const content = [textBlock(result)];
|
|
1109
|
+
const displayPath = applied.files.length === 1
|
|
1110
|
+
? applied.files[0]?.path
|
|
1111
|
+
: `${applied.files.length} files`;
|
|
1112
|
+
logToolCall(config, {
|
|
1113
|
+
tool: "apply_patch",
|
|
1114
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
1028
1115
|
path: displayPath,
|
|
1029
|
-
|
|
1030
|
-
|
|
1116
|
+
success: true,
|
|
1117
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1118
|
+
});
|
|
1119
|
+
return {
|
|
1120
|
+
content,
|
|
1121
|
+
_meta: {
|
|
1122
|
+
tool: "apply_patch",
|
|
1123
|
+
card: {
|
|
1124
|
+
workspaceId,
|
|
1125
|
+
path: displayPath,
|
|
1126
|
+
summary: {
|
|
1127
|
+
files: applied.files.length,
|
|
1128
|
+
additions: applied.additions,
|
|
1129
|
+
removals: applied.removals,
|
|
1130
|
+
},
|
|
1131
|
+
files: applied.files,
|
|
1132
|
+
payload: { patch: applied.patch },
|
|
1133
|
+
},
|
|
1134
|
+
},
|
|
1135
|
+
structuredContent: {
|
|
1136
|
+
result,
|
|
1031
1137
|
additions: applied.additions,
|
|
1032
1138
|
removals: applied.removals,
|
|
1139
|
+
files: applied.files,
|
|
1033
1140
|
},
|
|
1034
|
-
|
|
1035
|
-
payload: { patch: applied.patch },
|
|
1036
|
-
},
|
|
1037
|
-
},
|
|
1038
|
-
structuredContent: {
|
|
1039
|
-
result,
|
|
1040
|
-
additions: applied.additions,
|
|
1041
|
-
removals: applied.removals,
|
|
1042
|
-
files: applied.files,
|
|
1141
|
+
};
|
|
1043
1142
|
},
|
|
1044
|
-
};
|
|
1143
|
+
});
|
|
1045
1144
|
});
|
|
1046
1145
|
}
|
|
1047
1146
|
if (config.widgets === "changes") {
|
|
@@ -1056,44 +1155,50 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1056
1155
|
outputSchema: resultOutputSchema(),
|
|
1057
1156
|
...toolWidgetDescriptorMeta(config, "show_changes"),
|
|
1058
1157
|
annotations: { readOnlyHint: true },
|
|
1059
|
-
}, async ({ workspaceId }) => {
|
|
1060
|
-
const startedAt = performance.now();
|
|
1158
|
+
}, async ({ workspaceId }, extra) => {
|
|
1061
1159
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1062
|
-
|
|
1063
|
-
workspaceId,
|
|
1064
|
-
root: workspace.root,
|
|
1065
|
-
markReviewed: true,
|
|
1066
|
-
});
|
|
1067
|
-
const content = [textBlock(review.result)];
|
|
1068
|
-
logToolCall(config, {
|
|
1160
|
+
return runToolWithHooks(hooks, {
|
|
1069
1161
|
tool: "show_changes",
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
return {
|
|
1075
|
-
content,
|
|
1076
|
-
_meta: {
|
|
1077
|
-
tool: "show_changes",
|
|
1078
|
-
card: {
|
|
1162
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1163
|
+
operation: async () => {
|
|
1164
|
+
const startedAt = performance.now();
|
|
1165
|
+
const review = await reviewCheckpoints.reviewChanges({
|
|
1079
1166
|
workspaceId,
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1167
|
+
root: workspace.root,
|
|
1168
|
+
markReviewed: true,
|
|
1169
|
+
});
|
|
1170
|
+
const content = [textBlock(review.result)];
|
|
1171
|
+
logToolCall(config, {
|
|
1172
|
+
tool: "show_changes",
|
|
1173
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
1174
|
+
success: true,
|
|
1175
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1176
|
+
});
|
|
1177
|
+
return {
|
|
1178
|
+
content,
|
|
1179
|
+
_meta: {
|
|
1180
|
+
tool: "show_changes",
|
|
1181
|
+
card: {
|
|
1182
|
+
workspaceId,
|
|
1183
|
+
summary: review.summary,
|
|
1184
|
+
files: review.files,
|
|
1185
|
+
payload: {
|
|
1186
|
+
patch: review.patch,
|
|
1187
|
+
},
|
|
1188
|
+
},
|
|
1084
1189
|
},
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1190
|
+
structuredContent: {
|
|
1191
|
+
result: contentText(content),
|
|
1192
|
+
},
|
|
1193
|
+
};
|
|
1089
1194
|
},
|
|
1090
|
-
};
|
|
1195
|
+
});
|
|
1091
1196
|
});
|
|
1092
1197
|
}
|
|
1093
1198
|
if (config.toolMode === "full") {
|
|
1094
1199
|
registerAppTool(server, toolNames.grep, {
|
|
1095
1200
|
title: "Grep",
|
|
1096
|
-
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.",
|
|
1201
|
+
description: "Search file contents inside an open workspace or the OS temp directory. Use this before broad reads when looking for symbols, text, or usage sites. Respects project ignore rules. Call open_workspace first and pass workspaceId.",
|
|
1097
1202
|
inputSchema: {
|
|
1098
1203
|
workspaceId: z
|
|
1099
1204
|
.string()
|
|
@@ -1102,60 +1207,67 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1102
1207
|
path: z
|
|
1103
1208
|
.string()
|
|
1104
1209
|
.optional()
|
|
1105
|
-
.describe("Optional path or glob scope relative to the workspace root."),
|
|
1210
|
+
.describe("Optional path or glob scope relative to the workspace root, or an absolute path inside the OS temp directory."),
|
|
1106
1211
|
include: z.string().optional().describe("Optional include glob."),
|
|
1107
1212
|
},
|
|
1108
1213
|
outputSchema: resultOutputSchema(),
|
|
1109
1214
|
...toolWidgetDescriptorMeta(config, "search"),
|
|
1110
1215
|
annotations: { readOnlyHint: true },
|
|
1111
|
-
}, async ({ workspaceId, ...input }) => {
|
|
1112
|
-
const startedAt = performance.now();
|
|
1216
|
+
}, async ({ workspaceId, ...input }, extra) => {
|
|
1113
1217
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1114
|
-
|
|
1115
|
-
workspaces.resolvePath(workspace, input.path);
|
|
1116
|
-
const response = await grepFilesTool(input, {
|
|
1117
|
-
cwd: workspace.root,
|
|
1118
|
-
root: workspace.root,
|
|
1119
|
-
});
|
|
1120
|
-
if (response.isError) {
|
|
1121
|
-
logFailedToolResponse(config, {
|
|
1122
|
-
tool: toolNames.grep,
|
|
1123
|
-
workspaceId,
|
|
1124
|
-
path: input.path,
|
|
1125
|
-
}, response.content, startedAt);
|
|
1126
|
-
return response;
|
|
1127
|
-
}
|
|
1128
|
-
const summary = {
|
|
1129
|
-
pattern: input.pattern,
|
|
1130
|
-
scope: input.path ?? ".",
|
|
1131
|
-
...textSummary(response.content),
|
|
1132
|
-
};
|
|
1133
|
-
logToolCall(config, {
|
|
1218
|
+
return runToolWithHooks(hooks, {
|
|
1134
1219
|
tool: toolNames.grep,
|
|
1135
|
-
|
|
1136
|
-
path: input.path,
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1220
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1221
|
+
payload: { pattern: input.pattern, path: input.path, include: input.include },
|
|
1222
|
+
isFailure: toolResultIsError,
|
|
1223
|
+
operation: async () => {
|
|
1224
|
+
const startedAt = performance.now();
|
|
1225
|
+
const response = await grepFilesTool(input, {
|
|
1226
|
+
cwd: workspace.root,
|
|
1227
|
+
root: workspace.root,
|
|
1228
|
+
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1229
|
+
});
|
|
1230
|
+
if (response.isError) {
|
|
1231
|
+
logFailedToolResponse(config, {
|
|
1232
|
+
tool: toolNames.grep,
|
|
1233
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
1234
|
+
path: input.path,
|
|
1235
|
+
}, response.content, startedAt);
|
|
1236
|
+
return response;
|
|
1237
|
+
}
|
|
1238
|
+
const summary = {
|
|
1239
|
+
pattern: input.pattern,
|
|
1240
|
+
scope: input.path ?? ".",
|
|
1241
|
+
...textSummary(response.content),
|
|
1242
|
+
};
|
|
1243
|
+
logToolCall(config, {
|
|
1244
|
+
tool: toolNames.grep,
|
|
1245
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
1146
1246
|
path: input.path,
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1247
|
+
success: true,
|
|
1248
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1249
|
+
});
|
|
1250
|
+
return {
|
|
1251
|
+
...response,
|
|
1252
|
+
_meta: {
|
|
1253
|
+
tool: toolNames.grep,
|
|
1254
|
+
card: {
|
|
1255
|
+
workspaceId,
|
|
1256
|
+
path: input.path,
|
|
1257
|
+
summary,
|
|
1258
|
+
payload: { content: response.content },
|
|
1259
|
+
},
|
|
1260
|
+
},
|
|
1261
|
+
structuredContent: {
|
|
1262
|
+
result: contentText(response.content),
|
|
1263
|
+
},
|
|
1264
|
+
};
|
|
1153
1265
|
},
|
|
1154
|
-
};
|
|
1266
|
+
});
|
|
1155
1267
|
});
|
|
1156
1268
|
registerAppTool(server, toolNames.glob, {
|
|
1157
1269
|
title: "Glob",
|
|
1158
|
-
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.",
|
|
1270
|
+
description: "Find files by glob pattern inside an open workspace or the OS temp directory. Use this to discover filenames or narrow file sets before reading. Respects project ignore rules. Call open_workspace first and pass workspaceId.",
|
|
1159
1271
|
inputSchema: {
|
|
1160
1272
|
workspaceId: z
|
|
1161
1273
|
.string()
|
|
@@ -1164,109 +1276,124 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1164
1276
|
path: z
|
|
1165
1277
|
.string()
|
|
1166
1278
|
.optional()
|
|
1167
|
-
.describe("Optional path scope relative to the workspace root."),
|
|
1279
|
+
.describe("Optional path scope relative to the workspace root, or an absolute path inside the OS temp directory."),
|
|
1168
1280
|
},
|
|
1169
1281
|
outputSchema: resultOutputSchema(),
|
|
1170
1282
|
...toolWidgetDescriptorMeta(config, "search"),
|
|
1171
1283
|
annotations: { readOnlyHint: true },
|
|
1172
|
-
}, async ({ workspaceId, ...input }) => {
|
|
1173
|
-
const startedAt = performance.now();
|
|
1284
|
+
}, async ({ workspaceId, ...input }, extra) => {
|
|
1174
1285
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1175
|
-
|
|
1176
|
-
workspaces.resolvePath(workspace, input.path);
|
|
1177
|
-
const response = await findFilesTool(input, {
|
|
1178
|
-
cwd: workspace.root,
|
|
1179
|
-
root: workspace.root,
|
|
1180
|
-
});
|
|
1181
|
-
if (response.isError) {
|
|
1182
|
-
logFailedToolResponse(config, {
|
|
1183
|
-
tool: toolNames.glob,
|
|
1184
|
-
workspaceId,
|
|
1185
|
-
path: input.path,
|
|
1186
|
-
}, response.content, startedAt);
|
|
1187
|
-
return response;
|
|
1188
|
-
}
|
|
1189
|
-
const summary = {
|
|
1190
|
-
pattern: input.pattern,
|
|
1191
|
-
scope: input.path ?? ".",
|
|
1192
|
-
...textSummary(response.content),
|
|
1193
|
-
};
|
|
1194
|
-
logToolCall(config, {
|
|
1286
|
+
return runToolWithHooks(hooks, {
|
|
1195
1287
|
tool: toolNames.glob,
|
|
1196
|
-
|
|
1197
|
-
path: input.path,
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1288
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1289
|
+
payload: { pattern: input.pattern, path: input.path },
|
|
1290
|
+
isFailure: toolResultIsError,
|
|
1291
|
+
operation: async () => {
|
|
1292
|
+
const startedAt = performance.now();
|
|
1293
|
+
const response = await findFilesTool(input, {
|
|
1294
|
+
cwd: workspace.root,
|
|
1295
|
+
root: workspace.root,
|
|
1296
|
+
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1297
|
+
});
|
|
1298
|
+
if (response.isError) {
|
|
1299
|
+
logFailedToolResponse(config, {
|
|
1300
|
+
tool: toolNames.glob,
|
|
1301
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
1302
|
+
path: input.path,
|
|
1303
|
+
}, response.content, startedAt);
|
|
1304
|
+
return response;
|
|
1305
|
+
}
|
|
1306
|
+
const summary = {
|
|
1307
|
+
pattern: input.pattern,
|
|
1308
|
+
scope: input.path ?? ".",
|
|
1309
|
+
...textSummary(response.content),
|
|
1310
|
+
};
|
|
1311
|
+
logToolCall(config, {
|
|
1312
|
+
tool: toolNames.glob,
|
|
1313
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
1207
1314
|
path: input.path,
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1315
|
+
success: true,
|
|
1316
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1317
|
+
});
|
|
1318
|
+
return {
|
|
1319
|
+
...response,
|
|
1320
|
+
_meta: {
|
|
1321
|
+
tool: toolNames.glob,
|
|
1322
|
+
card: {
|
|
1323
|
+
workspaceId,
|
|
1324
|
+
path: input.path,
|
|
1325
|
+
summary,
|
|
1326
|
+
payload: { content: response.content },
|
|
1327
|
+
},
|
|
1328
|
+
},
|
|
1329
|
+
structuredContent: {
|
|
1330
|
+
result: contentText(response.content),
|
|
1331
|
+
},
|
|
1332
|
+
};
|
|
1214
1333
|
},
|
|
1215
|
-
};
|
|
1334
|
+
});
|
|
1216
1335
|
});
|
|
1217
1336
|
registerAppTool(server, toolNames.ls, {
|
|
1218
1337
|
title: "Ls",
|
|
1219
|
-
description: "List a directory inside an open workspace. Use this for directory inspection before reading files. Call open_workspace first and pass workspaceId.",
|
|
1338
|
+
description: "List a directory inside an open workspace or the OS temp directory. Use this for directory inspection before reading files. Call open_workspace first and pass workspaceId.",
|
|
1220
1339
|
inputSchema: {
|
|
1221
1340
|
workspaceId: z
|
|
1222
1341
|
.string()
|
|
1223
1342
|
.describe("Workspace identifier returned by open_workspace."),
|
|
1224
1343
|
path: z
|
|
1225
1344
|
.string()
|
|
1226
|
-
.describe("Directory path to list, relative to the workspace root."),
|
|
1345
|
+
.describe("Directory path to list, relative to the workspace root or absolute inside the OS temp directory."),
|
|
1227
1346
|
},
|
|
1228
1347
|
outputSchema: resultOutputSchema(),
|
|
1229
1348
|
...toolWidgetDescriptorMeta(config, "directory"),
|
|
1230
1349
|
annotations: { readOnlyHint: true },
|
|
1231
|
-
}, async ({ workspaceId, ...input }) => {
|
|
1232
|
-
const startedAt = performance.now();
|
|
1350
|
+
}, async ({ workspaceId, ...input }, extra) => {
|
|
1233
1351
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1234
|
-
|
|
1235
|
-
const response = await listDirectoryTool(input, {
|
|
1236
|
-
cwd: workspace.root,
|
|
1237
|
-
root: workspace.root,
|
|
1238
|
-
});
|
|
1239
|
-
if (response.isError) {
|
|
1240
|
-
logFailedToolResponse(config, {
|
|
1241
|
-
tool: toolNames.ls,
|
|
1242
|
-
workspaceId,
|
|
1243
|
-
path: input.path,
|
|
1244
|
-
}, response.content, startedAt);
|
|
1245
|
-
return response;
|
|
1246
|
-
}
|
|
1247
|
-
const summary = textSummary(response.content);
|
|
1248
|
-
logToolCall(config, {
|
|
1352
|
+
return runToolWithHooks(hooks, {
|
|
1249
1353
|
tool: toolNames.ls,
|
|
1250
|
-
|
|
1251
|
-
path: input.path,
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1354
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1355
|
+
payload: { path: input.path },
|
|
1356
|
+
isFailure: toolResultIsError,
|
|
1357
|
+
operation: async () => {
|
|
1358
|
+
const startedAt = performance.now();
|
|
1359
|
+
const response = await listDirectoryTool(input, {
|
|
1360
|
+
cwd: workspace.root,
|
|
1361
|
+
root: workspace.root,
|
|
1362
|
+
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1363
|
+
});
|
|
1364
|
+
if (response.isError) {
|
|
1365
|
+
logFailedToolResponse(config, {
|
|
1366
|
+
tool: toolNames.ls,
|
|
1367
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
1368
|
+
path: input.path,
|
|
1369
|
+
}, response.content, startedAt);
|
|
1370
|
+
return response;
|
|
1371
|
+
}
|
|
1372
|
+
const summary = textSummary(response.content);
|
|
1373
|
+
logToolCall(config, {
|
|
1374
|
+
tool: toolNames.ls,
|
|
1375
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
1261
1376
|
path: input.path,
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1377
|
+
success: true,
|
|
1378
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1379
|
+
});
|
|
1380
|
+
return {
|
|
1381
|
+
...response,
|
|
1382
|
+
_meta: {
|
|
1383
|
+
tool: toolNames.ls,
|
|
1384
|
+
card: {
|
|
1385
|
+
workspaceId,
|
|
1386
|
+
path: input.path,
|
|
1387
|
+
summary,
|
|
1388
|
+
payload: { content: response.content },
|
|
1389
|
+
},
|
|
1390
|
+
},
|
|
1391
|
+
structuredContent: {
|
|
1392
|
+
result: contentText(response.content),
|
|
1393
|
+
},
|
|
1394
|
+
};
|
|
1268
1395
|
},
|
|
1269
|
-
};
|
|
1396
|
+
});
|
|
1270
1397
|
});
|
|
1271
1398
|
}
|
|
1272
1399
|
if (config.toolMode !== "codex") {
|
|
@@ -1294,62 +1421,75 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1294
1421
|
outputSchema: resultOutputSchema(),
|
|
1295
1422
|
...toolWidgetDescriptorMeta(config, "shell"),
|
|
1296
1423
|
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
1297
|
-
}, async ({ workspaceId, workingDirectory, ...input }) => {
|
|
1298
|
-
const startedAt = performance.now();
|
|
1424
|
+
}, async ({ workspaceId, workingDirectory, ...input }, extra) => {
|
|
1299
1425
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1300
|
-
|
|
1301
|
-
const response = await runShellTool(input, {
|
|
1302
|
-
cwd,
|
|
1303
|
-
root: workspace.root,
|
|
1304
|
-
});
|
|
1305
|
-
if (response.isError) {
|
|
1306
|
-
logFailedToolResponse(config, {
|
|
1307
|
-
tool: toolNames.shell,
|
|
1308
|
-
workspaceId,
|
|
1309
|
-
workingDirectory: workingDirectory ?? ".",
|
|
1310
|
-
command: input.command,
|
|
1311
|
-
commandLength: input.command.length,
|
|
1312
|
-
}, response.content, startedAt);
|
|
1313
|
-
return response;
|
|
1314
|
-
}
|
|
1315
|
-
const summary = {
|
|
1316
|
-
command: input.command,
|
|
1317
|
-
workingDirectory: workingDirectory ?? ".",
|
|
1318
|
-
...textSummary(response.content),
|
|
1319
|
-
};
|
|
1320
|
-
logToolCall(config, {
|
|
1426
|
+
return runToolWithHooks(hooks, {
|
|
1321
1427
|
tool: toolNames.shell,
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1328
|
-
});
|
|
1329
|
-
return {
|
|
1330
|
-
...response,
|
|
1331
|
-
_meta: {
|
|
1332
|
-
tool: toolNames.shell,
|
|
1333
|
-
card: {
|
|
1334
|
-
workspaceId,
|
|
1335
|
-
path: workingDirectory,
|
|
1336
|
-
summary,
|
|
1337
|
-
payload: { content: response.content },
|
|
1338
|
-
},
|
|
1428
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1429
|
+
payload: {
|
|
1430
|
+
command: input.command,
|
|
1431
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1432
|
+
timeoutSeconds: input.timeout,
|
|
1339
1433
|
},
|
|
1340
|
-
|
|
1341
|
-
|
|
1434
|
+
isFailure: toolResultIsError,
|
|
1435
|
+
operation: async () => {
|
|
1436
|
+
const startedAt = performance.now();
|
|
1437
|
+
const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
|
|
1438
|
+
const response = await runShellTool(input, {
|
|
1439
|
+
cwd,
|
|
1440
|
+
root: workspace.root,
|
|
1441
|
+
});
|
|
1442
|
+
if (response.isError) {
|
|
1443
|
+
logFailedToolResponse(config, {
|
|
1444
|
+
tool: toolNames.shell,
|
|
1445
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
1446
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1447
|
+
command: input.command,
|
|
1448
|
+
commandLength: input.command.length,
|
|
1449
|
+
}, response.content, startedAt);
|
|
1450
|
+
return response;
|
|
1451
|
+
}
|
|
1452
|
+
const summary = {
|
|
1453
|
+
command: input.command,
|
|
1454
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1455
|
+
...textSummary(response.content),
|
|
1456
|
+
};
|
|
1457
|
+
logToolCall(config, {
|
|
1458
|
+
tool: toolNames.shell,
|
|
1459
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
1460
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1461
|
+
command: input.command,
|
|
1462
|
+
commandLength: input.command.length,
|
|
1463
|
+
success: true,
|
|
1464
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1465
|
+
});
|
|
1466
|
+
return {
|
|
1467
|
+
...response,
|
|
1468
|
+
_meta: {
|
|
1469
|
+
tool: toolNames.shell,
|
|
1470
|
+
card: {
|
|
1471
|
+
workspaceId,
|
|
1472
|
+
path: workingDirectory,
|
|
1473
|
+
summary,
|
|
1474
|
+
payload: { content: response.content },
|
|
1475
|
+
},
|
|
1476
|
+
},
|
|
1477
|
+
structuredContent: {
|
|
1478
|
+
result: contentText(response.content),
|
|
1479
|
+
},
|
|
1480
|
+
};
|
|
1342
1481
|
},
|
|
1343
|
-
};
|
|
1482
|
+
});
|
|
1344
1483
|
});
|
|
1345
1484
|
}
|
|
1346
1485
|
if (config.toolMode === "codex") {
|
|
1347
|
-
registerCodexProcessTools(server, config, workspaces, processSessions);
|
|
1486
|
+
registerCodexProcessTools(server, config, workspaces, processSessions, hooks);
|
|
1348
1487
|
}
|
|
1349
1488
|
if (config.artifactsEnabled && isArtifactDownloadSupportedPlatform()) {
|
|
1350
1489
|
registerArtifactTools(server, {
|
|
1351
1490
|
config,
|
|
1352
1491
|
workspaces,
|
|
1492
|
+
hooks,
|
|
1353
1493
|
incomingArtifactAdapters,
|
|
1354
1494
|
});
|
|
1355
1495
|
}
|