@gajae-code/coding-agent 0.14.2 → 0.15.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.
Files changed (345) hide show
  1. package/CHANGELOG.md +120 -1
  2. package/dist/types/async/job-manager.d.ts +4 -1
  3. package/dist/types/autoresearch/dashboard.d.ts +29 -4
  4. package/dist/types/autoresearch/data-context.d.ts +25 -0
  5. package/dist/types/autoresearch/git.d.ts +65 -7
  6. package/dist/types/autoresearch/harness.d.ts +58 -0
  7. package/dist/types/autoresearch/prompts.d.ts +51 -0
  8. package/dist/types/autoresearch/runs.d.ts +173 -0
  9. package/dist/types/autoresearch/types.d.ts +9 -139
  10. package/dist/types/capability/types.d.ts +20 -0
  11. package/dist/types/cli/accounts-cli.d.ts +1 -0
  12. package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
  13. package/dist/types/config/autorouting-contract.d.ts +146 -0
  14. package/dist/types/config/autorouting-generator.d.ts +40 -0
  15. package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
  16. package/dist/types/config/autorouting.d.ts +35 -0
  17. package/dist/types/config/model-discovery-manager.d.ts +8 -1
  18. package/dist/types/config/model-registry.d.ts +30 -1
  19. package/dist/types/config/provider-selection-policy.d.ts +23 -0
  20. package/dist/types/config/settings-schema.d.ts +61 -3
  21. package/dist/types/config/settings.d.ts +3 -0
  22. package/dist/types/coordinator/contract.d.ts +1 -1
  23. package/dist/types/coordinator-mcp/durability.d.ts +29 -0
  24. package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
  26. package/dist/types/coordinator-mcp/server.d.ts +14 -0
  27. package/dist/types/crash/index-store.d.ts +3 -0
  28. package/dist/types/crash/record-loader.d.ts +2 -0
  29. package/dist/types/crash/upstream/relay.d.ts +5 -3
  30. package/dist/types/cursor.d.ts +12 -0
  31. package/dist/types/defaults/gjc-defaults.d.ts +35 -1
  32. package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
  33. package/dist/types/discovery/helpers.d.ts +1 -1
  34. package/dist/types/edit/index.d.ts +2 -1
  35. package/dist/types/eval/py/executor.d.ts +2 -0
  36. package/dist/types/extensibility/extensions/loader.d.ts +32 -0
  37. package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
  38. package/dist/types/extensibility/skills.d.ts +2 -0
  39. package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
  40. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  41. package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
  42. package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
  43. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
  44. package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
  45. package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
  46. package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
  47. package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
  48. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
  49. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
  50. package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
  51. package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
  52. package/dist/types/hooks/skill-keywords.d.ts +1 -1
  53. package/dist/types/main.d.ts +0 -10
  54. package/dist/types/modes/acp/acp-agent.d.ts +13 -6
  55. package/dist/types/modes/components/model-selector.d.ts +14 -0
  56. package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
  57. package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
  58. package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
  59. package/dist/types/modes/interactive-mode.d.ts +3 -0
  60. package/dist/types/modes/types.d.ts +9 -0
  61. package/dist/types/rlm/artifacts.d.ts +4 -4
  62. package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
  63. package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
  64. package/dist/types/runtime-mcp/config.d.ts +6 -0
  65. package/dist/types/runtime-mcp/manager.d.ts +10 -0
  66. package/dist/types/sdk/acp/adapter.d.ts +0 -4
  67. package/dist/types/sdk/broker/broker.d.ts +1 -1
  68. package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
  69. package/dist/types/sdk/broker/session-index.d.ts +15 -0
  70. package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
  71. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
  72. package/dist/types/sdk/bus/index.d.ts +8 -0
  73. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
  74. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
  75. package/dist/types/sdk/client/client.d.ts +39 -4
  76. package/dist/types/sdk/host/host.d.ts +1 -0
  77. package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
  78. package/dist/types/sdk/host/model-pin.d.ts +29 -0
  79. package/dist/types/sdk/host/session-runtime.d.ts +2 -1
  80. package/dist/types/sdk/lifecycle-session.d.ts +13 -0
  81. package/dist/types/sdk/router/session-router.d.ts +27 -1
  82. package/dist/types/sdk/session.d.ts +11 -1
  83. package/dist/types/session/agent-session.d.ts +36 -6
  84. package/dist/types/session/artifacts.d.ts +16 -1
  85. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  86. package/dist/types/session/session-manager.d.ts +84 -2
  87. package/dist/types/session/session-staging-paths.d.ts +3 -0
  88. package/dist/types/skill-state/canonical-skills.d.ts +1 -1
  89. package/dist/types/skill-state/workflow-hud.d.ts +12 -21
  90. package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
  91. package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
  92. package/dist/types/system-prompt.d.ts +23 -0
  93. package/dist/types/task/executor.d.ts +35 -4
  94. package/dist/types/task/index.d.ts +17 -3
  95. package/dist/types/task/receipt.d.ts +3 -1
  96. package/dist/types/task/types.d.ts +144 -0
  97. package/dist/types/tools/atomic-file-write.d.ts +72 -0
  98. package/dist/types/tools/descriptors.d.ts +1 -0
  99. package/dist/types/tools/index.d.ts +36 -1
  100. package/dist/types/tools/move-session.d.ts +40 -0
  101. package/dist/types/tools/python.d.ts +12 -0
  102. package/dist/types/tools/resource-gc.d.ts +0 -3
  103. package/dist/types/tools/search.d.ts +8 -1
  104. package/dist/types/tools/skill.d.ts +9 -4
  105. package/dist/types/tools/subagent.d.ts +33 -3
  106. package/dist/types/utils/xml-escape.d.ts +2 -0
  107. package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
  108. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
  109. package/package.json +11 -12
  110. package/scripts/build-sdk-package-smoke.ts +7 -5
  111. package/scripts/check-autorouting-tier-map.ts +183 -0
  112. package/scripts/generate-sdk-operation-inventory.ts +12 -0
  113. package/scripts/generate-tool-catalog.ts +40 -5
  114. package/src/async/job-manager.ts +5 -2
  115. package/src/autoresearch/dashboard.ts +134 -314
  116. package/src/autoresearch/data-context.ts +35 -0
  117. package/src/autoresearch/git.ts +186 -26
  118. package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
  119. package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
  120. package/src/autoresearch/prompts/prompt.md +103 -0
  121. package/src/autoresearch/prompts.ts +126 -0
  122. package/src/autoresearch/runs.ts +708 -0
  123. package/src/autoresearch/types.ts +9 -150
  124. package/src/capability/index.ts +4 -4
  125. package/src/capability/types.ts +20 -0
  126. package/src/cli/accounts-cli.ts +21 -5
  127. package/src/cli/config-cli.ts +25 -1
  128. package/src/cli/fast-help.ts +1 -2
  129. package/src/cli/list-models.ts +5 -8
  130. package/src/cli/setup-cli.ts +8 -0
  131. package/src/cli.ts +2 -21
  132. package/src/commands/autoresearch.ts +33 -0
  133. package/src/commands/coordinator.ts +15 -0
  134. package/src/commands/launch.ts +2 -4
  135. package/src/commands/quick-lane.ts +1 -1
  136. package/src/commands/sdk.ts +24 -0
  137. package/src/commands/state.ts +1 -1
  138. package/src/config/autorouting-contract.ts +474 -0
  139. package/src/config/autorouting-generator.ts +238 -0
  140. package/src/config/autorouting-tier-map.ts +6108 -0
  141. package/src/config/autorouting.ts +120 -0
  142. package/src/config/model-discovery-manager.ts +42 -11
  143. package/src/config/model-registry.ts +348 -68
  144. package/src/config/provider-selection-policy.ts +60 -9
  145. package/src/config/settings-schema.ts +195 -6
  146. package/src/config/settings.ts +62 -1
  147. package/src/config.ts +2 -3
  148. package/src/coordinator/contract.ts +0 -1
  149. package/src/coordinator-mcp/codex-handoff.ts +68 -42
  150. package/src/coordinator-mcp/durability.ts +200 -0
  151. package/src/coordinator-mcp/event-webhook.ts +278 -0
  152. package/src/coordinator-mcp/question-state.ts +109 -46
  153. package/src/coordinator-mcp/server.ts +932 -504
  154. package/src/crash/index-store.ts +126 -22
  155. package/src/crash/record-loader.ts +7 -0
  156. package/src/crash/upstream/relay.ts +82 -34
  157. package/src/cursor.ts +84 -1
  158. package/src/debug/index.ts +26 -0
  159. package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
  160. package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
  161. package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
  162. package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
  163. package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
  164. package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
  165. package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
  166. package/src/defaults/gjc-defaults.ts +89 -2
  167. package/src/defaults/gjc-skills.generated.ts +24 -17
  168. package/src/discovery/builtin.ts +18 -5
  169. package/src/discovery/helpers.ts +6 -6
  170. package/src/edit/index.ts +5 -2
  171. package/src/eval/py/executor.ts +8 -0
  172. package/src/extensibility/extensions/loader.ts +135 -13
  173. package/src/extensibility/extensions/wrapper.ts +1 -2
  174. package/src/extensibility/gjc-plugins/schema.ts +1 -1
  175. package/src/extensibility/runtime-skill-discovery.ts +2 -2
  176. package/src/extensibility/skill-management.ts +2 -3
  177. package/src/extensibility/skills.ts +16 -8
  178. package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
  179. package/src/gjc-runtime/gc-render.ts +0 -1
  180. package/src/gjc-runtime/gc-runtime.ts +1 -11
  181. package/src/gjc-runtime/launch-tmux.ts +3 -3
  182. package/src/gjc-runtime/psmux-detect.ts +2 -2
  183. package/src/gjc-runtime/python-transcript.ts +58 -0
  184. package/src/gjc-runtime/ralplan-runtime.ts +6 -18
  185. package/src/gjc-runtime/session-layout.ts +18 -7
  186. package/src/gjc-runtime/session-state-sidecar.ts +58 -9
  187. package/src/gjc-runtime/state-renderer.ts +0 -38
  188. package/src/gjc-runtime/state-runtime.ts +43 -22
  189. package/src/gjc-runtime/state-schema.ts +1 -1
  190. package/src/gjc-runtime/tmux-common.ts +7 -7
  191. package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
  192. package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
  193. package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
  194. package/src/gjc-runtime/workflow-command-ref.ts +15 -11
  195. package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
  196. package/src/gjc-runtime/workflow-manifest.ts +43 -83
  197. package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
  198. package/src/hooks/skill-keywords.ts +3 -3
  199. package/src/hooks/skill-state.ts +2 -2
  200. package/src/internal-urls/docs-index.generated.ts +26 -24
  201. package/src/lsp/index.ts +25 -5
  202. package/src/main.ts +2 -27
  203. package/src/modes/acp/acp-agent.ts +200 -75
  204. package/src/modes/components/model-selector.ts +216 -13
  205. package/src/modes/components/oauth-selector.ts +5 -5
  206. package/src/modes/components/skill-hud/render.ts +173 -35
  207. package/src/modes/components/smart-routing-panel.ts +531 -0
  208. package/src/modes/components/tool-status-header.ts +6 -3
  209. package/src/modes/controllers/command-controller.ts +17 -7
  210. package/src/modes/controllers/input-controller.ts +44 -9
  211. package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
  212. package/src/modes/controllers/selector-controller.ts +298 -65
  213. package/src/modes/interactive-mode.ts +8 -1
  214. package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
  215. package/src/modes/types.ts +9 -1
  216. package/src/prompts/agents/planner.md +1 -1
  217. package/src/prompts/system/system-prompt.md +1 -1
  218. package/src/prompts/system/volatile-project-context.md +2 -1
  219. package/src/prompts/tools/move-session.md +15 -0
  220. package/src/prompts/tools/python.md +12 -0
  221. package/src/prompts/tools/skill-discovery.md +1 -1
  222. package/src/prompts/tools/skill.md +2 -2
  223. package/src/prompts/tools/task-summary.md +1 -0
  224. package/src/prompts/tools/task.md +6 -0
  225. package/src/rlm/artifacts.ts +12 -10
  226. package/src/runtime/optional-runtime-services.ts +3 -2
  227. package/src/runtime/workspace-tree-service.ts +11 -3
  228. package/src/runtime-mcp/config.ts +8 -1
  229. package/src/runtime-mcp/manager.ts +33 -0
  230. package/src/sdk/acp/adapter.ts +48 -1
  231. package/src/sdk/broker/broker.ts +30 -2
  232. package/src/sdk/broker/discovery.ts +217 -2
  233. package/src/sdk/broker/lifecycle.ts +385 -59
  234. package/src/sdk/broker/session-index.ts +243 -48
  235. package/src/sdk/broker/transport.ts +1 -0
  236. package/src/sdk/bus/chat-daemon-control.ts +8 -2
  237. package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
  238. package/src/sdk/bus/index.ts +208 -11
  239. package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
  240. package/src/sdk/bus/reconciliation-store.ts +27 -5
  241. package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
  242. package/src/sdk/client/client.ts +204 -22
  243. package/src/sdk/host/host.ts +16 -0
  244. package/src/sdk/host/internal-autorouting-state.ts +13 -0
  245. package/src/sdk/host/model-pin.ts +101 -0
  246. package/src/sdk/host/reverse-leases.ts +2 -1
  247. package/src/sdk/host/session-runtime.test.ts +224 -1
  248. package/src/sdk/host/session-runtime.ts +125 -37
  249. package/src/sdk/lifecycle/service.ts +3 -1
  250. package/src/sdk/lifecycle-session.ts +42 -1
  251. package/src/sdk/protocol/operation-inventory.generated.json +66 -0
  252. package/src/sdk/router/session-router.ts +375 -36
  253. package/src/sdk/session.ts +547 -47
  254. package/src/session/account-inventory.ts +1 -1
  255. package/src/session/agent-session.ts +765 -189
  256. package/src/session/artifacts.ts +234 -4
  257. package/src/session/internal/managed-session-storage.ts +91 -4
  258. package/src/session/provider-safety-stop-hint.ts +3 -2
  259. package/src/session/session-manager.ts +775 -16
  260. package/src/session/session-staging-paths.ts +8 -0
  261. package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
  262. package/src/setup/host-plugin-setup.ts +2 -2
  263. package/src/skill-state/active-state.ts +2 -2
  264. package/src/skill-state/canonical-skills.ts +1 -1
  265. package/src/skill-state/initial-phase.ts +1 -1
  266. package/src/skill-state/workflow-hud.ts +41 -41
  267. package/src/skill-state/workflow-mutation-guard.ts +26 -7
  268. package/src/slash-commands/builtin-registry.ts +77 -0
  269. package/src/slash-commands/helpers/autorouting-status.ts +55 -0
  270. package/src/system-prompt.ts +102 -5
  271. package/src/task/discovery.ts +2 -3
  272. package/src/task/executor.ts +617 -43
  273. package/src/task/index.ts +262 -15
  274. package/src/task/receipt.ts +30 -0
  275. package/src/task/render.ts +11 -1
  276. package/src/task/types.ts +316 -0
  277. package/src/tools/atomic-file-write.ts +594 -0
  278. package/src/tools/bisect.ts +3 -2
  279. package/src/tools/descriptors.test.ts +3 -0
  280. package/src/tools/descriptors.ts +24 -0
  281. package/src/tools/image-gen.ts +5 -1
  282. package/src/tools/index.ts +62 -5
  283. package/src/tools/move-session.ts +107 -0
  284. package/src/tools/python.ts +205 -0
  285. package/src/tools/read.ts +88 -15
  286. package/src/tools/renderers.ts +2 -0
  287. package/src/tools/resource-gc.ts +8 -177
  288. package/src/tools/search.ts +30 -6
  289. package/src/tools/skill-discovery.ts +1 -0
  290. package/src/tools/skill.ts +37 -7
  291. package/src/tools/subagent-render.ts +119 -13
  292. package/src/tools/subagent.ts +91 -74
  293. package/src/tools/tool-catalog.generated.ts +64 -2
  294. package/src/tools/write.ts +16 -12
  295. package/src/utils/xml-escape.ts +14 -0
  296. package/src/web/search/providers/codex.ts +2 -2
  297. package/src/workflow/workflow-intent-diff.ts +13 -12
  298. package/src/workflow/workflow-intent-report-consumer.ts +6 -6
  299. package/dist/types/autoresearch/helpers.d.ts +0 -24
  300. package/dist/types/autoresearch/index.d.ts +0 -2
  301. package/dist/types/autoresearch/state.d.ts +0 -17
  302. package/dist/types/autoresearch/storage.d.ts +0 -142
  303. package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
  304. package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
  305. package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
  306. package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
  307. package/dist/types/commands/team.d.ts +0 -24
  308. package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
  309. package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
  310. package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
  311. package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
  312. package/dist/types/gjc-runtime/team-store.d.ts +0 -168
  313. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
  314. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
  315. package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
  316. package/dist/types/rlm/index.d.ts +0 -47
  317. package/dist/types/rlm/preset.d.ts +0 -23
  318. package/dist/types/rlm/python-tool.d.ts +0 -16
  319. package/src/autoresearch/command-resume.md +0 -14
  320. package/src/autoresearch/index.ts +0 -536
  321. package/src/autoresearch/prompt.md +0 -103
  322. package/src/autoresearch/resume-message.md +0 -10
  323. package/src/autoresearch/state.ts +0 -273
  324. package/src/autoresearch/storage.ts +0 -699
  325. package/src/autoresearch/tools/init-experiment.ts +0 -272
  326. package/src/autoresearch/tools/log-experiment.ts +0 -524
  327. package/src/autoresearch/tools/run-experiment.ts +0 -475
  328. package/src/autoresearch/tools/update-notes.ts +0 -109
  329. package/src/commands/rlm.ts +0 -19
  330. package/src/commands/team.ts +0 -238
  331. package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
  332. package/src/defaults/gjc/skills/team/SKILL.md +0 -467
  333. package/src/gjc-runtime/team-gc.ts +0 -49
  334. package/src/gjc-runtime/team-launch.ts +0 -404
  335. package/src/gjc-runtime/team-notify.ts +0 -326
  336. package/src/gjc-runtime/team-runtime.ts +0 -5923
  337. package/src/gjc-runtime/team-store.ts +0 -878
  338. package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
  339. package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
  340. package/src/gjc-runtime/team-workers.ts +0 -1123
  341. package/src/prompts/system/rlm-report-command.md +0 -1
  342. package/src/prompts/system/rlm-research.md +0 -23
  343. package/src/rlm/index.ts +0 -365
  344. package/src/rlm/preset.ts +0 -76
  345. package/src/rlm/python-tool.ts +0 -68
