@gajae-code/coding-agent 0.18.11 → 0.19.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 (462) hide show
  1. package/CHANGELOG.md +760 -0
  2. package/dist/types/async/job-manager.d.ts +7 -3
  3. package/dist/types/async/result-formatting.d.ts +2 -0
  4. package/dist/types/autoresearch/git.d.ts +1 -1
  5. package/dist/types/capability/fs.d.ts +2 -2
  6. package/dist/types/capability/rule.d.ts +7 -2
  7. package/dist/types/cli/accounts-cli.d.ts +2 -2
  8. package/dist/types/cli/append-system-prompt.d.ts +6 -0
  9. package/dist/types/cli/args.d.ts +7 -2
  10. package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
  11. package/dist/types/cli/customize-doctor.d.ts +4 -3
  12. package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
  13. package/dist/types/cli/install-activation.d.ts +10 -4
  14. package/dist/types/cli/public-command-errors.d.ts +2 -0
  15. package/dist/types/cli/root-flags.d.ts +0 -12
  16. package/dist/types/cli/update-cli.d.ts +21 -3
  17. package/dist/types/cli-main.d.ts +0 -6
  18. package/dist/types/cli-ordinary.d.ts +2 -2
  19. package/dist/types/commands/accounts.d.ts +2 -1
  20. package/dist/types/commands/launch.d.ts +0 -6
  21. package/dist/types/commands/migrate.d.ts +1 -1
  22. package/dist/types/commands/sdk.d.ts +13 -0
  23. package/dist/types/commit/changelog/index.d.ts +1 -0
  24. package/dist/types/commit/map-reduce/index.d.ts +4 -4
  25. package/dist/types/config/autorouting-tier-map.d.ts +53 -1
  26. package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
  27. package/dist/types/config/config-hot-reload.d.ts +44 -0
  28. package/dist/types/config/file-lock.d.ts +52 -3
  29. package/dist/types/config/model-discovery-manager.d.ts +2 -0
  30. package/dist/types/config/model-profile-activation.d.ts +79 -9
  31. package/dist/types/config/model-registry.d.ts +72 -8
  32. package/dist/types/config/model-resolver.d.ts +7 -4
  33. package/dist/types/config/models-config-schema.d.ts +10 -0
  34. package/dist/types/config/settings-schema.d.ts +57 -4
  35. package/dist/types/config/settings.d.ts +33 -1
  36. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  37. package/dist/types/customization/import.d.ts +2 -0
  38. package/dist/types/customization/types.d.ts +1 -1
  39. package/dist/types/dap/config.d.ts +8 -4
  40. package/dist/types/dap/session.d.ts +11 -2
  41. package/dist/types/debug/system-info.d.ts +6 -1
  42. package/dist/types/discovery/helpers.d.ts +7 -2
  43. package/dist/types/discovery/mcp-compat.d.ts +3 -1
  44. package/dist/types/edit/index.d.ts +5 -0
  45. package/dist/types/edit/modes/patch.d.ts +14 -0
  46. package/dist/types/edit/modes/replace.d.ts +3 -0
  47. package/dist/types/eval/py/runtime.d.ts +9 -0
  48. package/dist/types/exec/bash-executor.d.ts +12 -0
  49. package/dist/types/export/ttsr.d.ts +19 -0
  50. package/dist/types/extensibility/extensions/types.d.ts +5 -1
  51. package/dist/types/extensibility/slash-commands.d.ts +1 -0
  52. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
  53. package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
  54. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
  55. package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
  56. package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
  57. package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
  58. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
  59. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  60. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  61. package/dist/types/hindsight/content.d.ts +5 -6
  62. package/dist/types/internal-urls/types.d.ts +2 -0
  63. package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
  64. package/dist/types/main.d.ts +23 -18
  65. package/dist/types/migrate/action-planner.d.ts +10 -1
  66. package/dist/types/migrate/adapters/codex.d.ts +2 -1
  67. package/dist/types/migrate/adapters/index.d.ts +42 -1
  68. package/dist/types/migrate/executor.d.ts +3 -1
  69. package/dist/types/migrate/hook-module.d.ts +23 -0
  70. package/dist/types/migrate/report.d.ts +2 -1
  71. package/dist/types/migrate/types.d.ts +82 -3
  72. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  73. package/dist/types/modes/components/model-selector.d.ts +43 -0
  74. package/dist/types/modes/components/settings-selector.d.ts +6 -0
  75. package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
  76. package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
  77. package/dist/types/modes/theme/theme.d.ts +1 -0
  78. package/dist/types/modes/utils/host-status.d.ts +15 -11
  79. package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
  80. package/dist/types/runtime-mcp/manager.d.ts +7 -0
  81. package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
  82. package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
  83. package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
  84. package/dist/types/sdk/broker/broker-exit.d.ts +3 -3
  85. package/dist/types/sdk/broker/broker.d.ts +29 -2
  86. package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
  87. package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
  88. package/dist/types/sdk/broker/ensure.d.ts +141 -9
  89. package/dist/types/sdk/broker/hop.d.ts +45 -0
  90. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
  91. package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
  92. package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
  93. package/dist/types/sdk/broker/session-index.d.ts +6 -1
  94. package/dist/types/sdk/broker/transport.d.ts +2 -0
  95. package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
  96. package/dist/types/sdk/bus/index.d.ts +44 -0
  97. package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
  98. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -1
  99. package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
  100. package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
  101. package/dist/types/sdk/cli/session-cli.d.ts +0 -1
  102. package/dist/types/sdk/host/control/operations.d.ts +5 -1
  103. package/dist/types/sdk/host/events.d.ts +4 -0
  104. package/dist/types/sdk/host/host.d.ts +2 -1
  105. package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
  106. package/dist/types/sdk/host/session-runtime.d.ts +40 -8
  107. package/dist/types/sdk/host/surface-policy.d.ts +2 -1
  108. package/dist/types/sdk/lifecycle/service.d.ts +12 -0
  109. package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
  110. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
  111. package/dist/types/sdk/prompt-status.d.ts +11 -4
  112. package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
  113. package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
  114. package/dist/types/sdk/session-list.d.ts +1 -1
  115. package/dist/types/session/agent-session.d.ts +107 -9
  116. package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
  117. package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
  118. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
  119. package/dist/types/session/messages.d.ts +5 -1
  120. package/dist/types/session/session-manager.d.ts +29 -3
  121. package/dist/types/session/startup-auth-config.d.ts +24 -2
  122. package/dist/types/session/streaming-output.d.ts +11 -0
  123. package/dist/types/session/terminal-abort.d.ts +3 -1
  124. package/dist/types/session-import/service.d.ts +1 -1
  125. package/dist/types/skill-state/workflow-mutation-guard.d.ts +20 -4
  126. package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
  127. package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
  128. package/dist/types/ssh/ssh-executor.d.ts +8 -1
  129. package/dist/types/system-prompt.d.ts +3 -2
  130. package/dist/types/task/executor.d.ts +19 -2
  131. package/dist/types/task/role-body.d.ts +11 -0
  132. package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
  133. package/dist/types/tools/atomic-file-write.d.ts +9 -7
  134. package/dist/types/tools/awaiting-input.d.ts +28 -0
  135. package/dist/types/tools/bash-allowed-prefixes.d.ts +17 -0
  136. package/dist/types/tools/bash-skill-urls.d.ts +4 -0
  137. package/dist/types/tools/bash.d.ts +7 -0
  138. package/dist/types/tools/descriptors.d.ts +15 -2
  139. package/dist/types/tools/fetch.d.ts +7 -1
  140. package/dist/types/tools/index.d.ts +2 -0
  141. package/dist/types/tools/irc.d.ts +5 -3
  142. package/dist/types/tools/python.d.ts +1 -1
  143. package/dist/types/tools/report-issue.d.ts +41 -0
  144. package/dist/types/tools/subagent.d.ts +23 -0
  145. package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
  146. package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
  147. package/dist/types/tools/write.d.ts +2 -0
  148. package/dist/types/utils/file-mentions.d.ts +5 -0
  149. package/dist/types/utils/image-limits.d.ts +7 -0
  150. package/dist/types/utils/image-loading.d.ts +0 -1
  151. package/dist/types/utils/open.d.ts +0 -1
  152. package/dist/types/utils/pasted-image-loading.d.ts +1 -5
  153. package/dist/types/utils/prompt-suggestion.d.ts +2 -2
  154. package/dist/types/utils/shell-snapshot.d.ts +2 -0
  155. package/dist/types/utils/title-generator.d.ts +13 -3
  156. package/dist/types/web/scrapers/types.d.ts +8 -1
  157. package/dist/types/web/search/providers/codex.d.ts +6 -7
  158. package/dist/types/web/search/providers/insane.d.ts +7 -1
  159. package/dist/types/web/search/providers/searxng.d.ts +5 -0
  160. package/package.json +10 -9
  161. package/scripts/acp-conformance-agent.ts +12 -2
  162. package/scripts/build-binary.ts +2 -1
  163. package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
  164. package/scripts/generate-docs-index.ts +8 -1
  165. package/scripts/generate-sdk-operation-inventory.ts +38 -4
  166. package/src/async/job-manager.ts +356 -56
  167. package/src/async/result-formatting.ts +40 -0
  168. package/src/autoresearch/git.ts +9 -2
  169. package/src/autoresearch/prompts/prompt-setup.md +1 -1
  170. package/src/capability/fs.ts +58 -22
  171. package/src/capability/index.ts +2 -2
  172. package/src/capability/rule.ts +11 -2
  173. package/src/cli/accounts-cli.ts +176 -8
  174. package/src/cli/append-system-prompt.ts +12 -0
  175. package/src/cli/args.ts +11 -7
  176. package/src/cli/auth-broker-cli.ts +21 -11
  177. package/src/cli/customize-doctor-precedence.ts +31 -0
  178. package/src/cli/customize-doctor.ts +49 -39
  179. package/src/cli/doctor/install-repairs.ts +35 -5
  180. package/src/cli/fast-help.ts +1 -3
  181. package/src/cli/install-activation.ts +47 -4
  182. package/src/cli/migrate-cli.ts +19 -6
  183. package/src/cli/public-command-errors.ts +6 -0
  184. package/src/cli/root-flags.ts +2 -4
  185. package/src/cli/stats-cli.ts +9 -5
  186. package/src/cli/update-cli.ts +471 -13
  187. package/src/cli-ordinary.ts +7 -2
  188. package/src/commands/accounts.ts +10 -5
  189. package/src/commands/harness.ts +25 -15
  190. package/src/commands/migrate.ts +3 -3
  191. package/src/commands/sdk.ts +277 -50
  192. package/src/commands/ultragoal.ts +16 -8
  193. package/src/commit/agentic/agent.ts +6 -6
  194. package/src/commit/analysis/conventional.ts +14 -5
  195. package/src/commit/changelog/index.ts +39 -1
  196. package/src/commit/map-reduce/index.ts +7 -7
  197. package/src/commit/message.ts +10 -3
  198. package/src/commit/pipeline.ts +12 -12
  199. package/src/config/autorouting-tier-map.ts +50 -1
  200. package/src/config/config-file.ts +106 -3
  201. package/src/config/config-hot-reload-trust.ts +148 -0
  202. package/src/config/config-hot-reload.ts +742 -0
  203. package/src/config/file-lock.ts +1615 -328
  204. package/src/config/keybindings.ts +154 -7
  205. package/src/config/model-bindings-applier.ts +85 -47
  206. package/src/config/model-discovery-manager.ts +10 -0
  207. package/src/config/model-profile-activation.ts +889 -148
  208. package/src/config/model-registry.ts +1706 -205
  209. package/src/config/model-resolver.ts +52 -22
  210. package/src/config/models-config-schema.ts +6 -0
  211. package/src/config/settings-schema.ts +73 -4
  212. package/src/config/settings.ts +460 -24
  213. package/src/coordinator-mcp/policy.ts +26 -1
  214. package/src/coordinator-mcp/server.ts +37 -2
  215. package/src/crash/sanitize.ts +7 -1
  216. package/src/customization/import.ts +279 -8
  217. package/src/customization/inventory.ts +24 -8
  218. package/src/customization/types.ts +1 -0
  219. package/src/dap/client.ts +3 -2
  220. package/src/dap/config.ts +31 -11
  221. package/src/dap/defaults.json +1 -1
  222. package/src/dap/session.ts +161 -36
  223. package/src/debug/crash-diagnostics.ts +89 -6
  224. package/src/debug/report-bundle.ts +90 -2
  225. package/src/debug/system-info.ts +8 -3
  226. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
  227. package/src/discovery/agents-md.ts +1 -1
  228. package/src/discovery/builtin.ts +4 -3
  229. package/src/discovery/helpers.ts +25 -16
  230. package/src/discovery/mcp-compat.ts +120 -17
  231. package/src/edit/apply-patch/index.ts +12 -1
  232. package/src/edit/index.ts +204 -22
  233. package/src/edit/modes/patch.ts +214 -8
  234. package/src/edit/modes/replace.ts +136 -0
  235. package/src/eval/js/context-manager.ts +29 -3
  236. package/src/eval/js/worker-entry.ts +51 -18
  237. package/src/eval/py/executor.ts +14 -4
  238. package/src/eval/py/runner.py +144 -5
  239. package/src/eval/py/runtime.ts +44 -4
  240. package/src/exec/bash-executor.ts +81 -4
  241. package/src/exec/bash-shell-guardian.ts +4 -1
  242. package/src/exec/bash-shell-supervisor.ts +8 -4
  243. package/src/exec/isolated-shell.ts +5 -1
  244. package/src/exec/non-interactive-env.ts +0 -1
  245. package/src/export/html/index.ts +86 -6
  246. package/src/export/ttsr.ts +176 -1
  247. package/src/extensibility/custom-tools/wrapper.ts +3 -1
  248. package/src/extensibility/extensions/loader.ts +10 -1
  249. package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
  250. package/src/extensibility/extensions/types.ts +5 -1
  251. package/src/extensibility/hooks/loader.ts +10 -2
  252. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
  253. package/src/extensibility/plugins/marketplace/manager.ts +36 -10
  254. package/src/extensibility/slash-commands.ts +5 -0
  255. package/src/gjc-runtime/gc-runtime.ts +37 -7
  256. package/src/gjc-runtime/launch-tmux.ts +30 -15
  257. package/src/gjc-runtime/launch-worktree.test.ts +148 -0
  258. package/src/gjc-runtime/launch-worktree.ts +101 -14
  259. package/src/gjc-runtime/managed-owner-admission.ts +46 -2
  260. package/src/gjc-runtime/ralplan-current-final.ts +294 -0
  261. package/src/gjc-runtime/ralplan-runtime.ts +95 -1
  262. package/src/gjc-runtime/session-resolution.ts +38 -6
  263. package/src/gjc-runtime/session-state-lock.ts +59 -14
  264. package/src/gjc-runtime/state-runtime.ts +23 -19
  265. package/src/gjc-runtime/state-writer.ts +109 -5
  266. package/src/gjc-runtime/tmux-sessions.ts +93 -13
  267. package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
  268. package/src/gjc-runtime/ultragoal-guard.ts +9 -1
  269. package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
  270. package/src/harness-control-plane/finalize.ts +11 -0
  271. package/src/harness-control-plane/receipt-spool.ts +105 -28
  272. package/src/harness-control-plane/storage.ts +124 -6
  273. package/src/hindsight/content.ts +116 -16
  274. package/src/hindsight/mental-models.ts +22 -10
  275. package/src/hindsight/state.ts +7 -4
  276. package/src/hooks/native-skill-hook.ts +17 -12
  277. package/src/internal-urls/artifact-protocol.ts +3 -1
  278. package/src/internal-urls/docs-index.generated.ts +23 -22
  279. package/src/internal-urls/local-protocol.ts +167 -8
  280. package/src/internal-urls/types.ts +2 -0
  281. package/src/lsp/client.ts +2 -1
  282. package/src/lsp/clients/biome-client.ts +2 -0
  283. package/src/lsp/clients/swiftlint-client.ts +2 -0
  284. package/src/lsp/index.ts +29 -0
  285. package/src/lsp/workspace-path.ts +23 -0
  286. package/src/main.ts +330 -92
  287. package/src/memories/index.ts +25 -2
  288. package/src/migrate/action-planner.ts +574 -2
  289. package/src/migrate/adapters/claude-code.ts +162 -5
  290. package/src/migrate/adapters/codex.ts +77 -7
  291. package/src/migrate/adapters/index.ts +258 -5
  292. package/src/migrate/adapters/opencode.ts +10 -1
  293. package/src/migrate/executor.ts +222 -7
  294. package/src/migrate/hook-module.ts +193 -0
  295. package/src/migrate/report.ts +63 -2
  296. package/src/migrate/types.ts +84 -4
  297. package/src/modes/DESIGN.md +51 -0
  298. package/src/modes/acp/acp-agent.ts +10 -1
  299. package/src/modes/acp/permission-mode.ts +18 -1
  300. package/src/modes/components/agent-dashboard.ts +20 -4
  301. package/src/modes/components/assistant-message.ts +55 -1
  302. package/src/modes/components/customization/customization-dashboard.ts +7 -0
  303. package/src/modes/components/customization/import-wizard.ts +29 -3
  304. package/src/modes/components/model-selector.ts +157 -103
  305. package/src/modes/components/settings-selector.ts +103 -37
  306. package/src/modes/components/welcome.ts +3 -1
  307. package/src/modes/controllers/command-controller.ts +300 -9
  308. package/src/modes/controllers/event-controller.ts +1 -1
  309. package/src/modes/controllers/extension-ui-controller.ts +31 -1
  310. package/src/modes/controllers/goal-mode-controller.ts +79 -10
  311. package/src/modes/controllers/input-controller.ts +2 -0
  312. package/src/modes/controllers/plan-mode-controller.ts +66 -11
  313. package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
  314. package/src/modes/controllers/selector-controller.ts +76 -7
  315. package/src/modes/interactive-mode.ts +123 -4
  316. package/src/modes/prompt-suggestion-controller.ts +1 -1
  317. package/src/modes/runtime-init.ts +5 -1
  318. package/src/modes/theme/theme.ts +15 -0
  319. package/src/modes/utils/host-status.ts +21 -10
  320. package/src/prompts/agent-fragments/browser-aside.md +2 -2
  321. package/src/prompts/system/irc-incoming.md +3 -1
  322. package/src/prompts/tools/debug.md +1 -1
  323. package/src/prompts/tools/irc.md +1 -1
  324. package/src/prompts/tools/read.md +1 -1
  325. package/src/prompts/tools/report-issue.md +13 -0
  326. package/src/prompts/tools/task.md +1 -1
  327. package/src/runtime-credential-selector.ts +2 -2
  328. package/src/runtime-mcp/content-limits.ts +7 -0
  329. package/src/runtime-mcp/manager-pool.test.ts +5 -4
  330. package/src/runtime-mcp/manager.ts +40 -3
  331. package/src/runtime-mcp/oauth-flow.ts +32 -6
  332. package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
  333. package/src/runtime-mcp/project-host-values.ts +12 -8
  334. package/src/runtime-mcp/render.ts +16 -1
  335. package/src/runtime-mcp/tool-bridge.ts +194 -21
  336. package/src/runtime-mcp/transports/stdio.ts +2 -1
  337. package/src/sdk/broker/broker-exit.ts +10 -5
  338. package/src/sdk/broker/broker.ts +484 -85
  339. package/src/sdk/broker/daemon-entry.ts +338 -116
  340. package/src/sdk/broker/doctor-restart.ts +2 -1
  341. package/src/sdk/broker/ensure.ts +1214 -268
  342. package/src/sdk/broker/hop.ts +286 -0
  343. package/src/sdk/broker/lifecycle-ledger.ts +38 -11
  344. package/src/sdk/broker/lifecycle.ts +102 -6
  345. package/src/sdk/broker/lock-host-identity.ts +29 -0
  346. package/src/sdk/broker/session-index.ts +91 -16
  347. package/src/sdk/broker/transport.ts +17 -1
  348. package/src/sdk/bus/chat-command-policy.ts +16 -3
  349. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  350. package/src/sdk/bus/index.ts +460 -35
  351. package/src/sdk/bus/reconciliation-store.ts +201 -8
  352. package/src/sdk/bus/session-control.ts +2 -0
  353. package/src/sdk/bus/slack-daemon.ts +105 -9
  354. package/src/sdk/bus/telegram-daemon-contract.ts +9 -1
  355. package/src/sdk/bus/telegram-daemon.ts +10 -2
  356. package/src/sdk/bus/threaded-render.ts +20 -1
  357. package/src/sdk/cli/session-cli.ts +23 -6
  358. package/src/sdk/host/control/dispatch.ts +13 -1
  359. package/src/sdk/host/control/operations.ts +10 -1
  360. package/src/sdk/host/events.ts +67 -4
  361. package/src/sdk/host/host.ts +8 -2
  362. package/src/sdk/host/prompt-image-upload.ts +438 -0
  363. package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
  364. package/src/sdk/host/session-runtime.test.ts +1737 -129
  365. package/src/sdk/host/session-runtime.ts +684 -83
  366. package/src/sdk/host/surface-policy.ts +4 -0
  367. package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
  368. package/src/sdk/host/websocket-transport.ts +19 -7
  369. package/src/sdk/lifecycle/service.ts +15 -1
  370. package/src/sdk/prompt-deadline-flush.ts +78 -5
  371. package/src/sdk/prompt-deadline-manager.ts +68 -14
  372. package/src/sdk/prompt-status.ts +6 -4
  373. package/src/sdk/protocol/adapter-validation.ts +81 -12
  374. package/src/sdk/protocol/operation-inventory.generated.json +261 -2
  375. package/src/sdk/protocol/operation-registry.ts +33 -3
  376. package/src/sdk/reconciliation-extensions.ts +1 -0
  377. package/src/sdk/session-list.ts +1 -0
  378. package/src/sdk/session.ts +237 -97
  379. package/src/session/agent-session.ts +2726 -479
  380. package/src/session/history-storage.ts +182 -12
  381. package/src/session/internal/managed-session-scope.ts +173 -57
  382. package/src/session/internal/managed-session-storage.ts +85 -12
  383. package/src/session/internal/native-publish-outcome.ts +5 -1
  384. package/src/session/messages.ts +47 -6
  385. package/src/session/session-manager.ts +326 -19
  386. package/src/session/startup-auth-config.ts +116 -8
  387. package/src/session/streaming-output.ts +262 -4
  388. package/src/session/terminal-abort.ts +14 -2
  389. package/src/session-import/redact.ts +53 -2
  390. package/src/session-import/service.ts +1 -0
  391. package/src/skill-state/workflow-mutation-guard.ts +2145 -206
  392. package/src/slash-commands/builtin-registry.ts +21 -6
  393. package/src/slash-commands/helpers/credential-switch.ts +10 -5
  394. package/src/slash-commands/helpers/mcp.ts +11 -3
  395. package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
  396. package/src/ssh/ssh-executor.ts +9 -2
  397. package/src/stt/recorder.ts +26 -9
  398. package/src/system-prompt.ts +61 -16
  399. package/src/task/executor.ts +115 -20
  400. package/src/task/index.ts +389 -175
  401. package/src/task/role-body.ts +84 -0
  402. package/src/task/token-log-mode.test.ts +162 -0
  403. package/src/task/token-log.ts +91 -2
  404. package/src/tools/archive-reader.ts +650 -37
  405. package/src/tools/ask.ts +1 -0
  406. package/src/tools/ast-edit-write-path.ts +64 -0
  407. package/src/tools/ast-edit.ts +18 -1
  408. package/src/tools/ast-grep.ts +7 -2
  409. package/src/tools/atomic-file-write.ts +46 -15
  410. package/src/tools/auto-generated-guard.ts +15 -3
  411. package/src/tools/awaiting-input.ts +114 -0
  412. package/src/tools/bash-allowed-prefixes.ts +214 -6
  413. package/src/tools/bash-interactive.ts +14 -2
  414. package/src/tools/bash-skill-urls.ts +58 -5
  415. package/src/tools/bash.ts +113 -26
  416. package/src/tools/browser/tab-worker.ts +39 -13
  417. package/src/tools/browser.ts +17 -7
  418. package/src/tools/cron.ts +10 -1
  419. package/src/tools/debug.ts +16 -8
  420. package/src/tools/descriptors.test.ts +44 -0
  421. package/src/tools/descriptors.ts +50 -6
  422. package/src/tools/eval.ts +15 -0
  423. package/src/tools/fetch.ts +93 -4
  424. package/src/tools/gh.ts +27 -12
  425. package/src/tools/image-gen.ts +20 -6
  426. package/src/tools/index.ts +2 -0
  427. package/src/tools/irc.ts +5 -3
  428. package/src/tools/monitor.ts +9 -1
  429. package/src/tools/output-meta.ts +74 -5
  430. package/src/tools/python.ts +1 -1
  431. package/src/tools/read.ts +26 -37
  432. package/src/tools/recipe/runner.ts +23 -12
  433. package/src/tools/render-utils.ts +2 -2
  434. package/src/tools/report-issue.ts +184 -0
  435. package/src/tools/search.ts +3 -2
  436. package/src/tools/ssh.ts +274 -8
  437. package/src/tools/subagent-render.ts +114 -12
  438. package/src/tools/subagent.ts +118 -5
  439. package/src/tools/tmux-self-injection-guard.ts +3301 -122
  440. package/src/tools/todo-write.ts +41 -3
  441. package/src/tools/tool-catalog.generated.ts +32 -4
  442. package/src/tools/ultragoal-ask-guard.ts +13 -1
  443. package/src/tools/write.ts +42 -3
  444. package/src/tui/tree-list.ts +15 -1
  445. package/src/utils/commit-message-generator.ts +12 -4
  446. package/src/utils/external-editor.ts +21 -7
  447. package/src/utils/file-mentions.ts +75 -7
  448. package/src/utils/git.ts +38 -7
  449. package/src/utils/image-limits.ts +8 -0
  450. package/src/utils/image-loading.ts +1 -1
  451. package/src/utils/open.ts +73 -4
  452. package/src/utils/pasted-image-loading.ts +9 -8
  453. package/src/utils/prompt-suggestion.ts +5 -4
  454. package/src/utils/shell-snapshot.ts +9 -1
  455. package/src/utils/title-generator.ts +72 -22
  456. package/src/web/scrapers/index.ts +3 -1
  457. package/src/web/scrapers/types.ts +8 -0
  458. package/src/web/scrapers/youtube.ts +3 -2
  459. package/src/web/search/index.ts +2 -1
  460. package/src/web/search/providers/codex.ts +29 -32
  461. package/src/web/search/providers/insane.ts +91 -28
  462. package/src/web/search/providers/searxng.ts +62 -10
