@akira-tl/forgerelay 0.2.0 → 0.2.2

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.
Files changed (96) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/apply-patch.js +21 -6
  3. package/dist/artifact-tools.js +9 -4
  4. package/dist/config.js +8 -6
  5. package/dist/file-mutations.js +50 -0
  6. package/dist/hooks.js +4 -1
  7. package/dist/logger.js +157 -15
  8. package/dist/mcp/server-instructions.js +13 -9
  9. package/dist/pi-tools.js +14 -13
  10. package/dist/roots.js +30 -1
  11. package/dist/server.js +210 -54
  12. package/dist/ui/.vite/manifest.json +280 -280
  13. package/dist/ui/assets/{angular-html-CovppXt5.js → angular-html-CQmCUfkv.js} +1 -1
  14. package/dist/ui/assets/{angular-ts-0Y9l6YXu.js → angular-ts-BSbfxpMS.js} +1 -1
  15. package/dist/ui/assets/{apl-BmBhiklA.js → apl-CWCaH-E3.js} +1 -1
  16. package/dist/ui/assets/{astro-BAmqcqyV.js → astro-R_p4G0xA.js} +1 -1
  17. package/dist/ui/assets/{blade-BjFcmRJo.js → blade-irHensDY.js} +1 -1
  18. package/dist/ui/assets/{c-CaOJn4x0.js → c-_XEQUoq6.js} +1 -1
  19. package/dist/ui/assets/{cobol-DR4zWFMt.js → cobol-Css6VQjJ.js} +1 -1
  20. package/dist/ui/assets/{coffee-ReGSThmM.js → coffee-CpXUkyDm.js} +1 -1
  21. package/dist/ui/assets/{cpp-CnlQh6MO.js → cpp-BQHsPWwI.js} +1 -1
  22. package/dist/ui/assets/{crystal-BrpPbH1U.js → crystal-Cbv8J9Fm.js} +1 -1
  23. package/dist/ui/assets/{css-DRBEvyPU.js → css-D8DkrS3U.js} +1 -1
  24. package/dist/ui/assets/{edge-cfwpdvl9.js → edge-bBL_XDAG.js} +1 -1
  25. package/dist/ui/assets/{elixir-7owrJxb6.js → elixir-CQjsM9sA.js} +1 -1
  26. package/dist/ui/assets/{elm-udiOjODk.js → elm-DLFl-jEZ.js} +1 -1
  27. package/dist/ui/assets/{erb-8ctXHMkS.js → erb-BJjsdB_Q.js} +1 -1
  28. package/dist/ui/assets/{git-rebase-P0V_qK1w.js → git-rebase-CqP5A2s0.js} +1 -1
  29. package/dist/ui/assets/{glimmer-js-CZ14ED7U.js → glimmer-js-B5Hh_7xe.js} +1 -1
  30. package/dist/ui/assets/{glimmer-ts-DtBSFP-o.js → glimmer-ts-oaeSAC8s.js} +1 -1
  31. package/dist/ui/assets/{glsl-BX2BkZJK.js → glsl-BylkOpcF.js} +1 -1
  32. package/dist/ui/assets/{graphql-Dj_cPv4w.js → graphql-DsVRvP6S.js} +1 -1
  33. package/dist/ui/assets/{hack-DGRkd8xn.js → hack-D4N5abT_.js} +1 -1
  34. package/dist/ui/assets/{haml-BAPSvujW.js → haml-CIAJQIox.js} +1 -1
  35. package/dist/ui/assets/{handlebars-PYksdULG.js → handlebars-DDEZyNXR.js} +1 -1
  36. package/dist/ui/assets/{heavy-payload-BjE0LSUi.js → heavy-payload-DafM8yjB.js} +1 -1
  37. package/dist/ui/assets/{html-derivative-C6q86ZrS.js → html-derivative-CZ91Y2d5.js} +1 -1
  38. package/dist/ui/assets/{html-DloAPGZ1.js → html-zDy-3lvy.js} +1 -1
  39. package/dist/ui/assets/{http-fPvYDvHA.js → http-ezaodZRf.js} +1 -1
  40. package/dist/ui/assets/{hurl-Dbir4WvE.js → hurl-BmL0o7TO.js} +1 -1
  41. package/dist/ui/assets/{java-DvkqXg3z.js → java-Q2C0vwbH.js} +1 -1
  42. package/dist/ui/assets/{javascript-TONTV6Ty.js → javascript-UDhBkHE3.js} +1 -1
  43. package/dist/ui/assets/{jinja-OM1F5Gct.js → jinja-BWRskcYS.js} +1 -1
  44. package/dist/ui/assets/{jison-CBttugts.js → jison-B18BY9Ds.js} +1 -1
  45. package/dist/ui/assets/{json-Db5oY-7J.js → json-DNj7MpRr.js} +1 -1
  46. package/dist/ui/assets/{jsx-DKeJCgsc.js → jsx-CVpHJ6cr.js} +1 -1
  47. package/dist/ui/assets/{julia-NlIPhXaL.js → julia-CJBNBS9c.js} +1 -1
  48. package/dist/ui/assets/{just-BXBJB-mX.js → just-Bmu2JCQT.js} +1 -1
  49. package/dist/ui/assets/{latex-CGK-6yDy.js → latex-BB7F93KY.js} +1 -1
  50. package/dist/ui/assets/{liquid-D7waQ0MY.js → liquid-Btnomc6E.js} +1 -1
  51. package/dist/ui/assets/{lua-C_kkt4h1.js → lua-D0FYT9Vo.js} +1 -1
  52. package/dist/ui/assets/{marko-BokXuCts.js → marko-MHLw0ZlK.js} +1 -1
  53. package/dist/ui/assets/{mdc-Bk0nnUJA.js → mdc-CSr-suGy.js} +1 -1
  54. package/dist/ui/assets/{nginx-jM6NF9TV.js → nginx-ur1l6E-g.js} +1 -1
  55. package/dist/ui/assets/{nim-hYsL-WKb.js → nim-B2k0IugA.js} +1 -1
  56. package/dist/ui/assets/{perl-DBI_cUHJ.js → perl-BqOHxFDn.js} +1 -1
  57. package/dist/ui/assets/{php-BX_aQhFl.js → php-DzPK6O1_.js} +1 -1
  58. package/dist/ui/assets/{pug-DwFAYy1e.js → pug-D169OZkr.js} +1 -1
  59. package/dist/ui/assets/{qml-eLSwQUKt.js → qml-BtemJTC4.js} +1 -1
  60. package/dist/ui/assets/{r-BMnQsMLf.js → r-BGVHu0bd.js} +1 -1
  61. package/dist/ui/assets/{razor-CgciiMyw.js → razor-Diisjg1d.js} +1 -1
  62. package/dist/ui/assets/{regexp-ZrURNrn5.js → regexp-B2F5vMZ6.js} +1 -1
  63. package/dist/ui/assets/{review-payload-HGkpJjjm.js → review-payload-B-3I3Oj_.js} +1 -1
  64. package/dist/ui/assets/{rst-EoAOr3M6.js → rst-BxpeZwuT.js} +1 -1
  65. package/dist/ui/assets/{ruby-Dqx5_Rqw.js → ruby-DrCq4riN.js} +1 -1
  66. package/dist/ui/assets/{sas-Bf6xPLSX.js → sas-CI_2W-1n.js} +1 -1
  67. package/dist/ui/assets/{scrollbar-DX4Ujotb.js → scrollbar-DHsDF5w-.js} +3 -3
  68. package/dist/ui/assets/{scss-DDpW3yTd.js → scss-BxtyHZ1p.js} +1 -1
  69. package/dist/ui/assets/{shellscript-DayAQ-W9.js → shellscript-BY_YYrHT.js} +1 -1
  70. package/dist/ui/assets/{shellsession-jPr_lSnA.js → shellsession-C6uBrLmA.js} +1 -1
  71. package/dist/ui/assets/{soy-X9adks8G.js → soy-BHM9gdPh.js} +1 -1
  72. package/dist/ui/assets/{sql-Bmt09AIR.js → sql-XmhfEGz_.js} +1 -1
  73. package/dist/ui/assets/{stata-DVkNpH8r.js → stata-BbHY7NEo.js} +1 -1
  74. package/dist/ui/assets/{surrealql-eEXzMb8j.js → surrealql-CD49DZ3E.js} +1 -1
  75. package/dist/ui/assets/{svelte-CFoBxZF3.js → svelte-CbYsEYJY.js} +1 -1
  76. package/dist/ui/assets/{templ-Da_324JF.js → templ-B5JoUxEB.js} +1 -1
  77. package/dist/ui/assets/{tex-HAzlmKJ0.js → tex-DZcOfTZK.js} +1 -1
  78. package/dist/ui/assets/{ts-tags-7iLrNH8p.js → ts-tags-BeU-xqc0.js} +1 -1
  79. package/dist/ui/assets/{tsx-gZaXf8PJ.js → tsx-V7W1xGl3.js} +1 -1
  80. package/dist/ui/assets/{twig-CLpQ7p1N.js → twig-MIaJo5BU.js} +1 -1
  81. package/dist/ui/assets/{typescript-B2WWCRI2.js → typescript-C0WESuVc.js} +1 -1
  82. package/dist/ui/assets/{vue-No_8Jygd.js → vue-eJHwc9ra.js} +1 -1
  83. package/dist/ui/assets/{vue-html-kHJg98Kk.js → vue-html-CLmBujTU.js} +1 -1
  84. package/dist/ui/assets/{vue-vine-Df4cEYaN.js → vue-vine-BP5kjIGb.js} +1 -1
  85. package/dist/ui/assets/{workspace-app-C_wsC6VL.js → workspace-app-C1qR5cAY.js} +3 -3
  86. package/dist/ui/assets/{xml-DUZ05dpG.js → xml-j7pmyP48.js} +1 -1
  87. package/dist/ui/assets/{xsl-Bwp3w2OF.js → xsl-5VFHbg6o.js} +1 -1
  88. package/dist/ui/assets/{yaml-IA_-TEM1.js → yaml-DxFAt-KU.js} +1 -1
  89. package/dist/ui/workspace-app.html +1 -1
  90. package/dist/workspaces.js +19 -6
  91. package/docs/chatgpt-coding-workflow.md +4 -1
  92. package/docs/configuration.md +16 -8
  93. package/docs/debugging.md +6 -5
  94. package/docs/security.md +18 -7
  95. package/package.json +2 -2
  96. package/scripts/debug/accept.mjs +65 -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";