@@ -1,3 +1,5 @@
1
+ import * as nodeFs from "node:fs";
2
+ import * as fs from "node:fs/promises";
1
3
  import * as path from "node:path";
2
4
  import {
3
5
  Agent,
@@ -33,6 +35,7 @@ import {
33
35
  postmortem,
34
36
  prompt,
35
37
  Snowflake,
38
+ setProjectDir,
36
39
  } from "@gajae-code/utils";
37
40
  import {
38
41
  createAppendOnlyContextManager,
@@ -46,9 +49,10 @@ import {
46
49
  isBackgroundJobSupportEnabled,
47
50
  jobElapsedMs,
48
51
  } from "../async";
49
- import { loadCapability } from "../capability";
52
+ import { loadCapability, reset as resetCapabilities } from "../capability";
50
53
  import { type Rule, ruleCapability, setActiveRules } from "../capability/rule";
51
54
  import type { SourceMeta } from "../capability/types";
55
+ import { AUTOROUTING_INACTIVE_WARNING } from "../config/autorouting-contract";
52
56
  import { resolveModelProfileName } from "../config/model-profile-contract";
53
57
  import { resolveProfileBindings } from "../config/model-profiles";
54
58
  import { kNoAuth, ModelRegistry } from "../config/model-registry";
@@ -64,12 +68,15 @@ import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate
64
68
  import { Settings, type SkillsSettings } from "../config/settings";
65
69
  import { resolveEagerTaskDelegation } from "../config/task-delegation";
66
70
  import { CursorExecHandlers } from "../cursor";
71
+ import { EditTool } from "../edit";
67
72
  import type { BashRestrictionProfile } from "../tools/bash-allowed-prefixes";
73
+ import { SearchTool } from "../tools/search";
68
74
  import "../discovery";
69
75
  import { resolveConfigValue } from "../config/resolve-config-value";
70
76
  import { getEmbeddedDefaultGjcSkills } from "../defaults/gjc-defaults";
71
77
  import { BUNDLED_GROK_BUILD_EXTENSION_ID, getBundledGrokBuildExtensionFactory } from "../defaults/gjc-grok-cli";
72
78
  import { initializeWithSettings } from "../discovery";
79
+ import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../discovery/helpers";
73
80
  import { TtsrManager } from "../export/ttsr";
74
81
  import type { CustomCommandsLoadResult, LoadedCustomCommand } from "../extensibility/custom-commands";
75
82
  import type { CustomTool, CustomToolContext, CustomToolSessionEvent } from "../extensibility/custom-tools/types";
@@ -109,6 +116,7 @@ import type { HindsightSessionState } from "../hindsight/state";
109
116
  import { normalizePluginHook } from "../hooks/normalize";
110
117
  import { initializeLocalRoot, LocalProtocolHandler, type LocalProtocolOptions } from "../internal-urls";
111
118
  import type { LspStartupServerInfo } from "../lsp";
119
+ import { shutdownAll as shutdownAllLspClients } from "../lsp/client";
112
120
  import btwUserPrompt from "../prompts/system/btw-user.md" with { type: "text" };
113
121
  import asyncResultTemplate from "../prompts/tools/async-result.md" with { type: "text" };
114
122
  import { AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
@@ -129,9 +137,9 @@ import {
129
137
  import { createReconciliationStore, type ReconciliationStore } from "../sdk/bus/reconciliation-store";
130
138
  import { NotificationSessionController } from "../sdk/bus/session-control";
131
139
  import { shouldHostSdk } from "../sdk/host";
140
+ import { markAutoroutingInactive } from "../sdk/host/internal-autorouting-state";
132
141
  import { createSdkSessionRuntimeExtension, registerSdkOnlyNotificationCommand } from "../sdk/host/session-runtime";
133
142
  import { createSdkWebSocketTransport } from "../sdk/host/websocket-transport";
134
-
135
143
  import type { SecretObfuscator } from "../secrets";
136
144
  import { AgentSession, type ForkContextSeed } from "../session/agent-session";
137
145
  import { AuthBrokerClient, AuthStorage, RemoteAuthCredentialStore } from "../session/auth-storage";
@@ -161,6 +169,8 @@ import { AgentOutputManager } from "../task/output-manager";
161
169
  import { parseThinkingLevel, resolveThinkingLevelForModel, toReasoningEffort } from "../thinking";
162
170
  import { isMCPBridgeTool, selectRestorableDiscoveredBuiltinToolNames } from "../tool-discovery/tool-index";
163
171
  import {
172
+ type AutomationToolName,
173
+ type AutomationTools,
164
174
  BUILTIN_TOOL_DESCRIPTORS,
165
175
  BUILTIN_TOOLS,
166
176
  computeEssentialBuiltinNames,
@@ -183,6 +193,8 @@ import {
183
193
  type SdkStartupCapability,
184
194
  } from "./startup-capability";
185
195
 
196
+ export type { AutomationToolName, AutomationTools } from "../tools";
197
+
186
198
  type AsyncResultEntry = {
187
199
  jobId: string;
188
200
  generation: string;
@@ -211,6 +223,14 @@ type McpNotificationEntry = {
211
223
  uri: string;
212
224
  };
213
225
 
226
+ /** Capture the cursor edit grant before the model-facing edit entry is removed. */
227
+ export function captureCursorEditTool<T>(
228
+ toolRegistry: ReadonlyMap<string, unknown>,
229
+ createReplaceTool: () => T,
230
+ ): T | undefined {
231
+ return toolRegistry.has("edit") ? createReplaceTool() : undefined;
232
+ }
233
+
214
234
  function buildAsyncResultBatchMessage(entries: AsyncResultEntry[]): CustomMessage<AsyncResultDetails> | null {
215
235
  if (entries.length === 0) return null;
216
236
  // Partition denied owned-completion entries out ENTIRELY before batch
@@ -440,6 +460,13 @@ export interface CreateAgentSessionOptions {
440
460
 
441
461
  /** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
442
462
  customTools?: (CustomTool | ToolDefinition)[];
463
+ /**
464
+ * Host-owned implementations for the built-in `browser` and `computer`
465
+ * surfaces. These are materialized as built-ins, retain built-in provenance
466
+ * and activation behavior, and receive cancellation through AgentTool's
467
+ * AbortSignal. A matching custom or extension tool is rejected.
468
+ */
469
+ automationTools?: AutomationTools;
443
470
  /** Explicit parent/phase used to load active GJC sub-skill tools for this session. */
444
471
  gjcSubskillToolContext?: { parent: string; phase: string; sessionId?: string; cwd?: string };
445
472
  /** Inline extensions (merged with discovery). */
@@ -1246,7 +1273,31 @@ function findDeferredExactMcpToolNameCollisions(
1246
1273
  return [...collisions];
1247
1274
  }
1248
1275
 
1276
+ const AUTOMATION_TOOL_NAMES = new Set<AutomationToolName>(["browser", "computer"]);
1277
+
1278
+ function validateAutomationTools(options: CreateAgentSessionOptions): AutomationTools {
1279
+ const automationTools = options.automationTools ?? {};
1280
+ const entries = Object.entries(automationTools);
1281
+ for (const [name, tool] of entries) {
1282
+ if (!AUTOMATION_TOOL_NAMES.has(name as AutomationToolName)) {
1283
+ throw new Error(`Unsupported SDK automation tool name: ${name}`);
1284
+ }
1285
+ if (!tool || tool.name !== name) {
1286
+ throw new Error(`SDK automation tool ${name} must expose the built-in name ${JSON.stringify(name)}`);
1287
+ }
1288
+ }
1289
+ const externalNames = new Set(entries.map(([name]) => name));
1290
+ const collisions = [
1291
+ ...new Set((options.customTools ?? []).map(tool => tool.name).filter(name => externalNames.has(name))),
1292
+ ];
1293
+ if (collisions.length > 0) {
1294
+ throw new Error(`SDK automation tools cannot collide with custom tools: ${collisions.join(", ")}`);
1295
+ }
1296
+ return automationTools;
1297
+ }
1298
+
1249
1299
  export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
1300
+ const automationTools = validateAutomationTools(options);
1250
1301
  const lifecycleStartupCapability = (
1251
1302
  options as CreateAgentSessionOptions & { [lifecycleStartupCapabilityOption]?: SdkStartupCapability }
1252
1303
  )[lifecycleStartupCapabilityOption];
@@ -1302,7 +1353,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1302
1353
 
1303
1354
  let agent: Agent;
1304
1355
  let session!: AgentSession;
1356
+ let sessionManager!: SessionManager;
1305
1357
  let hasSession = false;
1358
+ let processCwdClaimed = false;
1306
1359
  let hasRegistered = false;
1307
1360
  let asyncJobManager: AsyncJobManager | undefined;
1308
1361
  let asyncJobManagerAdmitted = false;
@@ -1363,7 +1416,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1363
1416
  authStorage.setSessionCredentialSelector(scopeId, provider, selector);
1364
1417
  };
1365
1418
  const settings = options.settings ?? (await logger.time("settings", Settings.init, { cwd, agentDir }));
1366
- const runtimeServices = createOptionalRuntimeServices(settings, options.runtimeServices, { cwd });
1419
+ const autoroutingInactive =
1420
+ settings.get("task.autorouting.enabled") === true && !settings.getEffectiveAutorouting().active;
1421
+ // Cwd-derived runtime state must follow a rescope (`move_session`, `/move`),
1422
+ // so services resolve the LIVE session cwd per activation instead of
1423
+ // capturing the launch root. Before the manager exists the launch cwd is the
1424
+ // only truth available, and it is also the manager's initial cwd.
1425
+ let liveSessionManager: SessionManager | undefined;
1426
+ const getLiveCwd = (): string => liveSessionManager?.getCwd() ?? cwd;
1427
+ const runtimeServices = createOptionalRuntimeServices(settings, options.runtimeServices, { cwd: getLiveCwd });
1367
1428
  modelRegistry.applyConfiguredModelBindings(settings);
1368
1429
  logger.time("initializeWithSettings", initializeWithSettings, settings);
1369
1430
  if (!options.modelRegistry) {
@@ -1420,11 +1481,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1420
1481
  }
1421
1482
  applyConfiguredSearchTimeout(settings);
1422
1483
 
1423
- const sessionManager =
1484
+ sessionManager =
1424
1485
  options.sessionManager ??
1425
1486
  (await logger.time("sessionManager", async () => {
1426
1487
  return SessionManager.create(cwd, SessionManager.managedDestination(cwd, agentDir));
1427
1488
  }));
1489
+ liveSessionManager = sessionManager;
1428
1490
  const logicalSessionId = sessionManager.getSessionId();
1429
1491
  // Fork-context seeds carry conversation content only, never provider identity:
1430
1492
  // a shared continuity id would make concurrent subagents present the same
@@ -1866,7 +1928,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1866
1928
  contextFilesResultPromise,
1867
1929
  raceWithDeadline("buildWorkspaceTree", workspaceTreePromise),
1868
1930
  ]);
1869
- const contextFiles = contextFilesResult.contextFiles;
1931
+ // Mutable: a rescope re-discovers cwd-derived project instructions so the
1932
+ // model is never shown the launcher root's AGENTS.md alongside the new cwd.
1933
+ let contextFiles = contextFilesResult.contextFiles;
1934
+ let liveWorkspaceTree: WorkspaceTree | undefined = resolvedWorkspaceTree;
1870
1935
  const discoveredContextFileWarnings = contextFilesResult.warnings;
1871
1936
 
1872
1937
  const backgroundJobsEnabled = isBackgroundJobSupportEnabled(settings);
@@ -1974,6 +2039,168 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1974
2039
  if (model) return formatModelString(model);
1975
2040
  return undefined;
1976
2041
  };
2042
+ let mcpManager: MCPManager | undefined = options.mcpManager;
2043
+ let ownsMcpManager = false;
2044
+ const cwdCapturingToolNames: string[] = [];
2045
+ const notificationDebounceTimers = new Map<string, Timer>();
2046
+ const wireMcpManagerCallbacks = (manager: MCPManager): void => {
2047
+ manager.setOnPromptsChanged(serverName => {
2048
+ const promptCommands = buildMCPPromptCommands(manager);
2049
+ session.setMCPPromptCommands(promptCommands);
2050
+ logger.debug("MCP prompt commands refreshed", { path: `mcp:${serverName}` });
2051
+ });
2052
+ manager.setOnResourcesChanged((serverName, uri) => {
2053
+ logger.debug("MCP resources changed", { path: `mcp:${serverName}`, uri });
2054
+ if (!settings.get("mcp.notifications")) return;
2055
+ const debounceMs = settings.get("mcp.notificationDebounceMs");
2056
+ const key = `${serverName}:${uri}`;
2057
+ const existing = notificationDebounceTimers.get(key);
2058
+ if (existing) clearTimeout(existing);
2059
+ notificationDebounceTimers.set(
2060
+ key,
2061
+ setTimeout(() => {
2062
+ notificationDebounceTimers.delete(key);
2063
+ if (!settings.get("mcp.notifications")) return;
2064
+ session.yieldQueue.enqueue<McpNotificationEntry>("mcp-notification", { serverName, uri });
2065
+ }, debounceMs),
2066
+ );
2067
+ });
2068
+ };
2069
+ const rebindCwdCapturingAuthority = async (to: string): Promise<void> => {
2070
+ if (!session) return;
2071
+ if (options.mcpManager && !ownsMcpManager) {
2072
+ throw new Error(
2073
+ "Cannot rescope a session with caller-owned MCP authority; recreate the session at the target cwd.",
2074
+ );
2075
+ }
2076
+ await session.refreshGjcSubskillTools();
2077
+ const previousCwdCapturing = [...cwdCapturingToolNames];
2078
+ const nextCwdCapturing: string[] = [];
2079
+ const nextCustomTools: CustomTool[] = [];
2080
+ try {
2081
+ const declarations = await getGjcPluginToolDeclarations(to);
2082
+ const pluginToolResult = await loadAlwaysOnPluginTools({
2083
+ cwd: to,
2084
+ reservedToolNames: session.getAllToolNames().filter(name => !previousCwdCapturing.includes(name)),
2085
+ declarations,
2086
+ });
2087
+ nextCustomTools.push(...pluginToolResult.tools);
2088
+ nextCwdCapturing.push(...pluginToolResult.tools.map(tool => tool.name));
2089
+ } catch (error) {
2090
+ logger.warn("Failed to reload always-on plugin tools after session rescope", {
2091
+ error: error instanceof Error ? error.message : String(error),
2092
+ });
2093
+ }
2094
+ if (!options.mcpManager && explicitMcpConfigPath === undefined) {
2095
+ const previousManager = mcpManager;
2096
+ if (previousManager) await previousManager.disconnectAll().catch(() => {});
2097
+ pluginMcpToolNames.length = 0;
2098
+ conventionalMcpToolNames.length = 0;
2099
+ let nextManager: MCPManager | undefined;
2100
+ try {
2101
+ const loaded = await loadAllMCPConfigs(to, {
2102
+ agentDir,
2103
+ enableProjectConfig: settings.has("mcp.enableProjectConfig")
2104
+ ? settings.get("mcp.enableProjectConfig")
2105
+ : true,
2106
+ autoloadOnly: true,
2107
+ nativeOnly: true,
2108
+ });
2109
+ const { configs: pluginConfigs } = await buildPluginMcpConfigs({ cwd: to });
2110
+ const pluginNames = new Set(Object.keys(pluginConfigs));
2111
+ const mergedConfigs = { ...loaded.configs, ...pluginConfigs };
2112
+ const mergedSources = {
2113
+ ...loaded.sources,
2114
+ ...Object.fromEntries(
2115
+ Object.keys(pluginConfigs).map(name => [
2116
+ name,
2117
+ { provider: "gjc-plugins", providerName: "GJC plugin bundle", level: "project" as const },
2118
+ ]),
2119
+ ),
2120
+ };
2121
+ if (Object.keys(mergedConfigs).length > 0) {
2122
+ nextManager = new MCPManager(to, null, { sharedPoolIdleMs: settings.get("mcp.sharedPoolIdleMs") });
2123
+ nextManager.setAuthStorage(authStorage);
2124
+ wireMcpManagerCallbacks(nextManager);
2125
+ const result = await nextManager.connectServers(mergedConfigs, mergedSources as never);
2126
+ nextCustomTools.push(...(result.tools as CustomTool[]));
2127
+ nextCwdCapturing.push(...result.tools.map(tool => tool.name));
2128
+ for (const tool of result.tools) {
2129
+ const serverName = tool.mcpServerName;
2130
+ if (serverName === undefined) continue;
2131
+ if (pluginNames.has(serverName)) pluginMcpToolNames.push(tool.name);
2132
+ else conventionalMcpToolNames.push(tool.name);
2133
+ }
2134
+ }
2135
+ } catch (error) {
2136
+ logger.warn("Failed to recreate MCP authority after session rescope", {
2137
+ error: error instanceof Error ? error.message : String(error),
2138
+ });
2139
+ }
2140
+ mcpManager = nextManager;
2141
+ ownsMcpManager = Boolean(nextManager);
2142
+ await session.replaceOwnedMcpManager(nextManager);
2143
+ await session.refreshMCPTools((nextManager?.getTools() ?? []) as CustomTool[]);
2144
+ }
2145
+ cwdCapturingToolNames.length = 0;
2146
+ cwdCapturingToolNames.push(...nextCwdCapturing);
2147
+ await session.replaceNamedCustomTools(
2148
+ previousCwdCapturing.filter(name => !nextCustomTools.some(tool => tool.name === name)),
2149
+ nextCustomTools,
2150
+ );
2151
+ };
2152
+
2153
+ /**
2154
+ * Re-discover the cwd-derived read-only state the model is shown after a
2155
+ * committed rescope: project instructions, skills, and the workspace tree.
2156
+ * Without this the volatile message pairs the NEW cwd with the launch
2157
+ * root's AGENTS.md and tree, and subagents inherit the same mismatch.
2158
+ */
2159
+ const applyRescopedReadState = async (to: string): Promise<void> => {
2160
+ try {
2161
+ const rediscovered = await loadContextFilesResultInternal({ cwd: to });
2162
+ contextFiles = rediscovered.contextFiles;
2163
+ } catch (error) {
2164
+ logger.warn("Failed to re-discover context files after session rescope", {
2165
+ error: safeErrorForLog(error),
2166
+ });
2167
+ }
2168
+ if (options.skills === undefined && settings.get("skills.enabled")) {
2169
+ try {
2170
+ const reloaded = await loadSkills({
2171
+ ...settings.getGroup("skills"),
2172
+ cwd: to,
2173
+ disabledExtensions: settings.get("disabledExtensions"),
2174
+ });
2175
+ skills = withEmbeddedDefaultGjcSkills(reloaded.skills);
2176
+ if (!options.parentTaskPrefix) setActiveSkills(skills);
2177
+ await session?.replaceSkills(skills);
2178
+ } catch (error) {
2179
+ logger.warn("Failed to reload skills after session rescope", { error: safeErrorForLog(error) });
2180
+ }
2181
+ }
2182
+ // The launch-bound tree is retired immediately: a stale root-scoped tree is
2183
+ // worse than none, and the next turn re-scans at the new cwd.
2184
+ liveWorkspaceTree = undefined;
2185
+ workspaceTreePromise = Promise.resolve({
2186
+ rootPath: to,
2187
+ rendered: "",
2188
+ truncated: false,
2189
+ totalLines: 0,
2190
+ agentsMdFiles: [],
2191
+ });
2192
+ workspaceTreePromise.catch(() => {});
2193
+ try {
2194
+ session?.retireWorkspaceTreeForRescope();
2195
+ await session?.refreshBaseSystemPrompt();
2196
+ } catch (error) {
2197
+ logger.warn("Committed session rescope could not refresh the post-move prompt", {
2198
+ error: safeErrorForLog(error),
2199
+ cwd: to,
2200
+ });
2201
+ }
2202
+ };
2203
+
1977
2204
  const toolSession: ToolSession = {
1978
2205
  get cwd() {
1979
2206
  return sessionManager.getCwd();
@@ -1988,14 +2215,248 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1988
2215
  return !requestedToolNames || requestedToolNames.includes("edit");
1989
2216
  },
1990
2217
  skipPythonPreflight: options.skipPythonPreflight,
1991
- contextFiles,
1992
- workspaceTree: resolvedWorkspaceTree,
1993
- skills,
2218
+ // Getters, not snapshots: subagents launched after a rescope inherit the
2219
+ // CURRENT cwd's context files, skills, and tree rather than the launch
2220
+ // root's, which would otherwise pair the new cwd with retired instructions.
2221
+ get contextFiles() {
2222
+ return contextFiles;
2223
+ },
2224
+ get workspaceTree() {
2225
+ return liveWorkspaceTree;
2226
+ },
2227
+ get skills() {
2228
+ return skills;
2229
+ },
1994
2230
  eventBus,
1995
2231
  outputSchema: options.outputSchema,
1996
2232
  requireYieldTool: options.requireYieldTool,
1997
2233
  taskDepth: options.taskDepth ?? 0,
1998
2234
  currentAgentType: options.currentAgentType,
2235
+ // Agent-invokable session rescope (#4629). Provided only where
2236
+ // relocation is safe: canonical top-level sessions (the same
2237
+ // isCanonicalSubSession predicate that gates SDK hosting — taskDepth 0,
2238
+ // no parentTaskPrefix, no currentAgentType) without a restricted bash
2239
+ // surface. Runs the same sequence as the text/ACP `/move` handler so
2240
+ // tool path resolution, the bash default cwd, and plugin caches follow
2241
+ // the move. Unlike the user-driven `/move`, the model-invoked accessor
2242
+ // is bound to at most one successful move per session, rejects
2243
+ // re-entrant calls, refuses to move while a workflow skill is active
2244
+ // (its cwd-local state and guards stay pinned to the launch root), and
2245
+ // only narrows: the canonical target must be a strict descendant of
2246
+ // the canonical current cwd, so an injected or speculative call cannot
2247
+ // widen the session's tool/write scope to a parent, a sibling project,
2248
+ // or an arbitrary absolute path.
2249
+ ...(!isCanonicalSubSession &&
2250
+ !options.bashRestrictionProfile &&
2251
+ (options.bashAllowedPrefixes ?? []).length === 0 &&
2252
+ !options.mcpManager &&
2253
+ explicitMcpConfigPath === undefined &&
2254
+ options.workspaceTree === undefined
2255
+ ? {
2256
+ rescopeSessionCwd: (() => {
2257
+ let moveConsumed = false;
2258
+ return async (target: string): Promise<{ from: string; to: string }> => {
2259
+ if (moveConsumed) {
2260
+ throw new Error(
2261
+ "This session has already been rescoped; only one agent-invoked move is allowed per session.",
2262
+ );
2263
+ }
2264
+ if (session?.getEffectiveActiveWorkflowSkillState()) {
2265
+ throw new Error(
2266
+ "A workflow skill is active in this session; finish or exit it before rescoping.",
2267
+ );
2268
+ }
2269
+ if (options.mcpManager && !ownsMcpManager) {
2270
+ throw new Error(
2271
+ "Cannot rescope a session with caller-owned MCP authority; recreate the session at the target cwd.",
2272
+ );
2273
+ }
2274
+ return sessionManager.runExclusiveCwdTransition(async () => {
2275
+ if (moveConsumed) {
2276
+ throw new Error(
2277
+ "This session has already been rescoped; only one agent-invoked move is allowed per session.",
2278
+ );
2279
+ }
2280
+ if (session?.getEffectiveActiveWorkflowSkillState()) {
2281
+ throw new Error(
2282
+ "A workflow skill became active while waiting for the cwd transition; finish or exit it before rescoping.",
2283
+ );
2284
+ }
2285
+ const from = sessionManager.getCwd();
2286
+ const resolvedPath = path.resolve(from, target);
2287
+ let canonicalFrom: string;
2288
+ let canonicalTarget: string;
2289
+ try {
2290
+ canonicalFrom = await fs.realpath(from);
2291
+ canonicalTarget = await fs.realpath(resolvedPath);
2292
+ } catch {
2293
+ throw new Error(`Directory does not exist or is not a directory: ${resolvedPath}`);
2294
+ }
2295
+ if (!(await fs.stat(canonicalTarget)).isDirectory()) {
2296
+ throw new Error(`Directory does not exist or is not a directory: ${resolvedPath}`);
2297
+ }
2298
+ const relative = path.relative(canonicalFrom, canonicalTarget);
2299
+ if (relative === "") {
2300
+ throw new Error(
2301
+ `Target ${canonicalTarget} is the current session directory; nothing to move.`,
2302
+ );
2303
+ }
2304
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
2305
+ throw new Error(
2306
+ `Refusing to rescope outside the current session directory: ${canonicalTarget} is not within ${canonicalFrom}. move_session only narrows the session scope; ask the user to restart or /move for a broader relocation.`,
2307
+ );
2308
+ }
2309
+ let targetHandle: nodeFs.promises.FileHandle | undefined;
2310
+ let expectedIdentity: { dev: bigint; ino: bigint };
2311
+ try {
2312
+ targetHandle = await SessionManager.openNoFollowDirectory(canonicalTarget);
2313
+ const opened = await targetHandle.stat({ bigint: true });
2314
+ if (!opened.isDirectory()) {
2315
+ throw new Error(`Directory does not exist or is not a directory: ${resolvedPath}`);
2316
+ }
2317
+ expectedIdentity = { dev: opened.dev, ino: opened.ino };
2318
+ await fs.access(canonicalTarget, nodeFs.constants.R_OK | nodeFs.constants.X_OK);
2319
+ } catch (error) {
2320
+ await targetHandle?.close().catch(() => {});
2321
+ if (error instanceof Error && error.message.startsWith("Directory does not exist")) {
2322
+ throw error;
2323
+ }
2324
+ throw new Error(
2325
+ `Directory identity or access unavailable: ${canonicalTarget}${
2326
+ error instanceof Error ? ` (${error.message})` : ""
2327
+ }`,
2328
+ );
2329
+ }
2330
+ // Process-cwd authority is an explicit claim, never inferred from
2331
+ // `process.cwd() === from`: sibling sessions launched at the same
2332
+ // root both satisfy that, so acting on it would chdir the process
2333
+ // and clear process-global caches underneath the sibling.
2334
+ const ownsProcessCwd = SessionManager.isProcessCwdOwner(sessionManager);
2335
+ const restoreLaunchRoot = async (failure: unknown): Promise<never> => {
2336
+ const restoreErrors: Error[] = [];
2337
+ if (ownsProcessCwd) {
2338
+ try {
2339
+ setProjectDir(canonicalFrom);
2340
+ if (path.resolve(process.cwd()) !== path.resolve(canonicalFrom)) {
2341
+ throw new Error("Process cwd did not restore to the launch root.");
2342
+ }
2343
+ } catch (error) {
2344
+ restoreErrors.push(error instanceof Error ? error : new Error(String(error)));
2345
+ }
2346
+ try {
2347
+ resetCapabilities();
2348
+ const restoreRegistry = await resolveActiveProjectRegistryPath(canonicalFrom).catch(
2349
+ () => undefined,
2350
+ );
2351
+ clearPluginRootsAndCaches(restoreRegistry ? [restoreRegistry] : undefined);
2352
+ } catch (error) {
2353
+ restoreErrors.push(error instanceof Error ? error : new Error(String(error)));
2354
+ }
2355
+ }
2356
+ try {
2357
+ await rebindCwdCapturingAuthority(canonicalFrom);
2358
+ } catch (error) {
2359
+ restoreErrors.push(error instanceof Error ? error : new Error(String(error)));
2360
+ }
2361
+ if (restoreErrors.length > 0) {
2362
+ throw new AggregateError(
2363
+ restoreErrors,
2364
+ "Failed to restore launch-root rescope authority.",
2365
+ {
2366
+ cause: failure,
2367
+ },
2368
+ );
2369
+ }
2370
+ throw failure;
2371
+ };
2372
+ try {
2373
+ // Every fallible step that the moved session depends on runs
2374
+ // BEFORE the session-file commit, so a failure here leaves the
2375
+ // session exactly where it was and the tool call is a clean
2376
+ // rejection rather than a half-moved session.
2377
+ if (ownsProcessCwd) {
2378
+ setProjectDir(canonicalTarget);
2379
+ try {
2380
+ // `setProjectDir` chdirs a NAME. Confirm the process actually
2381
+ // landed on the pinned directory, so a path replaced after
2382
+ // the name checks cannot escape the validated descendant.
2383
+ await SessionManager.assertProcessCwdIdentity(expectedIdentity);
2384
+ } catch (error) {
2385
+ setProjectDir(canonicalFrom);
2386
+ throw error;
2387
+ }
2388
+ }
2389
+ let rescopeFailure: unknown;
2390
+ try {
2391
+ if (ownsProcessCwd) {
2392
+ resetCapabilities();
2393
+ const projectRegistry = await resolveActiveProjectRegistryPath(canonicalTarget);
2394
+ clearPluginRootsAndCaches(projectRegistry ? [projectRegistry] : undefined);
2395
+ }
2396
+ // Plugin/MCP/Python authority must be rebound successfully
2397
+ // before committing; swallowing a failure here is what leaves
2398
+ // a moved session holding launch-root tool authority.
2399
+ await rebindCwdCapturingAuthority(canonicalTarget);
2400
+ } catch (error) {
2401
+ rescopeFailure = error;
2402
+ }
2403
+ if (rescopeFailure !== undefined) {
2404
+ await restoreLaunchRoot(rescopeFailure);
2405
+ }
2406
+ try {
2407
+ await sessionManager.flush();
2408
+ // Commit last: `moveTo` re-validates the pinned identity through
2409
+ // the still-open handle at the state-changing boundary.
2410
+ await sessionManager.moveTo(canonicalTarget, {
2411
+ expectedIdentity,
2412
+ targetHandle,
2413
+ });
2414
+ } catch (error) {
2415
+ const committedCwd = sessionManager.getCwd();
2416
+ const stayedAtLaunchRoot = path.resolve(committedCwd) === path.resolve(from);
2417
+ if (stayedAtLaunchRoot) await restoreLaunchRoot(error);
2418
+ // SessionManager can publish the durable move before a later metadata
2419
+ // write fails. Treat that state as committed rather than reporting a
2420
+ // rejection after the session has moved.
2421
+ moveConsumed = true;
2422
+ logger.warn("Session rescope committed before finalization failed", {
2423
+ error: safeErrorForLog(error),
2424
+ cwd: committedCwd,
2425
+ });
2426
+ }
2427
+ moveConsumed = true;
2428
+ if (ownsProcessCwd) {
2429
+ try {
2430
+ await shutdownAllLspClients();
2431
+ } catch (error) {
2432
+ logger.warn("Failed to reset launch-root LSP clients after session rescope", {
2433
+ error: safeErrorForLog(error),
2434
+ });
2435
+ }
2436
+ }
2437
+ // Cwd-derived read-only state the prompt and subagents consume.
2438
+ // Best-effort by design: the move is committed, and a failed
2439
+ // re-discovery must not present a committed move as a failure.
2440
+ await applyRescopedReadState(sessionManager.getCwd());
2441
+ try {
2442
+ await session?.refreshSshTool({ activateIfAvailable: true });
2443
+ } catch (error) {
2444
+ // Non-fatal: the session has moved; the SSH tool refreshes
2445
+ // on its next activation attempt.
2446
+ logger.warn("Committed session rescope could not refresh the SSH tool", {
2447
+ error: safeErrorForLog(error),
2448
+ cwd: sessionManager.getCwd(),
2449
+ });
2450
+ }
2451
+ return { from, to: sessionManager.getCwd() };
2452
+ } finally {
2453
+ await targetHandle.close().catch(() => {});
2454
+ }
2455
+ });
2456
+ };
2457
+ })(),
2458
+ }
2459
+ : {}),
1999
2460
  getSessionFile: () => sessionManager.getSessionFile() ?? null,
2000
2461
  getEvalKernelOwnerId: () => evalKernelOwnerId,
2001
2462
  assertEvalExecutionAllowed: () => session?.assertEvalExecutionAllowed(),
@@ -2123,6 +2584,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2123
2584
  if (!options.parentTaskPrefix) {
2124
2585
  setActiveSkills(skills);
2125
2586
  setActiveRules([...rulebookRules, ...alwaysApplyRules]);
2587
+ // Claim process-cwd authority for the FIRST top-level session launched at
2588
+ // the process cwd. A later sibling launched at the same root does not get
2589
+ // the claim, so its rescope leaves process-global state (chdir,
2590
+ // capabilities, plugin caches, browser tab cwd) untouched.
2591
+ if (!isCanonicalSubSession && path.resolve(process.cwd()) === path.resolve(sessionManager.getCwd())) {
2592
+ processCwdClaimed = SessionManager.claimProcessCwdOwnership(sessionManager);
2593
+ }
2126
2594
  if (asyncJobManager) {
2127
2595
  // Register under the session endpoint so concurrent sessions'
2128
2596
  // owned work settles in the correct manager (review thread P1).
@@ -2164,7 +2632,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2164
2632
  );
2165
2633
 
2166
2634
  // Create built-in tools (already wrapped with meta notice formatting)
2167
- const builtinTools = await logger.time("createAllTools", createTools, toolSession, options.toolNames);
2635
+ const builtinTools = await logger.time(
2636
+ "createAllTools",
2637
+ createTools,
2638
+ toolSession,
2639
+ options.toolNames,
2640
+ automationTools,
2641
+ );
2168
2642
 
2169
2643
  // A top-level session loads MCP tools from three bounded surfaces: a
2170
2644
  // caller-supplied exact config (`--mcp-config`), conventional native
@@ -2172,8 +2646,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2172
2646
  // written by `gjc mcp add`; disabled by `--no-mcp`), and plugin-bundle
2173
2647
  // MCP servers (created below after `customTools` is populated). Existing
2174
2648
  // caller-supplied managers remain available for legacy in-process callers.
2175
- let mcpManager: MCPManager | undefined = options.mcpManager;
2176
- let ownsMcpManager = false;
2177
2649
  const customTools: CustomTool[] = [];
2178
2650
  const exactMcpToolNames: string[] = [];
2179
2651
  const pluginMcpToolNames: string[] = [];
@@ -2263,7 +2735,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2263
2735
  reservedToolNames: [...getReservedSubskillToolNames(), ...customTools.map(tool => tool.name)],
2264
2736
  declarations: gjcToolDeclarations,
2265
2737
  });
2266
- if (pluginToolResult.tools.length > 0) customTools.push(...pluginToolResult.tools);
2738
+ if (pluginToolResult.tools.length > 0) {
2739
+ customTools.push(...pluginToolResult.tools);
2740
+ cwdCapturingToolNames.push(...pluginToolResult.tools.map(tool => tool.name));
2741
+ }
2267
2742
  for (const q of pluginToolResult.quarantine) {
2268
2743
  gjcFindings.add({ identity: q.identity, surfaceId: q.surfaceId, code: q.code, message: q.message });
2269
2744
  logger.warn("Quarantined GJC plugin surface", { plugin: q.plugin, surface: q.surfaceId, code: q.code });
@@ -2293,6 +2768,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2293
2768
  const resultTools = result.tools as CustomTool[];
2294
2769
  exactMcpToolNames.push(...resultTools.map(tool => tool.name));
2295
2770
  customTools.push(...resultTools);
2771
+ cwdCapturingToolNames.push(...resultTools.map(tool => tool.name));
2296
2772
  if (result.errors.size > 0 || result.tools.length === 0) {
2297
2773
  logger.warn("MCP tools could not be loaded.");
2298
2774
  }
@@ -2314,6 +2790,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2314
2790
  if (options.enableMcpAutoload !== false) {
2315
2791
  try {
2316
2792
  const loaded = await loadAllMCPConfigs(cwd, {
2793
+ // User scope is this session's agent directory, the same file
2794
+ // `gjc mcp add` (user scope) writes; an SDK embedder that runs on
2795
+ // its own agent directory autoloads its own registrations.
2796
+ agentDir,
2317
2797
  // Project-scope native config loads by default; only an
2318
2798
  // explicitly configured `mcp.enableProjectConfig: false`
2319
2799
  // disables it (the legacy schema default stays false for
@@ -2383,6 +2863,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2383
2863
  mcpManager = owned;
2384
2864
  ownsMcpManager = true;
2385
2865
  customTools.push(...(result.tools as CustomTool[]));
2866
+ cwdCapturingToolNames.push(...result.tools.map(tool => tool.name));
2386
2867
  const connectedPluginNames = new Set(
2387
2868
  result.connectedServers.filter(name => pluginNames.has(name)),
2388
2869
  );
@@ -2546,6 +3027,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2546
3027
  if (notificationsExtensionEligible || sdkHostEligible) {
2547
3028
  inlineExtensions.push(async api => {
2548
3029
  try {
3030
+ if (autoroutingInactive) markAutoroutingInactive(api);
2549
3031
  if (lifecycleStartupCapability) attachLifecycleStartupCapability(api, lifecycleStartupCapability);
2550
3032
  if (lifecycleStartupCapability && process.env.GJC_SDK_TEST_FACTORY_FAILURE === cwd)
2551
3033
  throw new Error(process.env.GJC_SDK_TEST_FACTORY_SECRET ?? "Lifecycle factory test failure.");
@@ -2553,6 +3035,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2553
3035
  const createNotificationsExtension = await notificationAdapterService.get("session-extension");
2554
3036
  createNotificationsExtension(api, {
2555
3037
  settings,
3038
+
2556
3039
  controller: notificationSessionController,
2557
3040
  spawnedByGjc,
2558
3041
  sdkHostModeSupported: options.sdkHostModeSupported,
@@ -2741,14 +3224,23 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2741
3224
  const matchPreferences = {
2742
3225
  usageOrder: settings.getStorage()?.getModelUsageOrder(),
2743
3226
  };
2744
- const { model: resolved } = parseModelPattern(options.modelPattern, availableModels, matchPreferences, {
2745
- modelRegistry,
2746
- sessionId: logicalSessionId,
2747
- credentialSessionId,
2748
- });
3227
+ const { model: resolved, thinkingLevel: resolvedThinkingLevel } = parseModelPattern(
3228
+ options.modelPattern,
3229
+ availableModels,
3230
+ matchPreferences,
3231
+ {
3232
+ modelRegistry,
3233
+ sessionId: logicalSessionId,
3234
+ credentialSessionId,
3235
+ },
3236
+ );
2749
3237
  if (resolved) {
2750
3238
  model = resolved;
2751
3239
  modelFallbackMessage = undefined;
3240
+ if (resolvedThinkingLevel !== undefined) {
3241
+ thinkingLevel = resolvedThinkingLevel;
3242
+ thinkingLevelFromSchemaDefault = false;
3243
+ }
2752
3244
  if (thinkingLevelFromSchemaDefault && resolved.thinking?.defaultLevel !== undefined) {
2753
3245
  thinkingLevel = resolved.thinking.defaultLevel;
2754
3246
  thinkingLevelFromSchemaDefault = false;
@@ -2845,9 +3337,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2845
3337
  model: agent.state.model,
2846
3338
  isIdle: () => !session.isStreaming,
2847
3339
  hasQueuedMessages: () => session.queuedMessageCount > 0,
2848
- abort: () => {
2849
- session.abort();
2850
- },
3340
+ abort: () => session.abort(),
2851
3341
  settings,
2852
3342
  });
2853
3343
  const toolContextStore = new ToolContextStore(getSessionContext);
@@ -2884,6 +3374,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2884
3374
  .filter(isCustomTool)
2885
3375
  .map(tool => CustomToolAdapter.wrap(tool, customToolContext));
2886
3376
  }
3377
+ const automationToolNames = new Set(Object.keys(automationTools));
3378
+ const automationToolCollisions = [
3379
+ ...new Set(wrappedExtensionTools.map(tool => tool.name).filter(name => automationToolNames.has(name))),
3380
+ ];
3381
+ if (automationToolCollisions.length > 0) {
3382
+ throw new Error(
3383
+ `SDK automation tools cannot collide with extension or MCP tools: ${automationToolCollisions.join(", ")}`,
3384
+ );
3385
+ }
2887
3386
 
2888
3387
  // All built-in tools are active (conditional tools like git/ask return null from factory if disabled)
2889
3388
  const toolRegistry = new Map<string, Tool>();
@@ -2931,6 +3430,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2931
3430
  toolRegistry.set(tool.name, wrapped);
2932
3431
  }
2933
3432
  }
3433
+ const rawCursorReplaceEditTool = captureCursorEditTool(toolRegistry, () => new EditTool(toolSession, "replace"));
3434
+ const cursorReplaceEditTool: AgentTool | undefined = rawCursorReplaceEditTool
3435
+ ? extensionRunner
3436
+ ? (new ExtensionToolWrapper(rawCursorReplaceEditTool, extensionRunner) as AgentTool)
3437
+ : (rawCursorReplaceEditTool as AgentTool)
3438
+ : undefined;
2934
3439
  if (model?.provider === "cursor") {
2935
3440
  toolRegistry.delete("edit");
2936
3441
  builtinCandidateTools = builtinCandidateTools.filter(tool => tool.name !== "edit");
@@ -2978,6 +3483,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2978
3483
  const cursorExecHandlers = new CursorExecHandlers({
2979
3484
  cwd,
2980
3485
  tools: toolRegistry,
3486
+ getEditReplaceTool: () => cursorReplaceEditTool,
3487
+ createSearchTool: options => {
3488
+ if (!toolRegistry.has("search")) return undefined;
3489
+ const search = new SearchTool(toolSession, options);
3490
+ return extensionRunner
3491
+ ? (new ExtensionToolWrapper(search as unknown as AgentTool, extensionRunner) as AgentTool)
3492
+ : (search as unknown as AgentTool);
3493
+ },
2981
3494
  getToolContext: () => toolContextStore.getContext(),
2982
3495
  emitEvent: event => cursorEventEmitter?.(event),
2983
3496
  createEventEmitter: () => agent.createExternalEventEmitterForCurrentRun(),
@@ -3053,7 +3566,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3053
3566
  const appendPrompt: string | undefined = memoryInstructions ?? undefined;
3054
3567
  let pluginSystemAppendices = "";
3055
3568
  try {
3056
- pluginSystemAppendices = await renderAlwaysOnSystemAppendices({ cwd });
3569
+ pluginSystemAppendices = await renderAlwaysOnSystemAppendices({ cwd: getLiveCwd() });
3057
3570
  } catch (error) {
3058
3571
  gjcProducersComplete = false;
3059
3572
  logger.warn("Failed to render GJC plugin system appendices", { error: safeErrorForLog(error) });
@@ -3067,7 +3580,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3067
3580
  // `unavailable` rather than a stale generation.
3068
3581
  if (gjcProducersComplete) gjcRuntimeStore.publish(gjcFindings.snapshot(), gjcPassEpoch);
3069
3582
  const defaultPrompt = await buildSystemPromptInternal({
3070
- cwd,
3583
+ // Live cwd: the prompt is rebuilt after a rescope, and describing the
3584
+ // retired launcher root there is what makes the model pick wrong paths.
3585
+ cwd: getLiveCwd(),
3071
3586
  skills,
3072
3587
  contextFiles,
3073
3588
  tools: promptTools,
@@ -3600,7 +4115,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3600
4115
  convertToLlm: convertToLlmFinal,
3601
4116
  rebuildSystemPrompt,
3602
4117
  getMcpServerInstructions:
3603
- explicitMcpConfigPath === undefined && mcpManager ? () => mcpManager.getServerInstructions() : undefined,
4118
+ explicitMcpConfigPath === undefined ? () => mcpManager?.getServerInstructions() : undefined,
3604
4119
  workspaceTree: options.workspaceTree ?? (workspaceTreeMode === "eager" ? resolvedWorkspaceTree : undefined),
3605
4120
  workspaceTreeService: options.workspaceTree ? undefined : runtimeServices.workspaceTree,
3606
4121
  networkPrewarmService: runtimeServices.networkPrewarm,
@@ -3640,6 +4155,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3640
4155
  });
3641
4156
  session.setActiveModelProfile(startupActiveModelProfile);
3642
4157
  session.configWarnings.push(...contextFileWarnings);
4158
+ // Determined once, here, where settings are already available. Keep the
4159
+ // durable warning for interactive and print consumers; ACP delivery is
4160
+ // carried by the host replay ring through the internal runtime seam above.
4161
+ if (autoroutingInactive) session.configWarnings.push(AUTOROUTING_INACTIVE_WARNING);
3643
4162
  hasSession = true;
3644
4163
  const sessionAsyncJobManager = asyncJobManager;
3645
4164
  if (sessionAsyncJobManager) {
@@ -3792,35 +4311,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3792
4311
  void session.refreshMCPTools(tools);
3793
4312
  });
3794
4313
  }
3795
- // Wire prompt refresh → rebuild MCP prompt slash commands
3796
- mcpManager.setOnPromptsChanged(serverName => {
3797
- const promptCommands = buildMCPPromptCommands(mcpManager);
3798
- session.setMCPPromptCommands(promptCommands);
3799
- logger.debug("MCP prompt commands refreshed", { path: `mcp:${serverName}` });
3800
- });
3801
- const notificationDebounceTimers = new Map<string, Timer>();
3802
4314
  const clearDebounceTimers = () => {
3803
4315
  for (const timer of notificationDebounceTimers.values()) clearTimeout(timer);
3804
4316
  notificationDebounceTimers.clear();
3805
4317
  };
3806
4318
  postmortem.register("mcp-notification-cleanup", clearDebounceTimers);
3807
- mcpManager.setOnResourcesChanged((serverName, uri) => {
3808
- logger.debug("MCP resources changed", { path: `mcp:${serverName}`, uri });
3809
- if (!settings.get("mcp.notifications")) return;
3810
- const debounceMs = settings.get("mcp.notificationDebounceMs");
3811
- const key = `${serverName}:${uri}`;
3812
- const existing = notificationDebounceTimers.get(key);
3813
- if (existing) clearTimeout(existing);
3814
- notificationDebounceTimers.set(
3815
- key,
3816
- setTimeout(() => {
3817
- notificationDebounceTimers.delete(key);
3818
- // Re-check: user may have disabled notifications during the debounce window
3819
- if (!settings.get("mcp.notifications")) return;
3820
- session.yieldQueue.enqueue<McpNotificationEntry>("mcp-notification", { serverName, uri });
3821
- }, debounceMs),
3822
- );
3823
- });
4319
+ wireMcpManagerCallbacks(mcpManager);
3824
4320
  }
3825
4321
 
3826
4322
  // Constructor-time workflow-gate tool restoration is deferred by one
@@ -3934,6 +4430,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3934
4430
  cleanupDiagnostic: safeCleanupDiagnosticForLog(cleanupDiagnostic),
3935
4431
  });
3936
4432
  } finally {
4433
+ if (processCwdClaimed) {
4434
+ SessionManager.releaseProcessCwdOwnership(sessionManager);
4435
+ processCwdClaimed = false;
4436
+ }
3937
4437
  releaseLocalProtocolOverride();
3938
4438
  try {
3939
4439
  closeOwnedAuthStorage();