@@ -6,10 +6,12 @@
6
6
  */
7
7
 
8
8
  import * as fs from "node:fs";
9
+ import * as os from "node:os";
9
10
  import * as path from "node:path";
10
11
  import type { AgentToolResult } from "@gajae-code/agent-core";
11
12
  import { isEnoent } from "@gajae-code/utils";
12
13
  import * as z from "zod/v4";
14
+ import type { Settings } from "../../config/settings";
13
15
  import {
14
16
  type FileDiagnosticsResult,
15
17
  flushLspWritethroughBatch,
@@ -80,6 +82,17 @@ export interface ApplyPatchOptions {
80
82
  * object identity is not a durable-lock capability probe.
81
83
  */
82
84
  crossProcessLock?: boolean;
85
+ /** Explicit artifacts `local` root for this call. The caller session owns it. */
86
+ localRoot?: string;
87
+ signal?: AbortSignal;
88
+ /**
89
+ * Recheck a rename destination while this call holds the path mutation lock.
90
+ * The request preflight runs before that lock.
91
+ */
92
+ destinationGuard?: {
93
+ settings: Pick<Settings, "get">;
94
+ displayPath: string;
95
+ };
83
96
  }
84
97
 
85
98
  // ═══════════════════════════════════════════════════════════════════════════
@@ -117,6 +130,117 @@ function loadNativePatchBindings(): Promise<NativePatchBindings> {
117
130
  return nativePatchBindingsPromise;
118
131
  }
119
132
 
133
+ function explicitLocalRootForSession(session: ToolSession): string | undefined {
134
+ const artifactsDir = session.getArtifactsDir?.();
135
+ if (!artifactsDir || session.isManagedSessionDestination?.()) return undefined;
136
+ return path.resolve(artifactsDir, "local");
137
+ }
138
+
139
+ function pathIsWithin(target: string, root: string): boolean {
140
+ const relative = path.relative(root, target);
141
+ return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
142
+ }
143
+
144
+ /**
145
+ * Trust root for a delete. `localRoot` is the caller session's explicit
146
+ * artifacts root. `gjc-local/<id>` is recognized from the path. Else `cwd`.
147
+ */
148
+ function boundaryForPatchDelete(cwd: string, absolutePath: string, localRoot?: string): string {
149
+ const resolved = path.resolve(absolutePath);
150
+ if (localRoot) {
151
+ const explicit = path.resolve(localRoot);
152
+ if (pathIsWithin(resolved, explicit)) return explicit;
153
+ }
154
+ const managedParent = path.join(os.tmpdir(), "gjc-local");
155
+ if (pathIsWithin(resolved, managedParent)) {
156
+ const rest = resolved.slice(managedParent.length + path.sep.length);
157
+ const sessionSegment = rest.split(path.sep, 1)[0] ?? "";
158
+ if (sessionSegment.length > 0) return path.join(managedParent, sessionSegment);
159
+ }
160
+ return path.resolve(cwd);
161
+ }
162
+
163
+ /**
164
+ * `unlink` follows a directory symlink. Resolve the parent only, then delete
165
+ * that real parent plus the final name so a file symlink is removed as a link.
166
+ */
167
+ async function resolvePatchDeletePath(
168
+ root: string,
169
+ absolutePath: string,
170
+ displayPath: string,
171
+ signal?: AbortSignal,
172
+ ): Promise<string> {
173
+ signal?.throwIfAborted();
174
+ let realRoot: string;
175
+ try {
176
+ realRoot = await fs.promises.realpath(root);
177
+ } catch (error) {
178
+ if (isEnoent(error)) throw new ApplyPatchError(`File not found: ${displayPath}`);
179
+ throw error;
180
+ }
181
+ signal?.throwIfAborted();
182
+ let realParent: string;
183
+ try {
184
+ realParent = await fs.promises.realpath(path.dirname(absolutePath));
185
+ } catch (error) {
186
+ if (isEnoent(error)) throw new ApplyPatchError(`File not found: ${displayPath}`);
187
+ throw error;
188
+ }
189
+ signal?.throwIfAborted();
190
+ if (!pathIsWithin(realParent, realRoot)) {
191
+ throw new ApplyPatchError(`Delete target resolves outside its root: ${displayPath}`);
192
+ }
193
+ return path.join(realParent, path.basename(absolutePath));
194
+ }
195
+
196
+ /**
197
+ * `mkdir` follows a directory symlink. Walk to the nearest existing ancestor
198
+ * and return its real path plus the missing suffix. A parent that is already
199
+ * lexically outside the workspace or session local root is unchanged.
200
+ */
201
+ async function resolvePatchMkdirPath(
202
+ cwd: string,
203
+ absolutePath: string,
204
+ localRoot: string | undefined,
205
+ parentDir: string,
206
+ displayPath: string,
207
+ signal?: AbortSignal,
208
+ ): Promise<string> {
209
+ signal?.throwIfAborted();
210
+ const boundary = path.resolve(boundaryForPatchDelete(cwd, absolutePath, localRoot));
211
+ const lexicalParent = path.resolve(parentDir);
212
+ if (!pathIsWithin(lexicalParent, boundary)) return parentDir;
213
+ // A root that does not exist yet is not an escape. Keep the missing suffix
214
+ // under its real ancestor, which is what mkdir did before this check.
215
+ const realRoot = await realpathThroughExistingAncestor(boundary, signal);
216
+ signal?.throwIfAborted();
217
+ const realParent = await realpathThroughExistingAncestor(lexicalParent, signal);
218
+ signal?.throwIfAborted();
219
+ if (!pathIsWithin(realParent, realRoot)) {
220
+ throw new ApplyPatchError(`Parent directory resolves outside its root: ${displayPath}`);
221
+ }
222
+ return realParent;
223
+ }
224
+
225
+ async function realpathThroughExistingAncestor(target: string, signal?: AbortSignal): Promise<string> {
226
+ const resolved = path.resolve(target);
227
+ const missing: string[] = [];
228
+ let cursor = resolved;
229
+ for (;;) {
230
+ signal?.throwIfAborted();
231
+ try {
232
+ const real = await fs.promises.realpath(cursor);
233
+ return missing.length === 0 ? real : path.join(real, ...missing.reverse());
234
+ } catch (error) {
235
+ if (!isEnoent(error)) throw error;
236
+ const parent = path.dirname(cursor);
237
+ if (parent === cursor) return resolved;
238
+ missing.push(path.basename(cursor));
239
+ cursor = parent;
240
+ }
241
+ }
242
+ }
243
+
120
244
  async function readExistingPatchFile(fileSystem: FileSystem, absolutePath: string, path: string): Promise<string> {
121
245
  try {
122
246
  return await fileSystem.read(absolutePath);
@@ -160,6 +284,9 @@ export async function applyPatch(input: PatchInput, options: ApplyPatchOptions):
160
284
  const destPath = resolvePath(input.rename);
161
285
  if (destPath !== absolutePath) mutationPaths.push(destPath);
162
286
  }
287
+ // The cross-process lock creates missing parent directories for `<path>.lock`.
288
+ // Check every locked create/move path, including a rename on create, first.
289
+ await preflightPatchMkdir(input, options);
163
290
  const usingDefaultFs = options.fs === undefined || options.fs === defaultFileSystem;
164
291
  // dryRun/preview must stay read-only: never create durable `<path>.lock` dirs
165
292
  // (would fail on read-only parents and mutate the FS during preview) (#2900 review).
@@ -167,6 +294,36 @@ export async function applyPatch(input: PatchInput, options: ApplyPatchOptions):
167
294
  return withEditPathMutation(mutationPaths, () => applyNormalizedPatch(input, options), { crossProcess });
168
295
  }
169
296
 
297
+ /** Read-only mkdir check for one patch hunk. Does not create directories. */
298
+ export async function preflightPatchMkdir(input: PatchInput, options: ApplyPatchOptions): Promise<void> {
299
+ const resolvePath = (p: string): string => resolveToCwd(p, options.cwd);
300
+ const absolutePath = resolvePath(input.path);
301
+ const op = input.op ?? "update";
302
+ if (op === "create") {
303
+ await rejectPatchMkdirEscape(options.cwd, absolutePath, options.localRoot, input.path, options.signal);
304
+ }
305
+ if (input.rename) {
306
+ const destPath = resolvePath(input.rename);
307
+ if (destPath !== absolutePath) {
308
+ // The source is locked too. Check it before that lock can mkdir through a symlink.
309
+ await rejectPatchMkdirEscape(options.cwd, absolutePath, options.localRoot, input.path, options.signal);
310
+ await rejectPatchMkdirEscape(options.cwd, destPath, options.localRoot, input.rename, options.signal);
311
+ }
312
+ }
313
+ }
314
+
315
+ async function rejectPatchMkdirEscape(
316
+ cwd: string,
317
+ absolutePath: string,
318
+ localRoot: string | undefined,
319
+ displayPath: string,
320
+ signal?: AbortSignal,
321
+ ): Promise<void> {
322
+ const parentDir = path.dirname(absolutePath);
323
+ if (!parentDir || parentDir === ".") return;
324
+ await resolvePatchMkdirPath(cwd, absolutePath, localRoot, parentDir, displayPath, signal);
325
+ }
326
+
170
327
  /**
171
328
  * Apply a normalized patch operation to the filesystem.
172
329
  * Caller must already hold path mutation rights when concurrent writers exist.
@@ -179,6 +336,8 @@ async function applyNormalizedPatch(input: PatchInput, options: ApplyPatchOption
179
336
  fs = defaultFileSystem,
180
337
  fuzzyThreshold = DEFAULT_FUZZY_THRESHOLD,
181
338
  allowFuzzy = true,
339
+ localRoot,
340
+ signal,
182
341
  } = options;
183
342
 
184
343
  const resolvePath = (p: string): string => resolveToCwd(p, cwd);
@@ -201,11 +360,17 @@ async function applyNormalizedPatch(input: PatchInput, options: ApplyPatchOption
201
360
  const normalizedContent = normalizeCreateContent(input.diff);
202
361
  const content = normalizedContent.endsWith("\n") ? normalizedContent : `${normalizedContent}\n`;
203
362
 
363
+ const parentDir = path.dirname(absolutePath);
364
+ const mkdirPath =
365
+ parentDir && parentDir !== "."
366
+ ? await resolvePatchMkdirPath(cwd, absolutePath, localRoot, parentDir, input.path, signal)
367
+ : undefined;
204
368
  if (!dryRun) {
205
- const parentDir = path.dirname(absolutePath);
206
- if (parentDir && parentDir !== ".") {
207
- await fs.mkdir(parentDir);
369
+ if (mkdirPath) {
370
+ signal?.throwIfAborted();
371
+ await fs.mkdir(mkdirPath);
208
372
  }
373
+ signal?.throwIfAborted();
209
374
  await fs.write(absolutePath, content);
210
375
  }
211
376
 
@@ -220,9 +385,14 @@ async function applyNormalizedPatch(input: PatchInput, options: ApplyPatchOption
220
385
 
221
386
  // Handle DELETE operation
222
387
  if (op === "delete") {
223
- const oldContent = await readExistingPatchFile(fs, absolutePath, input.path);
388
+ signal?.throwIfAborted();
389
+ const boundary = boundaryForPatchDelete(cwd, absolutePath, localRoot);
390
+ const deletePath = await resolvePatchDeletePath(boundary, absolutePath, input.path, signal);
391
+ signal?.throwIfAborted();
392
+ const oldContent = await readExistingPatchFile(fs, deletePath, input.path);
393
+ signal?.throwIfAborted();
224
394
  if (!dryRun) {
225
- await fs.delete(absolutePath);
395
+ await fs.delete(deletePath);
226
396
  }
227
397
 
228
398
  return {
@@ -260,6 +430,20 @@ async function applyNormalizedPatch(input: PatchInput, options: ApplyPatchOption
260
430
  const finalContent = bom + restoreLineEndings(newContent, lineEnding);
261
431
  const destPath = input.rename ? resolvePath(input.rename) : absolutePath;
262
432
  const isMove = Boolean(input.rename) && destPath !== absolutePath;
433
+ let moveParent: string | undefined;
434
+ if (isMove) {
435
+ const parentDir = path.dirname(destPath);
436
+ if (parentDir && parentDir !== ".") {
437
+ moveParent = await resolvePatchMkdirPath(
438
+ cwd,
439
+ destPath,
440
+ localRoot,
441
+ parentDir,
442
+ input.rename ?? input.path,
443
+ signal,
444
+ );
445
+ }
446
+ }
263
447
 
264
448
  if (!dryRun) {
265
449
  // Commit-time CAS: reject if another writer mutated the file after our
@@ -269,10 +453,18 @@ async function applyNormalizedPatch(input: PatchInput, options: ApplyPatchOption
269
453
  throw new ApplyPatchError(`concurrent edit conflict: file changed since read: ${input.path}`);
270
454
  }
271
455
  if (isMove) {
272
- const parentDir = path.dirname(destPath);
273
- if (parentDir && parentDir !== ".") {
274
- await fs.mkdir(parentDir);
456
+ // Preflight can pass, then another writer holding this lock can
457
+ // turn a handwritten destination into a generated file.
458
+ if (options.destinationGuard) {
459
+ signal?.throwIfAborted();
460
+ await assertEditableFile(destPath, options.destinationGuard.displayPath, options.destinationGuard.settings);
461
+ signal?.throwIfAborted();
275
462
  }
463
+ if (moveParent) {
464
+ signal?.throwIfAborted();
465
+ await fs.mkdir(moveParent);
466
+ }
467
+ signal?.throwIfAborted();
276
468
  await fs.write(destPath, finalContent);
277
469
  await fs.delete(absolutePath);
278
470
  } else {
@@ -467,7 +659,15 @@ export async function executePatchSingle(
467
659
  const resolvedPath = resolvePlanPath(session, path);
468
660
  const resolvedRename = rename ? resolvePlanPath(session, rename) : undefined;
469
661
 
662
+ signal?.throwIfAborted();
470
663
  await assertEditableFile(resolvedPath, path, session.settings);
664
+ // The rename destination is the path that is written. The source call above
665
+ // already uses this helper, including generated names that do not exist yet.
666
+ signal?.throwIfAborted();
667
+ if (resolvedRename && rename && resolvedRename !== resolvedPath) {
668
+ await assertEditableFile(resolvedRename, rename, session.settings);
669
+ signal?.throwIfAborted();
670
+ }
471
671
 
472
672
  // Capture pre-edit content so we can verify the write actually hit disk.
473
673
  // `LspFileSystem.writeFile` delegates to a writethrough callback that, in
@@ -493,12 +693,18 @@ export async function executePatchSingle(
493
693
  // not `defaultFileSystem` by object identity. Force durable cross-process
494
694
  // locking so multi-process agents cannot silently race past the path mutex
495
695
  // (#2900 review: do not infer lock capability from FileSystem identity).
696
+ const localRoot = explicitLocalRootForSession(session);
496
697
  const result = await applyPatch(input, {
497
698
  cwd: session.cwd,
498
699
  fs: patchFileSystem,
499
700
  fuzzyThreshold,
500
701
  allowFuzzy,
501
702
  crossProcessLock: true,
703
+ localRoot,
704
+ signal,
705
+ ...(op === "update" && resolvedRename && rename && resolvedRename !== resolvedPath
706
+ ? { destinationGuard: { settings: session.settings, displayPath: rename } }
707
+ : {}),
502
708
  });
503
709
 
504
710
  // Post-write verification: only meaningful for in-place updates where the
@@ -9,6 +9,7 @@ import { markDesignedError } from "@gajae-code/utils/error-classification";
9
9
  import * as z from "zod/v4";
10
10
  import type { WritethroughCallback, WritethroughDeferredHandle } from "../../lsp";
11
11
  import type { ToolSession } from "../../tools";
12
+ import { assertEditableFile, assertEditableFileContent } from "../../tools/auto-generated-guard";
12
13
  import { invalidateFsScanAfterWrite } from "../../tools/fs-cache-invalidation";
13
14
  import { outputMeta } from "../../tools/output-meta";
14
15
  import { enforcePlanModeWrite, resolvePlanPath } from "../../tools/plan-mode-guard";
@@ -636,6 +637,7 @@ export async function executeReplaceSingle(
636
637
  }
637
638
 
638
639
  const absolutePath = resolvePlanPath(session, path);
640
+ await assertEditableFile(absolutePath, path, session.settings);
639
641
  return withEditPathMutation([absolutePath], () =>
640
642
  executeReplaceSingleUnderLock({
641
643
  session,
@@ -664,6 +666,7 @@ async function executeReplaceSingleUnderLock(
664
666
  },
665
667
  ): Promise<AgentToolResult<EditToolDetails, typeof replaceEditEntrySchema>> {
666
668
  const {
669
+ session,
667
670
  path,
668
671
  signal,
669
672
  batchRequest,
@@ -678,6 +681,8 @@ async function executeReplaceSingleUnderLock(
678
681
  } = options;
679
682
 
680
683
  const rawContent = await readEditFileText(absolutePath, path);
684
+ assertEditableFileContent(rawContent, path, session.settings);
685
+ await assertEditableFile(absolutePath, path, session.settings);
681
686
  const { bom, text: content } = stripBom(rawContent);
682
687
  const originalEnding = detectLineEnding(content);
683
688
  const normalizedContent = normalizeToLF(content);
@@ -749,3 +754,134 @@ async function executeReplaceSingleUnderLock(
749
754
  },
750
755
  };
751
756
  }
757
+
758
+ export async function executeReplaceEntries(
759
+ options: Omit<ExecuteReplaceSingleOptions, "params"> & { edits: ReplaceEditEntry[] },
760
+ ): Promise<AgentToolResult<EditToolDetails, typeof replaceEditSchema>> {
761
+ if (options.edits.length <= 1) {
762
+ const only = options.edits[0];
763
+ if (!only) {
764
+ throw new ToolError("edits must not be empty.");
765
+ }
766
+ return executeReplaceSingle({ ...options, params: only });
767
+ }
768
+
769
+ const { session, path, edits } = options;
770
+ try {
771
+ enforcePlanModeWrite(session, path);
772
+ for (const entry of edits) {
773
+ if (entry.old_text.length === 0) {
774
+ throw new ToolError("old_text must not be empty.");
775
+ }
776
+ }
777
+ const absolutePath = resolvePlanPath(session, path);
778
+ return await withEditPathMutation([absolutePath], () =>
779
+ executeReplaceEntriesUnderLock({ ...options, absolutePath }),
780
+ );
781
+ } catch (err) {
782
+ const errorText = err instanceof Error ? err.message : String(err);
783
+ return {
784
+ content: [{ type: "text", text: `Error editing ${path}: ${errorText}` }],
785
+ details: { diff: "", path },
786
+ isError: true,
787
+ };
788
+ }
789
+ }
790
+
791
+ async function executeReplaceEntriesUnderLock(
792
+ options: Omit<ExecuteReplaceSingleOptions, "params"> & {
793
+ edits: ReplaceEditEntry[];
794
+ absolutePath: string;
795
+ },
796
+ ): Promise<AgentToolResult<EditToolDetails, typeof replaceEditSchema>> {
797
+ const {
798
+ session,
799
+ path,
800
+ edits,
801
+ signal,
802
+ batchRequest,
803
+ allowFuzzy,
804
+ fuzzyThreshold,
805
+ writethrough,
806
+ beginDeferredDiagnosticsForPath,
807
+ absolutePath,
808
+ } = options;
809
+
810
+ const rawContent = await readEditFileText(absolutePath, path);
811
+ assertEditableFileContent(rawContent, path, session.settings);
812
+ await assertEditableFile(absolutePath, path, session.settings);
813
+
814
+ const { bom, text: content } = stripBom(rawContent);
815
+ const originalEnding = detectLineEnding(content);
816
+ let normalizedContent = normalizeToLF(content);
817
+ const originalNormalized = normalizedContent;
818
+ const texts: string[] = [];
819
+
820
+ for (const [index, entry] of edits.entries()) {
821
+ const normalizedOldText = normalizeToLF(entry.old_text);
822
+ const normalizedNewText = normalizeToLF(entry.new_text);
823
+ const replaced = await replaceText(normalizedContent, normalizedOldText, normalizedNewText, {
824
+ fuzzy: allowFuzzy,
825
+ all: entry.all ?? false,
826
+ threshold: fuzzyThreshold,
827
+ });
828
+ if (replaced.count === 0) {
829
+ const matchOutcome = await findMatch(normalizedContent, normalizedOldText, {
830
+ allowFuzzy,
831
+ threshold: fuzzyThreshold,
832
+ });
833
+ if (matchOutcome.occurrences && matchOutcome.occurrences > 1) {
834
+ throw new ToolError(formatOccurrenceError(path, matchOutcome));
835
+ }
836
+ throw new EditMatchError(path, normalizedOldText, matchOutcome.closest, {
837
+ allowFuzzy,
838
+ threshold: fuzzyThreshold,
839
+ fuzzyMatches: matchOutcome.fuzzyMatches,
840
+ });
841
+ }
842
+ if (normalizedContent === replaced.content) {
843
+ throw new ToolError(`Edits to ${path} resulted in no changes being made.`);
844
+ }
845
+ normalizedContent = replaced.content;
846
+ if (index < edits.length - 1) {
847
+ assertEditableFileContent(normalizedContent, path, session.settings);
848
+ }
849
+ texts.push(
850
+ replaced.count > 1
851
+ ? `Successfully replaced ${replaced.count} occurrences in ${path}.`
852
+ : `Successfully replaced text in ${path}.`,
853
+ );
854
+ }
855
+
856
+ const finalContent = await serializeEditFileText(
857
+ absolutePath,
858
+ path,
859
+ bom + restoreLineEndings(normalizedContent, originalEnding),
860
+ );
861
+ const diagnostics = await writethrough(
862
+ absolutePath,
863
+ finalContent,
864
+ signal,
865
+ Bun.file(absolutePath),
866
+ batchRequest,
867
+ dst => (dst === absolutePath ? beginDeferredDiagnosticsForPath(absolutePath) : undefined),
868
+ );
869
+ invalidateFsScanAfterWrite(absolutePath);
870
+
871
+ const diffResult = generateDiffString(originalNormalized, normalizedContent);
872
+ const meta = outputMeta()
873
+ .diagnostics(diagnostics?.summary ?? "", diagnostics?.messages ?? [])
874
+ .get();
875
+ return {
876
+ content: [{ type: "text", text: texts.join("\n") }],
877
+ details: {
878
+ diff: diffResult.diff,
879
+ path: absolutePath,
880
+ firstChangedLine: diffResult.firstChangedLine,
881
+ diagnostics,
882
+ meta,
883
+ oldText: rawContent,
884
+ newText: finalContent,
885
+ },
886
+ };
887
+ }
@@ -523,15 +523,41 @@ async function spawnJsWorker(): Promise<WorkerHandle> {
523
523
  }
524
524
 
525
525
  function wrapBunWorker(worker: Worker): WorkerHandle {
526
+ // Cells share parentPort, so worker→host frames use a port transferred before
527
+ // the first cell. worker "message" events are accepted only until ready.
528
+ const channel = new MessageChannel();
529
+ const hostPort = channel.port2;
530
+ const queued: WorkerOutbound[] = [];
531
+ let onFrame: ((msg: WorkerOutbound) => void) | undefined;
532
+ let acceptWorkerEvent = true;
533
+ const deliver = (msg: WorkerOutbound): void => {
534
+ if (msg?.type === "ready" || msg?.type === "init-failed") acceptWorkerEvent = false;
535
+ if (onFrame) onFrame(msg);
536
+ else queued.push(msg);
537
+ };
538
+ const onPort = (event: Event): void => {
539
+ deliver((event as MessageEvent).data as WorkerOutbound);
540
+ };
541
+ const onWorker = (event: Event): void => {
542
+ if (!acceptWorkerEvent) return;
543
+ deliver((event as MessageEvent).data as WorkerOutbound);
544
+ };
545
+ hostPort.addEventListener("message", onPort);
546
+ worker.addEventListener("message", onWorker);
547
+ worker.postMessage({ type: "gjc-bind", port: channel.port1 }, { transfer: [channel.port1] });
526
548
  return {
527
549
  mode: "worker",
528
550
  send(msg) {
529
551
  worker.postMessage(msg);
530
552
  },
531
553
  onMessage(handler) {
532
- const wrap = (event: MessageEvent): void => handler(event.data as WorkerOutbound);
533
- worker.addEventListener("message", wrap);
534
- return () => worker.removeEventListener("message", wrap);
554
+ onFrame = handler;
555
+ for (const msg of queued.splice(0)) handler(msg);
556
+ return () => {
557
+ if (onFrame === handler) onFrame = undefined;
558
+ hostPort.removeEventListener("message", onPort);
559
+ worker.removeEventListener("message", onWorker);
560
+ };
535
561
  },
536
562
  async terminate() {
537
563
  worker.terminate();
@@ -1,24 +1,57 @@
1
- import { parentPort } from "node:worker_threads";
1
+ import { type MessagePort, parentPort } from "node:worker_threads";
2
2
  import { WorkerCore } from "./worker-core";
3
3
  import type { Transport, WorkerInbound, WorkerOutbound } from "./worker-protocol";
4
4
 
5
5
  if (!parentPort) throw new Error("js worker-entry: missing parentPort");
6
6
 
7
- const port = parentPort;
8
- const transport: Transport = {
9
- send: (msg: WorkerOutbound) => port.postMessage(msg),
10
- onMessage: handler => {
11
- const wrap = (data: unknown): void => handler(data as WorkerInbound);
12
- port.on("message", wrap);
13
- return () => port.off("message", wrap);
14
- },
15
- close: () => {
16
- try {
17
- port.close();
18
- } catch {
19
- // Already closed.
20
- }
21
- },
22
- };
7
+ const parent = parentPort;
8
+
9
+ function isHostPort(value: unknown): value is MessagePort {
10
+ if (!value || typeof value !== "object") return false;
11
+ const port = value as MessagePort;
12
+ return typeof port.postMessage === "function" && typeof port.on === "function";
13
+ }
14
+
15
+ function readBindPort(data: unknown): MessagePort | undefined {
16
+ if (!data || typeof data !== "object") return undefined;
17
+ const record = data as { type?: unknown; port?: unknown };
18
+ if (record.type !== "gjc-bind" || !isHostPort(record.port)) return undefined;
19
+ return record.port;
20
+ }
23
21
 
24
- new WorkerCore(transport);
22
+ function listen(port: MessagePort): void {
23
+ // Bound before any user cell. A later prototype patch cannot retarget this send.
24
+ const post = port.postMessage.bind(port);
25
+ const transport: Transport = {
26
+ send: (msg: WorkerOutbound) => {
27
+ post(msg);
28
+ },
29
+ onMessage: handler => {
30
+ const wrap = (data: unknown): void => handler(data as WorkerInbound);
31
+ parent.on("message", wrap);
32
+ return () => parent.off("message", wrap);
33
+ },
34
+ close: () => {
35
+ try {
36
+ port.close();
37
+ } catch {
38
+ // Already closed.
39
+ }
40
+ try {
41
+ parent.close();
42
+ } catch {
43
+ // Already closed.
44
+ }
45
+ },
46
+ };
47
+ new WorkerCore(transport);
48
+ }
49
+
50
+ const onParent = (data: unknown): void => {
51
+ const port = readBindPort(data);
52
+ if (!port) return;
53
+ if (data && typeof data === "object") (data as { port?: unknown }).port = undefined;
54
+ parent.off("message", onParent);
55
+ listen(port);
56
+ };
57
+ parent.on("message", onParent);
@@ -912,7 +912,17 @@ async function replaceSessionKernel(
912
912
  const old = session.kernel;
913
913
  const remaining = getRemainingTimeoutMs(options.deadlineMs);
914
914
  const ownerIds = new Set(session.ownerIds);
915
- await shutdownOrRetainKernel(old, ownerIds, remaining === undefined ? undefined : Math.max(0, remaining));
915
+ try {
916
+ await shutdownOrRetainKernel(old, ownerIds, remaining === undefined ? undefined : Math.max(0, remaining));
917
+ } catch (error) {
918
+ // Suppress unconfirmed shutdown error ONLY when we have positive evidence the
919
+ // process actually exited (exitCode is set). Without that evidence, propagate the error
920
+ // so we don't start a replacement while the old subprocess is still running.
921
+ // See https://github.com/yeachan-heo/gajae-code/issues/6569
922
+ const hasConfirmedExit = old.getExitCode() !== null;
923
+ if (!hasConfirmedExit || !(error instanceof Error) || error.name !== "PythonKernelShutdownUnconfirmedError")
924
+ throw error;
925
+ }
916
926
  throwIfExecutionCancelled(options);
917
927
  if (sessions.get(session.sessionId) !== session) {
918
928
  throw new PythonExecutionCancelledError(false);
@@ -1076,7 +1086,6 @@ export function disposeKernelSessionsByOwner(ownerId: string): Promise<void> {
1076
1086
  const toShutdown: PythonSession[] = [];
1077
1087
  const initializersToAbort: InitializingPythonSession[] = [];
1078
1088
  for (const entry of initializing) {
1079
- entry.retirementOwnerIds.add(ownerId);
1080
1089
  let hasOwnerRequest = false;
1081
1090
  for (const request of entry.requests) {
1082
1091
  if (request.ownerId === ownerId && !request.signal?.aborted) {
@@ -1089,6 +1098,7 @@ export function disposeKernelSessionsByOwner(ownerId: string): Promise<void> {
1089
1098
  if (entry.ownerIds.size > 0) removeKernelOwner(entry.kernel, ownerId);
1090
1099
  }
1091
1100
  if (![...entry.ownerIds].some(id => id !== ownerId)) {
1101
+ entry.retirementOwnerIds.add(ownerId);
1092
1102
  entry.cancelled ??= new PythonExecutionCancelledError(false);
1093
1103
  initializersToAbort.push(entry);
1094
1104
  }
@@ -1137,10 +1147,10 @@ export function disposeKernelSessionsByOwner(ownerId: string): Promise<void> {
1137
1147
  return (async () => {
1138
1148
  await Promise.all([
1139
1149
  ...requests.map(request => request.completion),
1140
- ...initializing.map(entry => entry.promise.catch(() => undefined)),
1150
+ ...initializersToAbort.map(entry => entry.promise.catch(() => undefined)),
1141
1151
  ]);
1142
1152
  const failures: unknown[] = [];
1143
- for (const entry of initializing)
1153
+ for (const entry of initializersToAbort)
1144
1154
  if (entry.cleanupFailed) {
1145
1155
  if (entry.kernel) retainKernelForOwners(entry.kernel, new Set([ownerId]));
1146
1156
  failures.push(entry.cleanupError);