@@ -13,12 +14,13 @@ import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE, } from "@mode
13
14
  import express from "express";
14
15
  import * as z from "zod/v4";
15
16
  import { applyPatch } from "./apply-patch.js";
17
+ import { deletePath, renamePath } from "./file-mutations.js";
16
18
  import { isArtifactDownloadSupportedPlatform, registerArtifactTools, } from "./artifact-tools.js";
17
19
  import { loadConfig } from "./config.js";
18
20
  import { attachHookReports, HookRunner, runToolWithHooks } from "./hooks.js";
19
21
  import { buildServerInstructions, buildToolDescriptions, toolNames, } from "./mcp/server-instructions.js";
20
22
  import { createOpenAIIncomingArtifactAdapter, } from "./incoming-artifacts.js";
21
- import { logEvent, requestIp, requestPath, commandPreview, sessionIdPrefix, } from "./logger.js";
23
+ import { logEvent, requestIp, requestPath, commandPreview, sessionIdPrefix, workspaceLogLabel, } from "./logger.js";
22
24
  import { editFileTool, findFilesTool, grepFilesTool, listDirectoryTool, readFileTool, runShellTool, writeFileTool, } from "./pi-tools.js";
23
25
  import { SingleUserOAuthProvider } from "./oauth-provider.js";
