@akira-tl/forgerelay 0.1.0 → 0.2.0
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 +36 -0
- package/README.md +57 -10
- package/dist/artifact-tools.js +28 -14
- package/dist/cli.js +46 -3
- package/dist/config.js +14 -0
- 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 +542 -0
- package/dist/local-agent-store.js +14 -1
- package/dist/mcp/server-instructions.js +2 -1
- package/dist/process-platform.js +1 -0
- package/dist/server.js +542 -415
- package/dist/user-config.js +33 -1
- package/dist/workspaces.js +87 -16
- package/docs/chatgpt-coding-workflow.md +11 -5
- package/docs/configuration.md +137 -0
- package/docs/debugging.md +126 -0
- package/docs/roadmap.md +16 -21
- package/docs/security.md +14 -0
- package/docs/versioning.md +22 -15
- package/package.json +5 -3
- package/scripts/debug/accept.mjs +615 -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
|
@@ -15,6 +15,7 @@ import * as z from "zod/v4";
|
|
|
15
15
|
import { applyPatch } from "./apply-patch.js";
|
|
16
16
|
import { isArtifactDownloadSupportedPlatform, registerArtifactTools, } from "./artifact-tools.js";
|
|
17
17
|
import { loadConfig } from "./config.js";
|
|
18
|
+
import { attachHookReports, HookRunner, runToolWithHooks } from "./hooks.js";
|
|
18
19
|
import { buildServerInstructions, buildToolDescriptions, toolNames, } from "./mcp/server-instructions.js";
|
|
19
20
|
import { createOpenAIIncomingArtifactAdapter, } from "./incoming-artifacts.js";
|
|
20
21
|
import { logEvent, requestIp, requestPath, commandPreview, sessionIdPrefix, } from "./logger.js";
|
|
@@ -33,6 +34,7 @@ import { formatLocalAgentProviderAvailabilitySummary, getLocalAgentProviderAvail
|
|
|
33
34
|
// MCP clients can reconnect without closing the previous transport. Bound stale
|
|
34
35
|
// session retention so abandoned MCP servers do not accumulate for the life of the process.
|
|
35
36
|
const MCP_SESSION_IDLE_TIMEOUT_MS = 24 * 60 * 60 * 1_000;
|
|
37
|
+
const FORGERELAY_VERSION = readForgeRelayVersion();
|
|
36
38
|
const MCP_SESSION_CLEANUP_INTERVAL_MS = 5 * 60 * 1_000;
|
|
37
39
|
const WORKSPACE_APP_URI = "ui://forgerelay/workspace-app.html";
|
|
38
40
|
const WORKSPACE_APP_MANIFEST_ENTRY = "workspace-app.html";
|
|
@@ -313,6 +315,13 @@ function processOutputSchema() {
|
|
|
313
315
|
outputTruncated: z.boolean(),
|
|
314
316
|
});
|
|
315
317
|
}
|
|
318
|
+
function readForgeRelayVersion() {
|
|
319
|
+
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
320
|
+
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
321
|
+
throw new Error("Unable to read ForgeRelay package version.");
|
|
322
|
+
}
|
|
323
|
+
return packageJson.version;
|
|
324
|
+
}
|
|
316
325
|
function processToolResponse(tool, workspaceId, snapshot, summary) {
|
|
317
326
|
const result = processResult(snapshot);
|
|
318
327
|
const content = [textBlock(result)];
|
|
@@ -338,7 +347,18 @@ function processToolResponse(tool, workspaceId, snapshot, summary) {
|
|
|
338
347
|
},
|
|
339
348
|
};
|
|
340
349
|
}
|
|
341
|
-
function
|
|
350
|
+
function workspaceHookInvocation(workspace) {
|
|
351
|
+
return {
|
|
352
|
+
workspaceId: workspace.id,
|
|
353
|
+
workspaceRoot: workspace.root,
|
|
354
|
+
workspaceMode: workspace.mode,
|
|
355
|
+
sourceRoot: workspace.sourceRoot,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function toolResultIsError(result) {
|
|
359
|
+
return typeof result === "object" && result !== null && result.isError === true;
|
|
360
|
+
}
|
|
361
|
+
function registerCodexProcessTools(server, config, workspaces, processSessions, hooks) {
|
|
342
362
|
registerAppTool(server, "exec_command", {
|
|
343
363
|
title: "Execute command",
|
|
344
364
|
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.",
|
|
@@ -374,35 +394,42 @@ function registerCodexProcessTools(server, config, workspaces, processSessions)
|
|
|
374
394
|
...toolWidgetDescriptorMeta(config, "shell"),
|
|
375
395
|
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
376
396
|
}, async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, maxOutputTokens }) => {
|
|
377
|
-
const startedAt = performance.now();
|
|
378
397
|
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, {
|
|
398
|
+
return runToolWithHooks(hooks, {
|
|
392
399
|
tool: "exec_command",
|
|
393
|
-
|
|
394
|
-
workingDirectory: workingDirectory ?? ".",
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
400
|
+
invocation: workspaceHookInvocation(workspace),
|
|
401
|
+
payload: { command: cmd, workingDirectory: workingDirectory ?? "." },
|
|
402
|
+
operation: async () => {
|
|
403
|
+
const startedAt = performance.now();
|
|
404
|
+
const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
|
|
405
|
+
const snapshot = await processSessions.start({
|
|
406
|
+
workspaceId,
|
|
407
|
+
command: cmd,
|
|
408
|
+
cwd,
|
|
409
|
+
workspaceRoot: workspace.root,
|
|
410
|
+
tty,
|
|
411
|
+
columns,
|
|
412
|
+
rows,
|
|
413
|
+
yieldTimeMs,
|
|
414
|
+
maxOutputTokens,
|
|
415
|
+
});
|
|
416
|
+
logToolCall(config, {
|
|
417
|
+
tool: "exec_command",
|
|
418
|
+
workspaceId,
|
|
419
|
+
workingDirectory: workingDirectory ?? ".",
|
|
420
|
+
command: cmd,
|
|
421
|
+
commandLength: cmd.length,
|
|
422
|
+
success: true,
|
|
423
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
424
|
+
});
|
|
425
|
+
return processToolResponse("exec_command", workspaceId, snapshot, {
|
|
426
|
+
command: cmd,
|
|
427
|
+
workingDirectory: workingDirectory ?? ".",
|
|
428
|
+
running: snapshot.running,
|
|
429
|
+
exitCode: snapshot.exitCode,
|
|
430
|
+
wallTimeMs: snapshot.wallTimeMs,
|
|
431
|
+
});
|
|
432
|
+
},
|
|
406
433
|
});
|
|
407
434
|
});
|
|
408
435
|
registerAppTool(server, "write_stdin", {
|
|
@@ -433,38 +460,51 @@ function registerCodexProcessTools(server, config, workspaces, processSessions)
|
|
|
433
460
|
...toolWidgetDescriptorMeta(config, "shell"),
|
|
434
461
|
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
435
462
|
}, 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, {
|
|
463
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
464
|
+
return runToolWithHooks(hooks, {
|
|
448
465
|
tool: "write_stdin",
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
466
|
+
invocation: workspaceHookInvocation(workspace),
|
|
467
|
+
payload: {
|
|
468
|
+
sessionId,
|
|
469
|
+
charactersWritten: chars?.length ?? 0,
|
|
470
|
+
columns,
|
|
471
|
+
rows,
|
|
472
|
+
},
|
|
473
|
+
operation: async () => {
|
|
474
|
+
const startedAt = performance.now();
|
|
475
|
+
const snapshot = await processSessions.write({
|
|
476
|
+
workspaceId,
|
|
477
|
+
sessionId,
|
|
478
|
+
chars,
|
|
479
|
+
columns,
|
|
480
|
+
rows,
|
|
481
|
+
yieldTimeMs,
|
|
482
|
+
maxOutputTokens,
|
|
483
|
+
});
|
|
484
|
+
logToolCall(config, {
|
|
485
|
+
tool: "write_stdin",
|
|
486
|
+
workspaceId,
|
|
487
|
+
success: true,
|
|
488
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
489
|
+
});
|
|
490
|
+
return processToolResponse("write_stdin", workspaceId, snapshot, {
|
|
491
|
+
sessionId,
|
|
492
|
+
charactersWritten: chars?.length ?? 0,
|
|
493
|
+
running: snapshot.running,
|
|
494
|
+
exitCode: snapshot.exitCode,
|
|
495
|
+
wallTimeMs: snapshot.wallTimeMs,
|
|
496
|
+
});
|
|
497
|
+
},
|
|
459
498
|
});
|
|
460
499
|
});
|
|
461
500
|
}
|
|
462
501
|
export function createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders, incomingArtifactAdapters) {
|
|
463
502
|
const toolDescriptions = buildToolDescriptions(config);
|
|
503
|
+
const hooks = new HookRunner(config.hooks, config.logging);
|
|
464
504
|
const server = new McpServer({
|
|
465
505
|
name: "forgerelay",
|
|
466
506
|
title: "ForgeRelay",
|
|
467
|
-
version:
|
|
507
|
+
version: FORGERELAY_VERSION,
|
|
468
508
|
description: "Secure local coding workspace for MCP clients. Provides workspace-scoped file, search, edit, write, and shell tools.",
|
|
469
509
|
}, {
|
|
470
510
|
instructions: buildServerInstructions(config, {
|
|
@@ -559,7 +599,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
559
599
|
},
|
|
560
600
|
}, async ({ path, mode, baseRef, newWorktree }, { _meta }) => {
|
|
561
601
|
const startedAt = performance.now();
|
|
562
|
-
const { workspace, agentsFiles, availableAgentsFiles, workspaceReused, includeBootstrapContext, } = await workspaces.openWorkspace({ path, mode, baseRef, newWorktree }, { conversationScopeId: openAiConversationScopeId(_meta) });
|
|
602
|
+
const { workspace, agentsFiles, availableAgentsFiles, hookReports, workspaceReused, includeBootstrapContext, } = await workspaces.openWorkspace({ path, mode, baseRef, newWorktree }, { conversationScopeId: openAiConversationScopeId(_meta) });
|
|
563
603
|
const knownWorktrees = await workspaces.listKnownWorktrees(workspace);
|
|
564
604
|
if (config.widgets === "changes") {
|
|
565
605
|
await reviewCheckpoints.initializeWorkspace({
|
|
@@ -657,7 +697,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
657
697
|
success: true,
|
|
658
698
|
durationMs: Math.round(performance.now() - startedAt),
|
|
659
699
|
});
|
|
660
|
-
return {
|
|
700
|
+
return attachHookReports({
|
|
661
701
|
content: resultContent,
|
|
662
702
|
_meta: {
|
|
663
703
|
tool: "open_workspace",
|
|
@@ -706,7 +746,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
706
746
|
: {}),
|
|
707
747
|
instruction,
|
|
708
748
|
},
|
|
709
|
-
};
|
|
749
|
+
}, hookReports);
|
|
710
750
|
});
|
|
711
751
|
registerAppTool(server, toolNames.closeWorktree, {
|
|
712
752
|
title: "Close worktree",
|
|
@@ -733,38 +773,47 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
733
773
|
_meta: {},
|
|
734
774
|
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
735
775
|
}, 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, {
|
|
776
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
777
|
+
return runToolWithHooks(hooks, {
|
|
748
778
|
tool: toolNames.closeWorktree,
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
779
|
+
invocation: workspaceHookInvocation(workspace),
|
|
780
|
+
payload: { commitMessage },
|
|
781
|
+
afterCwd: (response) => response.structuredContent.sourceRoot,
|
|
782
|
+
operation: async () => {
|
|
783
|
+
const startedAt = performance.now();
|
|
784
|
+
const closed = await workspaces.closeWorktree(workspaceId, commitMessage);
|
|
785
|
+
const result = [
|
|
786
|
+
`Closed managed worktree ${workspaceId}.`,
|
|
787
|
+
`Merged ${closed.branch} into ${closed.targetBranch} by fast-forward.`,
|
|
788
|
+
`Source checkout: ${closed.sourceRoot}`,
|
|
789
|
+
`Commit: ${closed.commitSha}`,
|
|
790
|
+
closed.cleanupWarning
|
|
791
|
+
? `Cleanup warning: ${closed.cleanupWarning}`
|
|
792
|
+
: "The worktree directory and managed branch were removed.",
|
|
793
|
+
].join("\n");
|
|
794
|
+
logToolCall(config, {
|
|
795
|
+
tool: toolNames.closeWorktree,
|
|
796
|
+
workspaceId,
|
|
797
|
+
path: closed.sourceRoot,
|
|
798
|
+
success: true,
|
|
799
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
800
|
+
});
|
|
801
|
+
return attachHookReports({
|
|
802
|
+
content: [{ type: "text", text: result }],
|
|
803
|
+
structuredContent: {
|
|
804
|
+
result,
|
|
805
|
+
workspaceId,
|
|
806
|
+
sourceRoot: closed.sourceRoot,
|
|
807
|
+
branch: closed.branch,
|
|
808
|
+
targetBranch: closed.targetBranch,
|
|
809
|
+
commitSha: closed.commitSha,
|
|
810
|
+
mergedSha: closed.mergedSha,
|
|
811
|
+
committed: closed.committed,
|
|
812
|
+
cleanupWarning: closed.cleanupWarning,
|
|
813
|
+
},
|
|
814
|
+
}, closed.hookReports);
|
|
766
815
|
},
|
|
767
|
-
};
|
|
816
|
+
});
|
|
768
817
|
});
|
|
769
818
|
registerAppTool(server, toolNames.read, {
|
|
770
819
|
title: "Read file",
|
|
@@ -795,50 +844,58 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
795
844
|
...toolWidgetDescriptorMeta(config, "read"),
|
|
796
845
|
annotations: { readOnlyHint: true },
|
|
797
846
|
}, async ({ workspaceId, ...input }) => {
|
|
798
|
-
const startedAt = performance.now();
|
|
799
847
|
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, {
|
|
848
|
+
return runToolWithHooks(hooks, {
|
|
821
849
|
tool: toolNames.read,
|
|
822
|
-
|
|
823
|
-
path: input.path,
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
850
|
+
invocation: workspaceHookInvocation(workspace),
|
|
851
|
+
payload: { path: input.path, offset: input.offset, limit: input.limit },
|
|
852
|
+
isFailure: toolResultIsError,
|
|
853
|
+
operation: async () => {
|
|
854
|
+
const startedAt = performance.now();
|
|
855
|
+
const readPath = workspaces.resolveReadPath(workspace, input.path);
|
|
856
|
+
const response = await readFileTool({ ...input, path: readPath.absolutePath }, {
|
|
857
|
+
cwd: workspace.root,
|
|
858
|
+
root: workspace.root,
|
|
859
|
+
readRoots: readPath.readRoots,
|
|
860
|
+
});
|
|
861
|
+
if (response.isError) {
|
|
862
|
+
logFailedToolResponse(config, {
|
|
863
|
+
tool: toolNames.read,
|
|
864
|
+
workspaceId,
|
|
865
|
+
path: input.path,
|
|
866
|
+
}, response.content, startedAt);
|
|
867
|
+
return response;
|
|
868
|
+
}
|
|
869
|
+
workspaces.markReadPathLoaded(workspace, readPath);
|
|
870
|
+
const summary = {
|
|
871
|
+
...textSummary(response.content),
|
|
872
|
+
offset: input.offset ?? 1,
|
|
873
|
+
limited: input.limit !== undefined,
|
|
874
|
+
};
|
|
875
|
+
logToolCall(config, {
|
|
876
|
+
tool: toolNames.read,
|
|
832
877
|
workspaceId,
|
|
833
878
|
path: input.path,
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
879
|
+
success: true,
|
|
880
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
881
|
+
});
|
|
882
|
+
return {
|
|
883
|
+
...response,
|
|
884
|
+
_meta: {
|
|
885
|
+
tool: toolNames.read,
|
|
886
|
+
card: {
|
|
887
|
+
workspaceId,
|
|
888
|
+
path: input.path,
|
|
889
|
+
summary,
|
|
890
|
+
payload: { content: response.content },
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
structuredContent: {
|
|
894
|
+
result: contentText(response.content),
|
|
895
|
+
},
|
|
896
|
+
};
|
|
840
897
|
},
|
|
841
|
-
};
|
|
898
|
+
});
|
|
842
899
|
});
|
|
843
900
|
if (config.toolMode !== "codex") {
|
|
844
901
|
registerAppTool(server, toolNames.write, {
|
|
@@ -857,53 +914,62 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
857
914
|
...toolWidgetDescriptorMeta(config, "write"),
|
|
858
915
|
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
859
916
|
}, async ({ workspaceId, ...input }) => {
|
|
860
|
-
const startedAt = performance.now();
|
|
861
917
|
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, {
|
|
918
|
+
return runToolWithHooks(hooks, {
|
|
883
919
|
tool: toolNames.write,
|
|
884
|
-
|
|
885
|
-
path: input.path,
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
920
|
+
invocation: workspaceHookInvocation(workspace),
|
|
921
|
+
payload: { path: input.path },
|
|
922
|
+
isFailure: toolResultIsError,
|
|
923
|
+
changedPaths: (result) => toolResultIsError(result) ? [] : [input.path],
|
|
924
|
+
operation: async () => {
|
|
925
|
+
const startedAt = performance.now();
|
|
926
|
+
workspaces.resolvePath(workspace, input.path);
|
|
927
|
+
const response = await writeFileTool(input, {
|
|
928
|
+
cwd: workspace.root,
|
|
929
|
+
root: workspace.root,
|
|
930
|
+
});
|
|
931
|
+
if (response.isError) {
|
|
932
|
+
logFailedToolResponse(config, {
|
|
933
|
+
tool: toolNames.write,
|
|
934
|
+
workspaceId,
|
|
935
|
+
path: input.path,
|
|
936
|
+
}, response.content, startedAt);
|
|
937
|
+
return response;
|
|
938
|
+
}
|
|
939
|
+
const patch = newFilePatch(input.path, input.content);
|
|
940
|
+
const stats = countDiffStats(patch);
|
|
941
|
+
const summary = {
|
|
942
|
+
...stats,
|
|
943
|
+
lines: contentLineCount(input.content),
|
|
944
|
+
characters: input.content.length,
|
|
945
|
+
};
|
|
946
|
+
logToolCall(config, {
|
|
947
|
+
tool: toolNames.write,
|
|
894
948
|
workspaceId,
|
|
895
949
|
path: input.path,
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
950
|
+
success: true,
|
|
951
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
952
|
+
});
|
|
953
|
+
return {
|
|
954
|
+
...response,
|
|
955
|
+
_meta: {
|
|
956
|
+
tool: toolNames.write,
|
|
957
|
+
card: {
|
|
958
|
+
workspaceId,
|
|
959
|
+
path: input.path,
|
|
960
|
+
summary,
|
|
961
|
+
payload: {
|
|
962
|
+
content: response.content,
|
|
963
|
+
patch,
|
|
964
|
+
},
|
|
965
|
+
},
|
|
900
966
|
},
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
967
|
+
structuredContent: {
|
|
968
|
+
result: contentText(response.content),
|
|
969
|
+
},
|
|
970
|
+
};
|
|
905
971
|
},
|
|
906
|
-
};
|
|
972
|
+
});
|
|
907
973
|
});
|
|
908
974
|
registerAppTool(server, toolNames.edit, {
|
|
909
975
|
title: "Edit file",
|
|
@@ -930,54 +996,63 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
930
996
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
931
997
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
932
998
|
}, async ({ workspaceId, ...input }) => {
|
|
933
|
-
const startedAt = performance.now();
|
|
934
999
|
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, {
|
|
1000
|
+
return runToolWithHooks(hooks, {
|
|
956
1001
|
tool: toolNames.edit,
|
|
957
|
-
|
|
958
|
-
path: input.path,
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1002
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1003
|
+
payload: { path: input.path, editCount: input.edits.length },
|
|
1004
|
+
isFailure: toolResultIsError,
|
|
1005
|
+
changedPaths: (result) => toolResultIsError(result) ? [] : [input.path],
|
|
1006
|
+
operation: async () => {
|
|
1007
|
+
const startedAt = performance.now();
|
|
1008
|
+
workspaces.resolvePath(workspace, input.path);
|
|
1009
|
+
const response = await editFileTool(input, {
|
|
1010
|
+
cwd: workspace.root,
|
|
1011
|
+
root: workspace.root,
|
|
1012
|
+
});
|
|
1013
|
+
if (response.isError) {
|
|
1014
|
+
logFailedToolResponse(config, {
|
|
1015
|
+
tool: toolNames.edit,
|
|
1016
|
+
workspaceId,
|
|
1017
|
+
path: input.path,
|
|
1018
|
+
}, response.content, startedAt);
|
|
1019
|
+
return response;
|
|
1020
|
+
}
|
|
1021
|
+
const stats = countDiffStats(response.details?.patch ?? response.details?.diff);
|
|
1022
|
+
const summary = {
|
|
1023
|
+
...stats,
|
|
1024
|
+
editCount: input.edits.length,
|
|
1025
|
+
};
|
|
1026
|
+
const editResultText = `Edited ${input.path} (+${stats.additions} -${stats.removals}).`;
|
|
1027
|
+
const editContent = [textBlock(editResultText)];
|
|
1028
|
+
logToolCall(config, {
|
|
1029
|
+
tool: toolNames.edit,
|
|
967
1030
|
workspaceId,
|
|
968
1031
|
path: input.path,
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1032
|
+
success: true,
|
|
1033
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1034
|
+
});
|
|
1035
|
+
return {
|
|
1036
|
+
content: editContent,
|
|
1037
|
+
_meta: {
|
|
1038
|
+
tool: toolNames.edit,
|
|
1039
|
+
card: {
|
|
1040
|
+
workspaceId,
|
|
1041
|
+
path: input.path,
|
|
1042
|
+
summary,
|
|
1043
|
+
payload: {
|
|
1044
|
+
diff: response.details?.diff,
|
|
1045
|
+
patch: response.details?.patch,
|
|
1046
|
+
},
|
|
1047
|
+
},
|
|
973
1048
|
},
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1049
|
+
structuredContent: {
|
|
1050
|
+
status: "applied",
|
|
1051
|
+
result: contentText(editContent),
|
|
1052
|
+
},
|
|
1053
|
+
};
|
|
979
1054
|
},
|
|
980
|
-
};
|
|
1055
|
+
});
|
|
981
1056
|
});
|
|
982
1057
|
}
|
|
983
1058
|
if (config.toolMode === "codex") {
|
|
@@ -1004,44 +1079,53 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1004
1079
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
1005
1080
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
1006
1081
|
}, async ({ workspaceId, patch }) => {
|
|
1007
|
-
const startedAt = performance.now();
|
|
1008
1082
|
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, {
|
|
1083
|
+
return runToolWithHooks(hooks, {
|
|
1017
1084
|
tool: "apply_patch",
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1085
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1086
|
+
payload: { patchBytes: Buffer.byteLength(patch) },
|
|
1087
|
+
changedPaths: (response) => Array.from(new Set(response.structuredContent.files.flatMap((file) => [file.previousPath, file.path])
|
|
1088
|
+
.filter((path) => Boolean(path)))),
|
|
1089
|
+
operation: async () => {
|
|
1090
|
+
const startedAt = performance.now();
|
|
1091
|
+
const applied = await applyPatch(workspace.root, patch);
|
|
1092
|
+
const paths = applied.files.map((file) => file.path).join(", ");
|
|
1093
|
+
const result = `Applied patch to ${applied.files.length} file(s): ${paths}`;
|
|
1094
|
+
const content = [textBlock(result)];
|
|
1095
|
+
const displayPath = applied.files.length === 1
|
|
1096
|
+
? applied.files[0]?.path
|
|
1097
|
+
: `${applied.files.length} files`;
|
|
1098
|
+
logToolCall(config, {
|
|
1099
|
+
tool: "apply_patch",
|
|
1027
1100
|
workspaceId,
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1101
|
+
success: true,
|
|
1102
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1103
|
+
});
|
|
1104
|
+
return {
|
|
1105
|
+
content,
|
|
1106
|
+
_meta: {
|
|
1107
|
+
tool: "apply_patch",
|
|
1108
|
+
card: {
|
|
1109
|
+
workspaceId,
|
|
1110
|
+
path: displayPath,
|
|
1111
|
+
summary: {
|
|
1112
|
+
files: applied.files.length,
|
|
1113
|
+
additions: applied.additions,
|
|
1114
|
+
removals: applied.removals,
|
|
1115
|
+
},
|
|
1116
|
+
files: applied.files,
|
|
1117
|
+
payload: { patch: applied.patch },
|
|
1118
|
+
},
|
|
1119
|
+
},
|
|
1120
|
+
structuredContent: {
|
|
1121
|
+
result,
|
|
1031
1122
|
additions: applied.additions,
|
|
1032
1123
|
removals: applied.removals,
|
|
1124
|
+
files: applied.files,
|
|
1033
1125
|
},
|
|
1034
|
-
|
|
1035
|
-
payload: { patch: applied.patch },
|
|
1036
|
-
},
|
|
1126
|
+
};
|
|
1037
1127
|
},
|
|
1038
|
-
|
|
1039
|
-
result,
|
|
1040
|
-
additions: applied.additions,
|
|
1041
|
-
removals: applied.removals,
|
|
1042
|
-
files: applied.files,
|
|
1043
|
-
},
|
|
1044
|
-
};
|
|
1128
|
+
});
|
|
1045
1129
|
});
|
|
1046
1130
|
}
|
|
1047
1131
|
if (config.widgets === "changes") {
|
|
@@ -1057,37 +1141,43 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1057
1141
|
...toolWidgetDescriptorMeta(config, "show_changes"),
|
|
1058
1142
|
annotations: { readOnlyHint: true },
|
|
1059
1143
|
}, async ({ workspaceId }) => {
|
|
1060
|
-
const startedAt = performance.now();
|
|
1061
1144
|
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, {
|
|
1145
|
+
return runToolWithHooks(hooks, {
|
|
1069
1146
|
tool: "show_changes",
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1147
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1148
|
+
operation: async () => {
|
|
1149
|
+
const startedAt = performance.now();
|
|
1150
|
+
const review = await reviewCheckpoints.reviewChanges({
|
|
1151
|
+
workspaceId,
|
|
1152
|
+
root: workspace.root,
|
|
1153
|
+
markReviewed: true,
|
|
1154
|
+
});
|
|
1155
|
+
const content = [textBlock(review.result)];
|
|
1156
|
+
logToolCall(config, {
|
|
1157
|
+
tool: "show_changes",
|
|
1079
1158
|
workspaceId,
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1159
|
+
success: true,
|
|
1160
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1161
|
+
});
|
|
1162
|
+
return {
|
|
1163
|
+
content,
|
|
1164
|
+
_meta: {
|
|
1165
|
+
tool: "show_changes",
|
|
1166
|
+
card: {
|
|
1167
|
+
workspaceId,
|
|
1168
|
+
summary: review.summary,
|
|
1169
|
+
files: review.files,
|
|
1170
|
+
payload: {
|
|
1171
|
+
patch: review.patch,
|
|
1172
|
+
},
|
|
1173
|
+
},
|
|
1084
1174
|
},
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1175
|
+
structuredContent: {
|
|
1176
|
+
result: contentText(content),
|
|
1177
|
+
},
|
|
1178
|
+
};
|
|
1089
1179
|
},
|
|
1090
|
-
};
|
|
1180
|
+
});
|
|
1091
1181
|
});
|
|
1092
1182
|
}
|
|
1093
1183
|
if (config.toolMode === "full") {
|
|
@@ -1109,49 +1199,57 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1109
1199
|
...toolWidgetDescriptorMeta(config, "search"),
|
|
1110
1200
|
annotations: { readOnlyHint: true },
|
|
1111
1201
|
}, async ({ workspaceId, ...input }) => {
|
|
1112
|
-
const startedAt = performance.now();
|
|
1113
1202
|
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, {
|
|
1203
|
+
return runToolWithHooks(hooks, {
|
|
1134
1204
|
tool: toolNames.grep,
|
|
1135
|
-
|
|
1136
|
-
path: input.path,
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1205
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1206
|
+
payload: { pattern: input.pattern, path: input.path, include: input.include },
|
|
1207
|
+
isFailure: toolResultIsError,
|
|
1208
|
+
operation: async () => {
|
|
1209
|
+
const startedAt = performance.now();
|
|
1210
|
+
if (input.path)
|
|
1211
|
+
workspaces.resolvePath(workspace, input.path);
|
|
1212
|
+
const response = await grepFilesTool(input, {
|
|
1213
|
+
cwd: workspace.root,
|
|
1214
|
+
root: workspace.root,
|
|
1215
|
+
});
|
|
1216
|
+
if (response.isError) {
|
|
1217
|
+
logFailedToolResponse(config, {
|
|
1218
|
+
tool: toolNames.grep,
|
|
1219
|
+
workspaceId,
|
|
1220
|
+
path: input.path,
|
|
1221
|
+
}, response.content, startedAt);
|
|
1222
|
+
return response;
|
|
1223
|
+
}
|
|
1224
|
+
const summary = {
|
|
1225
|
+
pattern: input.pattern,
|
|
1226
|
+
scope: input.path ?? ".",
|
|
1227
|
+
...textSummary(response.content),
|
|
1228
|
+
};
|
|
1229
|
+
logToolCall(config, {
|
|
1230
|
+
tool: toolNames.grep,
|
|
1145
1231
|
workspaceId,
|
|
1146
1232
|
path: input.path,
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1233
|
+
success: true,
|
|
1234
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1235
|
+
});
|
|
1236
|
+
return {
|
|
1237
|
+
...response,
|
|
1238
|
+
_meta: {
|
|
1239
|
+
tool: toolNames.grep,
|
|
1240
|
+
card: {
|
|
1241
|
+
workspaceId,
|
|
1242
|
+
path: input.path,
|
|
1243
|
+
summary,
|
|
1244
|
+
payload: { content: response.content },
|
|
1245
|
+
},
|
|
1246
|
+
},
|
|
1247
|
+
structuredContent: {
|
|
1248
|
+
result: contentText(response.content),
|
|
1249
|
+
},
|
|
1250
|
+
};
|
|
1153
1251
|
},
|
|
1154
|
-
};
|
|
1252
|
+
});
|
|
1155
1253
|
});
|
|
1156
1254
|
registerAppTool(server, toolNames.glob, {
|
|
1157
1255
|
title: "Glob",
|
|
@@ -1170,49 +1268,57 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1170
1268
|
...toolWidgetDescriptorMeta(config, "search"),
|
|
1171
1269
|
annotations: { readOnlyHint: true },
|
|
1172
1270
|
}, async ({ workspaceId, ...input }) => {
|
|
1173
|
-
const startedAt = performance.now();
|
|
1174
1271
|
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, {
|
|
1272
|
+
return runToolWithHooks(hooks, {
|
|
1195
1273
|
tool: toolNames.glob,
|
|
1196
|
-
|
|
1197
|
-
path: input.path,
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1274
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1275
|
+
payload: { pattern: input.pattern, path: input.path },
|
|
1276
|
+
isFailure: toolResultIsError,
|
|
1277
|
+
operation: async () => {
|
|
1278
|
+
const startedAt = performance.now();
|
|
1279
|
+
if (input.path)
|
|
1280
|
+
workspaces.resolvePath(workspace, input.path);
|
|
1281
|
+
const response = await findFilesTool(input, {
|
|
1282
|
+
cwd: workspace.root,
|
|
1283
|
+
root: workspace.root,
|
|
1284
|
+
});
|
|
1285
|
+
if (response.isError) {
|
|
1286
|
+
logFailedToolResponse(config, {
|
|
1287
|
+
tool: toolNames.glob,
|
|
1288
|
+
workspaceId,
|
|
1289
|
+
path: input.path,
|
|
1290
|
+
}, response.content, startedAt);
|
|
1291
|
+
return response;
|
|
1292
|
+
}
|
|
1293
|
+
const summary = {
|
|
1294
|
+
pattern: input.pattern,
|
|
1295
|
+
scope: input.path ?? ".",
|
|
1296
|
+
...textSummary(response.content),
|
|
1297
|
+
};
|
|
1298
|
+
logToolCall(config, {
|
|
1299
|
+
tool: toolNames.glob,
|
|
1206
1300
|
workspaceId,
|
|
1207
1301
|
path: input.path,
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1302
|
+
success: true,
|
|
1303
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1304
|
+
});
|
|
1305
|
+
return {
|
|
1306
|
+
...response,
|
|
1307
|
+
_meta: {
|
|
1308
|
+
tool: toolNames.glob,
|
|
1309
|
+
card: {
|
|
1310
|
+
workspaceId,
|
|
1311
|
+
path: input.path,
|
|
1312
|
+
summary,
|
|
1313
|
+
payload: { content: response.content },
|
|
1314
|
+
},
|
|
1315
|
+
},
|
|
1316
|
+
structuredContent: {
|
|
1317
|
+
result: contentText(response.content),
|
|
1318
|
+
},
|
|
1319
|
+
};
|
|
1214
1320
|
},
|
|
1215
|
-
};
|
|
1321
|
+
});
|
|
1216
1322
|
});
|
|
1217
1323
|
registerAppTool(server, toolNames.ls, {
|
|
1218
1324
|
title: "Ls",
|
|
@@ -1229,44 +1335,52 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1229
1335
|
...toolWidgetDescriptorMeta(config, "directory"),
|
|
1230
1336
|
annotations: { readOnlyHint: true },
|
|
1231
1337
|
}, async ({ workspaceId, ...input }) => {
|
|
1232
|
-
const startedAt = performance.now();
|
|
1233
1338
|
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, {
|
|
1339
|
+
return runToolWithHooks(hooks, {
|
|
1249
1340
|
tool: toolNames.ls,
|
|
1250
|
-
|
|
1251
|
-
path: input.path,
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1341
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1342
|
+
payload: { path: input.path },
|
|
1343
|
+
isFailure: toolResultIsError,
|
|
1344
|
+
operation: async () => {
|
|
1345
|
+
const startedAt = performance.now();
|
|
1346
|
+
workspaces.resolvePath(workspace, input.path);
|
|
1347
|
+
const response = await listDirectoryTool(input, {
|
|
1348
|
+
cwd: workspace.root,
|
|
1349
|
+
root: workspace.root,
|
|
1350
|
+
});
|
|
1351
|
+
if (response.isError) {
|
|
1352
|
+
logFailedToolResponse(config, {
|
|
1353
|
+
tool: toolNames.ls,
|
|
1354
|
+
workspaceId,
|
|
1355
|
+
path: input.path,
|
|
1356
|
+
}, response.content, startedAt);
|
|
1357
|
+
return response;
|
|
1358
|
+
}
|
|
1359
|
+
const summary = textSummary(response.content);
|
|
1360
|
+
logToolCall(config, {
|
|
1361
|
+
tool: toolNames.ls,
|
|
1260
1362
|
workspaceId,
|
|
1261
1363
|
path: input.path,
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1364
|
+
success: true,
|
|
1365
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1366
|
+
});
|
|
1367
|
+
return {
|
|
1368
|
+
...response,
|
|
1369
|
+
_meta: {
|
|
1370
|
+
tool: toolNames.ls,
|
|
1371
|
+
card: {
|
|
1372
|
+
workspaceId,
|
|
1373
|
+
path: input.path,
|
|
1374
|
+
summary,
|
|
1375
|
+
payload: { content: response.content },
|
|
1376
|
+
},
|
|
1377
|
+
},
|
|
1378
|
+
structuredContent: {
|
|
1379
|
+
result: contentText(response.content),
|
|
1380
|
+
},
|
|
1381
|
+
};
|
|
1268
1382
|
},
|
|
1269
|
-
};
|
|
1383
|
+
});
|
|
1270
1384
|
});
|
|
1271
1385
|
}
|
|
1272
1386
|
if (config.toolMode !== "codex") {
|
|
@@ -1295,61 +1409,74 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1295
1409
|
...toolWidgetDescriptorMeta(config, "shell"),
|
|
1296
1410
|
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
1297
1411
|
}, async ({ workspaceId, workingDirectory, ...input }) => {
|
|
1298
|
-
const startedAt = performance.now();
|
|
1299
1412
|
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, {
|
|
1413
|
+
return runToolWithHooks(hooks, {
|
|
1321
1414
|
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
|
-
},
|
|
1415
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1416
|
+
payload: {
|
|
1417
|
+
command: input.command,
|
|
1418
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1419
|
+
timeoutSeconds: input.timeout,
|
|
1339
1420
|
},
|
|
1340
|
-
|
|
1341
|
-
|
|
1421
|
+
isFailure: toolResultIsError,
|
|
1422
|
+
operation: async () => {
|
|
1423
|
+
const startedAt = performance.now();
|
|
1424
|
+
const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
|
|
1425
|
+
const response = await runShellTool(input, {
|
|
1426
|
+
cwd,
|
|
1427
|
+
root: workspace.root,
|
|
1428
|
+
});
|
|
1429
|
+
if (response.isError) {
|
|
1430
|
+
logFailedToolResponse(config, {
|
|
1431
|
+
tool: toolNames.shell,
|
|
1432
|
+
workspaceId,
|
|
1433
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1434
|
+
command: input.command,
|
|
1435
|
+
commandLength: input.command.length,
|
|
1436
|
+
}, response.content, startedAt);
|
|
1437
|
+
return response;
|
|
1438
|
+
}
|
|
1439
|
+
const summary = {
|
|
1440
|
+
command: input.command,
|
|
1441
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1442
|
+
...textSummary(response.content),
|
|
1443
|
+
};
|
|
1444
|
+
logToolCall(config, {
|
|
1445
|
+
tool: toolNames.shell,
|
|
1446
|
+
workspaceId,
|
|
1447
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1448
|
+
command: input.command,
|
|
1449
|
+
commandLength: input.command.length,
|
|
1450
|
+
success: true,
|
|
1451
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1452
|
+
});
|
|
1453
|
+
return {
|
|
1454
|
+
...response,
|
|
1455
|
+
_meta: {
|
|
1456
|
+
tool: toolNames.shell,
|
|
1457
|
+
card: {
|
|
1458
|
+
workspaceId,
|
|
1459
|
+
path: workingDirectory,
|
|
1460
|
+
summary,
|
|
1461
|
+
payload: { content: response.content },
|
|
1462
|
+
},
|
|
1463
|
+
},
|
|
1464
|
+
structuredContent: {
|
|
1465
|
+
result: contentText(response.content),
|
|
1466
|
+
},
|
|
1467
|
+
};
|
|
1342
1468
|
},
|
|
1343
|
-
};
|
|
1469
|
+
});
|
|
1344
1470
|
});
|
|
1345
1471
|
}
|
|
1346
1472
|
if (config.toolMode === "codex") {
|
|
1347
|
-
registerCodexProcessTools(server, config, workspaces, processSessions);
|
|
1473
|
+
registerCodexProcessTools(server, config, workspaces, processSessions, hooks);
|
|
1348
1474
|
}
|
|
1349
1475
|
if (config.artifactsEnabled && isArtifactDownloadSupportedPlatform()) {
|
|
1350
1476
|
registerArtifactTools(server, {
|
|
1351
1477
|
config,
|
|
1352
1478
|
workspaces,
|
|
1479
|
+
hooks,
|
|
1353
1480
|
incomingArtifactAdapters,
|
|
1354
1481
|
});
|
|
1355
1482
|
}
|