24
26
  import { McpSessionRegistry, } from "./mcp-sessions.js";
@@ -78,6 +80,13 @@ function toolWidgetDescriptorMeta(config, kind) {
78
80
  },
79
81
  };
80
82
  }
83
+ function workspaceLogContext(workspace, sessionId) {
84
+ return {
85
+ workspaceId: workspace.id,
86
+ workspace: workspaceLogLabel(workspace.root, workspace.id),
87
+ session: sessionIdPrefix(sessionId),
88
+ };
89
+ }
81
90
  function formatVisibleAgent(agent) {
82
91
  const model = agent.model ? `, model ${agent.model}` : "";
83
92
  const thinking = agent.thinking ? `, thinking ${agent.thinking}` : "";
@@ -393,7 +402,7 @@ function registerCodexProcessTools(server, config, workspaces, processSessions,
393
402
  outputSchema: processOutputSchema(),
394
403
  ...toolWidgetDescriptorMeta(config, "shell"),
395
404
  annotations: SHELL_TOOL_ANNOTATIONS,
396
- }, async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, maxOutputTokens }) => {
405
+ }, async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, maxOutputTokens }, extra) => {
397
406
  const workspace = workspaces.getWorkspace(workspaceId);
398
407
  return runToolWithHooks(hooks, {
399
408
  tool: "exec_command",
@@ -415,11 +424,14 @@ function registerCodexProcessTools(server, config, workspaces, processSessions,
415
424
  });
416
425
  logToolCall(config, {
417
426
  tool: "exec_command",
418
- workspaceId,
427
+ ...workspaceLogContext(workspace, extra.sessionId),
419
428
  workingDirectory: workingDirectory ?? ".",
420
429
  command: cmd,
421
430
  commandLength: cmd.length,
422
- success: true,
431
+ exitCode: snapshot.exitCode,
432
+ running: snapshot.running,
433
+ processSessionId: snapshot.sessionId,
434
+ success: snapshot.running || snapshot.exitCode === 0,
423
435
  durationMs: Math.round(performance.now() - startedAt),
424
436
  });
425
437
  return processToolResponse("exec_command", workspaceId, snapshot, {
@@ -459,7 +471,7 @@ function registerCodexProcessTools(server, config, workspaces, processSessions,
459
471
  outputSchema: processOutputSchema(),
460
472
  ...toolWidgetDescriptorMeta(config, "shell"),
461
473
  annotations: SHELL_TOOL_ANNOTATIONS,
462
- }, async ({ workspaceId, sessionId, chars, columns, rows, yieldTimeMs, maxOutputTokens }) => {
474
+ }, async ({ workspaceId, sessionId, chars, columns, rows, yieldTimeMs, maxOutputTokens }, extra) => {
463
475
  const workspace = workspaces.getWorkspace(workspaceId);
464
476
  return runToolWithHooks(hooks, {
465
477
  tool: "write_stdin",
@@ -483,8 +495,11 @@ function registerCodexProcessTools(server, config, workspaces, processSessions,
483
495
  });
484
496
  logToolCall(config, {
485
497
  tool: "write_stdin",
486
- workspaceId,
487
- success: true,
498
+ ...workspaceLogContext(workspace, extra.sessionId),
499
+ exitCode: snapshot.exitCode,
500
+ running: snapshot.running,
501
+ processSessionId: snapshot.sessionId,
502
+ success: snapshot.running || snapshot.exitCode === 0,
488
503
  durationMs: Math.round(performance.now() - startedAt),
489
504
  });
490
505
  return processToolResponse("write_stdin", workspaceId, snapshot, {
@@ -597,7 +612,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
597
612
  idempotentHint: false,
598
613
  openWorldHint: false,
599
614
  },
600
- }, async ({ path, mode, baseRef, newWorktree }, { _meta }) => {
615
+ }, async ({ path, mode, baseRef, newWorktree }, { _meta, sessionId }) => {
601
616
  const startedAt = performance.now();
602
617
  const { workspace, agentsFiles, availableAgentsFiles, hookReports, workspaceReused, includeBootstrapContext, } = await workspaces.openWorkspace({ path, mode, baseRef, newWorktree }, { conversationScopeId: openAiConversationScopeId(_meta) });
603
618
  const knownWorktrees = await workspaces.listKnownWorktrees(workspace);
@@ -692,7 +707,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
692
707
  ];
693
708
  logToolCall(config, {
694
709
  tool: "open_workspace",
695
- workspaceId: workspace.id,
710
+ ...workspaceLogContext(workspace, sessionId),
696
711
  path: workspace.root,
697
712
  success: true,
698
713
  durationMs: Math.round(performance.now() - startedAt),
@@ -772,7 +787,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
772
787
  }),
773
788
  _meta: {},
774
789
  annotations: WRITE_TOOL_ANNOTATIONS,
775
- }, async ({ workspaceId, commitMessage }) => {
790
+ }, async ({ workspaceId, commitMessage }, extra) => {
776
791
  const workspace = workspaces.getWorkspace(workspaceId);
777
792
  return runToolWithHooks(hooks, {
778
793
  tool: toolNames.closeWorktree,
@@ -793,7 +808,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
793
808
  ].join("\n");
794
809
  logToolCall(config, {
795
810
  tool: toolNames.closeWorktree,
796
- workspaceId,
811
+ ...workspaceLogContext(workspace, extra.sessionId),
797
812
  path: closed.sourceRoot,
798
813
  success: true,
799
814
  durationMs: Math.round(performance.now() - startedAt),
@@ -825,8 +840,8 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
825
840
  path: z
826
841
  .string()
827
842
  .describe(config.skillsEnabled
828
- ? "File path to read, relative to the workspace root. May also be an advertised skill path from open_workspace skills."
829
- : "File path to read, relative to the workspace root."),
843
+ ? "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."
844
+ : "File path to read, relative to the workspace root or absolute inside the OS temp directory."),
830
845
  offset: z
831
846
  .number()
832
847
  .int()
@@ -843,7 +858,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
843
858
  outputSchema: resultOutputSchema(),
844
859
  ...toolWidgetDescriptorMeta(config, "read"),
845
860
  annotations: { readOnlyHint: true },
846
- }, async ({ workspaceId, ...input }) => {
861
+ }, async ({ workspaceId, ...input }, extra) => {
847
862
  const workspace = workspaces.getWorkspace(workspaceId);
848
863
  return runToolWithHooks(hooks, {
849
864
  tool: toolNames.read,
@@ -861,7 +876,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
861
876
  if (response.isError) {
862
877
  logFailedToolResponse(config, {
863
878
  tool: toolNames.read,
864
- workspaceId,
879
+ ...workspaceLogContext(workspace, extra.sessionId),
865
880
  path: input.path,
866
881
  }, response.content, startedAt);
867
882
  return response;
@@ -874,7 +889,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
874
889
  };
875
890
  logToolCall(config, {
876
891
  tool: toolNames.read,
877
- workspaceId,
892
+ ...workspaceLogContext(workspace, extra.sessionId),
878
893
  path: input.path,
879
894
  success: true,
880
895
  durationMs: Math.round(performance.now() - startedAt),
@@ -907,13 +922,13 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
907
922
  .describe("Workspace identifier returned by open_workspace."),
908
923
  path: z
909
924
  .string()
910
- .describe("File path to write, relative to the workspace root."),
925
+ .describe("File path to write, relative to the workspace root or absolute inside the OS temp directory."),
911
926
  content: z.string().describe("Complete new file content."),
912
927
  },
913
928
  outputSchema: resultOutputSchema(),
914
929
  ...toolWidgetDescriptorMeta(config, "write"),
915
930
  annotations: WRITE_TOOL_ANNOTATIONS,
916
- }, async ({ workspaceId, ...input }) => {
931
+ }, async ({ workspaceId, ...input }, extra) => {
917
932
  const workspace = workspaces.getWorkspace(workspaceId);
918
933
  return runToolWithHooks(hooks, {
919
934
  tool: toolNames.write,
@@ -923,15 +938,15 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
923
938
  changedPaths: (result) => toolResultIsError(result) ? [] : [input.path],
924
939
  operation: async () => {
925
940
  const startedAt = performance.now();
926
- workspaces.resolvePath(workspace, input.path);
927
941
  const response = await writeFileTool(input, {
928
942
  cwd: workspace.root,
929
943
  root: workspace.root,
944
+ fileRoots: workspaces.fileToolRoots(workspace),
930
945
  });
931
946
  if (response.isError) {
932
947
  logFailedToolResponse(config, {
933
948
  tool: toolNames.write,
934
- workspaceId,
949
+ ...workspaceLogContext(workspace, extra.sessionId),
935
950
  path: input.path,
936
951
  }, response.content, startedAt);
937
952
  return response;
@@ -945,7 +960,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
945
960
  };
946
961
  logToolCall(config, {
947
962
  tool: toolNames.write,
948
- workspaceId,
963
+ ...workspaceLogContext(workspace, extra.sessionId),
949
964
  path: input.path,
950
965
  success: true,
951
966
  durationMs: Math.round(performance.now() - startedAt),
@@ -980,7 +995,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
980
995
  .describe("Workspace identifier returned by open_workspace."),
981
996
  path: z
982
997
  .string()
983
- .describe("File path to edit, relative to the workspace root."),
998
+ .describe("File path to edit, relative to the workspace root or absolute inside the OS temp directory."),
984
999
  edits: z
985
1000
  .array(z.object({
986
1001
  oldText: z
@@ -995,7 +1010,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
995
1010
  }),
996
1011
  ...toolWidgetDescriptorMeta(config, "edit"),
997
1012
  annotations: EDIT_TOOL_ANNOTATIONS,
998
- }, async ({ workspaceId, ...input }) => {
1013
+ }, async ({ workspaceId, ...input }, extra) => {
999
1014
  const workspace = workspaces.getWorkspace(workspaceId);
1000
1015
  return runToolWithHooks(hooks, {
1001
1016
  tool: toolNames.edit,
@@ -1005,15 +1020,15 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1005
1020
  changedPaths: (result) => toolResultIsError(result) ? [] : [input.path],
1006
1021
  operation: async () => {
1007
1022
  const startedAt = performance.now();
1008
- workspaces.resolvePath(workspace, input.path);
1009
1023
  const response = await editFileTool(input, {
1010
1024
  cwd: workspace.root,
1011
1025
  root: workspace.root,
1026
+ fileRoots: workspaces.fileToolRoots(workspace),
1012
1027
  });
1013
1028
  if (response.isError) {
1014
1029
  logFailedToolResponse(config, {
1015
1030
  tool: toolNames.edit,
1016
- workspaceId,
1031
+ ...workspaceLogContext(workspace, extra.sessionId),
1017
1032
  path: input.path,
1018
1033
  }, response.content, startedAt);
1019
1034
  return response;
@@ -1027,7 +1042,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1027
1042
  const editContent = [textBlock(editResultText)];
1028
1043
  logToolCall(config, {
1029
1044
  tool: toolNames.edit,
1030
- workspaceId,
1045
+ ...workspaceLogContext(workspace, extra.sessionId),
1031
1046
  path: input.path,
1032
1047
  success: true,
1033
1048
  durationMs: Math.round(performance.now() - startedAt),
@@ -1055,6 +1070,148 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1055
1070
  });
1056
1071
  });
1057
1072
  }
1073
+ registerAppTool(server, toolNames.rename, {
1074
+ title: "Rename path",
1075
+ description: toolDescriptions.rename,
1076
+ inputSchema: {
1077
+ workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
1078
+ path: z.string().describe("Source file or directory path relative to the workspace root, or absolute inside the OS temp directory."),
1079
+ newPath: z.string().describe("Destination path relative to the workspace root, or absolute inside the OS temp directory. The destination must not already exist."),
1080
+ },
1081
+ outputSchema: resultOutputSchema({
1082
+ status: z.literal("renamed"),
1083
+ path: z.string(),
1084
+ newPath: z.string(),
1085
+ }),
1086
+ ...toolWidgetDescriptorMeta(config, "edit"),
1087
+ annotations: EDIT_TOOL_ANNOTATIONS,
1088
+ }, async ({ workspaceId, path, newPath }, extra) => {
1089
+ const workspace = workspaces.getWorkspace(workspaceId);
1090
+ return runToolWithHooks(hooks, {
1091
+ tool: toolNames.rename,
1092
+ invocation: workspaceHookInvocation(workspace),
1093
+ payload: { path, newPath, paths: [path, newPath] },
1094
+ changedPaths: () => [path, newPath],
1095
+ operation: async () => {
1096
+ const startedAt = performance.now();
1097
+ try {
1098
+ await renamePath({ path, newPath }, {
1099
+ cwd: workspace.root,
1100
+ allowedRoots: workspaces.fileToolRoots(workspace),
1101
+ });
1102
+ const result = `Renamed ${path} to ${newPath}.`;
1103
+ const content = [textBlock(result)];
1104
+ logToolCall(config, {
1105
+ tool: toolNames.rename,
1106
+ ...workspaceLogContext(workspace, extra.sessionId),
1107
+ path: `${path} -> ${newPath}`,
1108
+ success: true,
1109
+ durationMs: Math.round(performance.now() - startedAt),
1110
+ });
1111
+ return {
1112
+ content,
1113
+ _meta: {
1114
+ tool: toolNames.rename,
1115
+ card: {
1116
+ workspaceId,
1117
+ path: newPath,
1118
+ summary: { previousPath: path },
1119
+ payload: { content },
1120
+ },
1121
+ },
1122
+ structuredContent: {
1123
+ result,
1124
+ status: "renamed",
1125
+ path,
1126
+ newPath,
1127
+ },
1128
+ };
1129
+ }
1130
+ catch (error) {
1131
+ logToolCall(config, {
1132
+ tool: toolNames.rename,
1133
+ ...workspaceLogContext(workspace, extra.sessionId),
1134
+ path: `${path} -> ${newPath}`,
1135
+ success: false,
1136
+ durationMs: Math.round(performance.now() - startedAt),
1137
+ error: error instanceof Error ? error.message : String(error),
1138
+ });
1139
+ throw error;
1140
+ }
1141
+ },
1142
+ });
1143
+ });
1144
+ registerAppTool(server, toolNames.delete, {
1145
+ title: "Delete path",
1146
+ description: toolDescriptions.delete,
1147
+ inputSchema: {
1148
+ workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
1149
+ path: z.string().describe("File or directory path relative to the workspace root, or absolute inside the OS temp directory."),
1150
+ recursive: z.boolean().optional().describe("Delete a non-empty directory tree. Defaults to false."),
1151
+ },
1152
+ outputSchema: resultOutputSchema({
1153
+ status: z.literal("deleted"),
1154
+ path: z.string(),
1155
+ recursive: z.boolean(),
1156
+ }),
1157
+ ...toolWidgetDescriptorMeta(config, "edit"),
1158
+ annotations: EDIT_TOOL_ANNOTATIONS,
1159
+ }, async ({ workspaceId, path, recursive }, extra) => {
1160
+ const workspace = workspaces.getWorkspace(workspaceId);
1161
+ return runToolWithHooks(hooks, {
1162
+ tool: toolNames.delete,
1163
+ invocation: workspaceHookInvocation(workspace),
1164
+ payload: { path, recursive: recursive ?? false },
1165
+ changedPaths: () => [path],
1166
+ operation: async () => {
1167
+ const startedAt = performance.now();
1168
+ try {
1169
+ const deleted = await deletePath({ path, recursive }, {
1170
+ cwd: workspace.root,
1171
+ allowedRoots: workspaces.fileToolRoots(workspace),
1172
+ });
1173
+ const result = `Deleted ${path}${deleted.recursive ? " recursively" : ""}.`;
1174
+ const content = [textBlock(result)];
1175
+ logToolCall(config, {
1176
+ tool: toolNames.delete,
1177
+ ...workspaceLogContext(workspace, extra.sessionId),
1178
+ path,
1179
+ success: true,
1180
+ durationMs: Math.round(performance.now() - startedAt),
1181
+ });
1182
+ return {
1183
+ content,
1184
+ _meta: {
1185
+ tool: toolNames.delete,
1186
+ card: {
1187
+ workspaceId,
1188
+ path,
1189
+ summary: { recursive: deleted.recursive },
1190
+ payload: { content },
1191
+ },
1192
+ },
1193
+ structuredContent: {
1194
+ result,
1195
+ status: "deleted",
1196
+ path,
1197
+ recursive: deleted.recursive,
1198
+ },
1199
+ };
1200
+ }
1201
+ catch (error) {
1202
+ logToolCall(config, {
1203
+ tool: toolNames.delete,
1204
+ ...workspaceLogContext(workspace, extra.sessionId),
1205
+ path,
1206
+ success: false,
1207
+ durationMs: Math.round(performance.now() - startedAt),
1208
+ error: error instanceof Error ? error.message : String(error),
1209
+ });
1210
+ throw error;
1211
+ }
1212
+ },
1213
+ });
1214
+ });
1058
1215
  if (config.toolMode === "codex") {
1059
1216
  registerAppTool(server, "apply_patch", {
1060
1217
  title: "Apply patch",
@@ -1078,7 +1235,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1078
1235
  }),
1079
1236
  ...toolWidgetDescriptorMeta(config, "edit"),
1080
1237
  annotations: EDIT_TOOL_ANNOTATIONS,
1081
- }, async ({ workspaceId, patch }) => {
1238
+ }, async ({ workspaceId, patch }, extra) => {
1082
1239
  const workspace = workspaces.getWorkspace(workspaceId);
1083
1240
  return runToolWithHooks(hooks, {
1084
1241
  tool: "apply_patch",
@@ -1088,7 +1245,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1088
1245
  .filter((path) => Boolean(path)))),
1089
1246
  operation: async () => {
1090
1247
  const startedAt = performance.now();
1091
- const applied = await applyPatch(workspace.root, patch);
1248
+ const applied = await applyPatch(workspace.root, patch, [tmpdir()]);
1092
1249
  const paths = applied.files.map((file) => file.path).join(", ");
1093
1250
  const result = `Applied patch to ${applied.files.length} file(s): ${paths}`;
1094
1251
  const content = [textBlock(result)];
@@ -1097,7 +1254,8 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1097
1254
  : `${applied.files.length} files`;
1098
1255
  logToolCall(config, {
1099
1256
  tool: "apply_patch",
1100
- workspaceId,
1257
+ ...workspaceLogContext(workspace, extra.sessionId),
1258
+ path: displayPath,
1101
1259
  success: true,
1102
1260
  durationMs: Math.round(performance.now() - startedAt),
1103
1261
  });
@@ -1140,7 +1298,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1140
1298
  outputSchema: resultOutputSchema(),
1141
1299
  ...toolWidgetDescriptorMeta(config, "show_changes"),
1142
1300
  annotations: { readOnlyHint: true },
1143
- }, async ({ workspaceId }) => {
1301
+ }, async ({ workspaceId }, extra) => {
1144
1302
  const workspace = workspaces.getWorkspace(workspaceId);
1145
1303
  return runToolWithHooks(hooks, {
1146
1304
  tool: "show_changes",
@@ -1155,7 +1313,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1155
1313
  const content = [textBlock(review.result)];
1156
1314
  logToolCall(config, {
1157
1315
  tool: "show_changes",
1158
- workspaceId,
1316
+ ...workspaceLogContext(workspace, extra.sessionId),
1159
1317
  success: true,
1160
1318
  durationMs: Math.round(performance.now() - startedAt),
1161
1319
  });
@@ -1183,7 +1341,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1183
1341
  if (config.toolMode === "full") {
1184
1342
  registerAppTool(server, toolNames.grep, {
1185
1343
  title: "Grep",
1186
- 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.",
1344
+ 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.",
1187
1345
  inputSchema: {
1188
1346
  workspaceId: z
1189
1347
  .string()
@@ -1192,13 +1350,13 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1192
1350
  path: z
1193
1351
  .string()
1194
1352
  .optional()
1195
- .describe("Optional path or glob scope relative to the workspace root."),
1353
+ .describe("Optional path or glob scope relative to the workspace root, or an absolute path inside the OS temp directory."),
1196
1354
  include: z.string().optional().describe("Optional include glob."),
1197
1355
  },
1198
1356
  outputSchema: resultOutputSchema(),
1199
1357
  ...toolWidgetDescriptorMeta(config, "search"),
1200
1358
  annotations: { readOnlyHint: true },
1201
- }, async ({ workspaceId, ...input }) => {
1359
+ }, async ({ workspaceId, ...input }, extra) => {
1202
1360
  const workspace = workspaces.getWorkspace(workspaceId);
1203
1361
  return runToolWithHooks(hooks, {
1204
1362
  tool: toolNames.grep,
@@ -1207,16 +1365,15 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1207
1365
  isFailure: toolResultIsError,
1208
1366
  operation: async () => {
1209
1367
  const startedAt = performance.now();
1210
- if (input.path)
1211
- workspaces.resolvePath(workspace, input.path);
1212
1368
  const response = await grepFilesTool(input, {
1213
1369
  cwd: workspace.root,
1214
1370
  root: workspace.root,
1371
+ fileRoots: workspaces.fileToolRoots(workspace),
1215
1372
  });
1216
1373
  if (response.isError) {
1217
1374
  logFailedToolResponse(config, {
1218
1375
  tool: toolNames.grep,
1219
- workspaceId,
1376
+ ...workspaceLogContext(workspace, extra.sessionId),
1220
1377
  path: input.path,
1221
1378
  }, response.content, startedAt);
1222
1379
  return response;
@@ -1228,7 +1385,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1228
1385
  };
1229
1386
  logToolCall(config, {
1230
1387
  tool: toolNames.grep,
1231
- workspaceId,
1388
+ ...workspaceLogContext(workspace, extra.sessionId),
1232
1389
  path: input.path,
1233
1390
  success: true,
1234
1391
  durationMs: Math.round(performance.now() - startedAt),
@@ -1253,7 +1410,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1253
1410
  });
1254
1411
  registerAppTool(server, toolNames.glob, {
1255
1412
  title: "Glob",
1256
- 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.",
1413
+ 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.",
1257
1414
  inputSchema: {
1258
1415
  workspaceId: z
1259
1416
  .string()
@@ -1262,12 +1419,12 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1262
1419
  path: z
1263
1420
  .string()
1264
1421
  .optional()
1265
- .describe("Optional path scope relative to the workspace root."),
1422
+ .describe("Optional path scope relative to the workspace root, or an absolute path inside the OS temp directory."),
1266
1423
  },
1267
1424
  outputSchema: resultOutputSchema(),
1268
1425
  ...toolWidgetDescriptorMeta(config, "search"),
1269
1426
  annotations: { readOnlyHint: true },
1270
- }, async ({ workspaceId, ...input }) => {
1427
+ }, async ({ workspaceId, ...input }, extra) => {
1271
1428
  const workspace = workspaces.getWorkspace(workspaceId);
1272
1429
  return runToolWithHooks(hooks, {
1273
1430
  tool: toolNames.glob,
@@ -1276,16 +1433,15 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1276
1433
  isFailure: toolResultIsError,
1277
1434
  operation: async () => {
1278
1435
  const startedAt = performance.now();
1279
- if (input.path)
1280
- workspaces.resolvePath(workspace, input.path);
1281
1436
  const response = await findFilesTool(input, {
1282
1437
  cwd: workspace.root,
1283
1438
  root: workspace.root,
1439
+ fileRoots: workspaces.fileToolRoots(workspace),
1284
1440
  });
1285
1441
  if (response.isError) {
1286
1442
  logFailedToolResponse(config, {
1287
1443
  tool: toolNames.glob,
1288
- workspaceId,
1444
+ ...workspaceLogContext(workspace, extra.sessionId),
1289
1445
  path: input.path,
1290
1446
  }, response.content, startedAt);
1291
1447
  return response;
@@ -1297,7 +1453,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1297
1453
  };
1298
1454
  logToolCall(config, {
1299
1455
  tool: toolNames.glob,
1300
- workspaceId,
1456
+ ...workspaceLogContext(workspace, extra.sessionId),
1301
1457
  path: input.path,
1302
1458
  success: true,
1303
1459
  durationMs: Math.round(performance.now() - startedAt),
@@ -1322,19 +1478,19 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1322
1478
  });
1323
1479
  registerAppTool(server, toolNames.ls, {
1324
1480
  title: "Ls",
1325
- description: "List a directory inside an open workspace. Use this for directory inspection before reading files. Call open_workspace first and pass workspaceId.",
1481
+ 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.",
1326
1482
  inputSchema: {
1327
1483
  workspaceId: z
1328
1484
  .string()
1329
1485
  .describe("Workspace identifier returned by open_workspace."),
1330
1486
  path: z
1331
1487
  .string()
1332
- .describe("Directory path to list, relative to the workspace root."),
1488
+ .describe("Directory path to list, relative to the workspace root or absolute inside the OS temp directory."),
1333
1489
  },
1334
1490
  outputSchema: resultOutputSchema(),
1335
1491
  ...toolWidgetDescriptorMeta(config, "directory"),
1336
1492
  annotations: { readOnlyHint: true },
1337
- }, async ({ workspaceId, ...input }) => {
1493
+ }, async ({ workspaceId, ...input }, extra) => {
1338
1494
  const workspace = workspaces.getWorkspace(workspaceId);
1339
1495
  return runToolWithHooks(hooks, {
1340
1496
  tool: toolNames.ls,
@@ -1343,15 +1499,15 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1343
1499
  isFailure: toolResultIsError,
1344
1500
  operation: async () => {
1345
1501
  const startedAt = performance.now();
1346
- workspaces.resolvePath(workspace, input.path);
1347
1502
  const response = await listDirectoryTool(input, {
1348
1503
  cwd: workspace.root,
1349
1504
  root: workspace.root,
1505
+ fileRoots: workspaces.fileToolRoots(workspace),
1350
1506
  });
1351
1507
  if (response.isError) {
1352
1508
  logFailedToolResponse(config, {
1353
1509
  tool: toolNames.ls,
1354
- workspaceId,
1510
+ ...workspaceLogContext(workspace, extra.sessionId),
1355
1511
  path: input.path,
1356
1512
  }, response.content, startedAt);
1357
1513
  return response;
@@ -1359,7 +1515,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1359
1515
  const summary = textSummary(response.content);
1360
1516
  logToolCall(config, {
1361
1517
  tool: toolNames.ls,
1362
- workspaceId,
1518
+ ...workspaceLogContext(workspace, extra.sessionId),
1363
1519
  path: input.path,
1364
1520
  success: true,
1365
1521
  durationMs: Math.round(performance.now() - startedAt),
@@ -1408,7 +1564,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1408
1564
  outputSchema: resultOutputSchema(),
1409
1565
  ...toolWidgetDescriptorMeta(config, "shell"),
1410
1566
  annotations: SHELL_TOOL_ANNOTATIONS,
1411
- }, async ({ workspaceId, workingDirectory, ...input }) => {
1567
+ }, async ({ workspaceId, workingDirectory, ...input }, extra) => {
1412
1568
  const workspace = workspaces.getWorkspace(workspaceId);
1413
1569
  return runToolWithHooks(hooks, {
1414
1570
  tool: toolNames.shell,
@@ -1429,7 +1585,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1429
1585
  if (response.isError) {
1430
1586
  logFailedToolResponse(config, {
1431
1587
  tool: toolNames.shell,
1432
- workspaceId,
1588
+ ...workspaceLogContext(workspace, extra.sessionId),
1433
1589
  workingDirectory: workingDirectory ?? ".",
1434
1590
  command: input.command,
1435
1591
  commandLength: input.command.length,
@@ -1443,7 +1599,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1443
1599
  };
1444
1600
  logToolCall(config, {
1445
1601
  tool: toolNames.shell,
1446
- workspaceId,
1602
+ ...workspaceLogContext(workspace, extra.sessionId),
1447
1603
  workingDirectory: workingDirectory ?? ".",
1448
1604
  command: input.command,
1449
1605
  commandLength: input.command.length,