@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
@@ -0,0 +1,286 @@
1
+ import * as childProcess from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as process from "node:process";
5
+ import type * as stream from "node:stream";
6
+ import type { Process } from "@gajae-code/natives";
7
+ import { nativeProcessBindings } from "@gajae-code/utils/native-process";
8
+ import type { BrokerHopMessage } from "./ensure";
9
+
10
+ /** Handoff protocol frames written to the trampoline's stdin. */
11
+ export const BROKER_HANDOFF_ACKNOWLEDGEMENT = "GJC_BROKER_HANDOFF_ACK\n";
12
+ export const BROKER_HANDOFF_COMMIT = "GJC_BROKER_HANDOFF_COMMIT\n";
13
+ export const BROKER_HANDOFF_ABORT = "GJC_BROKER_HANDOFF_ABORT\n";
14
+ /**
15
+ * Backstop for a retained trampoline whose parent never sends a decision. It must
16
+ * outlive the parent's own broker discovery deadline (which is derived from the
17
+ * publication budget and can double across a guarded retry), otherwise the
18
+ * trampoline reaps the broker first and the parent misreports its own discovery
19
+ * timeout as an "exited before discovery" startup failure.
20
+ */
21
+ export const BROKER_HANDOFF_DECISION_TIMEOUT_MS = 120_000;
22
+
23
+ /**
24
+ * Windows broker hop: spawns the real broker with detached:true and reports its pid.
25
+ *
26
+ * The hop is needed because a broker spawned with detached:true IS still killed when the
27
+ * parent process tree is terminated (e.g., by taskkill /T /F on Windows). Using an
28
+ * intermediate hop process that exits after spawning the real broker allows the broker
29
+ * to survive, because the hop's parent (the initiating client) can be killed without
30
+ * affecting the broker (the hop's pid is no longer valid).
31
+ *
32
+ * Invoked by gjc internals only. Usage:
33
+ * gjc internal broker-hop <json-encoded-args>
34
+ *
35
+ * Exits with code 0 after writing the broker pid/incarnation to stdout and
36
+ * receiving the parent's acknowledgement. If stdin closes first, it terminates
37
+ * the pinned broker and exits with code 1.
38
+ */
39
+
40
+ export async function runBrokerHopFromArgv(argv: string[]): Promise<void> {
41
+ if (argv.length !== 1) fail("broker hop requires exactly one argument");
42
+ let message: BrokerHopMessage;
43
+ try {
44
+ message = JSON.parse(argv[0]) as BrokerHopMessage;
45
+ } catch (error) {
46
+ fail(`broker hop JSON parse error: ${error instanceof Error ? error.message : String(error)}`);
47
+ }
48
+ if (!message.command?.file || !Array.isArray(message.command.args)) fail("broker hop message missing command");
49
+ try {
50
+ // stderr is opened here by path: a parent fd number is meaningless in this process.
51
+ const stderr = message.stderrLogPath ? fs.openSync(message.stderrLogPath, "a") : "ignore";
52
+ // The broker inherits this process's environment, which the parent set to the
53
+ // broker environment; it is never carried on the command line.
54
+ const child = childProcess.spawn(message.command.file, message.command.args, {
55
+ detached: true,
56
+ windowsHide: true,
57
+ stdio: ["ignore", "ignore", stderr],
58
+ env: process.env,
59
+ ...(message.cwd ? { cwd: message.cwd } : {}),
60
+ });
61
+ if (typeof stderr === "number") fs.closeSync(stderr);
62
+ const spawned = Promise.withResolvers<void>();
63
+ child.once("spawn", spawned.resolve);
64
+ child.once("error", spawned.reject);
65
+ await spawned.promise;
66
+ const pid = child.pid;
67
+ if (pid === undefined) fail("broker hop spawn succeeded but child pid unavailable");
68
+ const childReference = getSpawnedChildReference(pid);
69
+ if (!childReference) {
70
+ const cleanup = await terminateUnverifiableWindowsChild(child);
71
+ fail(`broker process identity could not be bound to the spawned child; ${cleanup}`);
72
+ }
73
+ const handoff = await writeBrokerHopReply(child, childReference, pid, writeBrokerHopStdout, () =>
74
+ waitForBrokerHandoffAcknowledgement(process.stdin),
75
+ );
76
+ if (handoff.kind === "failed") fail(handoff.reason);
77
+ process.exit(0);
78
+ } catch (error) {
79
+ fail(`broker hop spawn failed: ${error instanceof Error ? error.message : String(error)}`);
80
+ }
81
+ }
82
+
83
+ type BrokerHopWriteCallback = (error?: Error | null) => void;
84
+ type BrokerHopWriter = (chunk: string, callback: BrokerHopWriteCallback) => void;
85
+ type BrokerHopAcknowledger = () => Promise<boolean>;
86
+
87
+ async function writeBrokerHopReply(
88
+ child: childProcess.ChildProcess,
89
+ reference: Process,
90
+ pid: number,
91
+ write: BrokerHopWriter,
92
+ waitForAcknowledgement: BrokerHopAcknowledger,
93
+ ): Promise<{ kind: "written" } | { kind: "failed"; reason: string }> {
94
+ const completion = Promise.withResolvers<void>();
95
+ const acknowledgementPromise = waitForAcknowledgement();
96
+ let settled = false;
97
+ const settle = (error?: Error | null): void => {
98
+ if (settled) return;
99
+ settled = true;
100
+ if (error) completion.reject(error);
101
+ else completion.resolve();
102
+ };
103
+ try {
104
+ write(`${JSON.stringify({ pid, incarnation: reference.incarnation })}\n`, settle);
105
+ } catch (error) {
106
+ settle(error instanceof Error ? error : new Error(String(error)));
107
+ }
108
+ try {
109
+ await completion.promise;
110
+ } catch (error) {
111
+ const writeError = error instanceof Error ? error : new Error(String(error));
112
+ const cleanup = await terminateBrokerAfterFailedHandoff(reference);
113
+ return {
114
+ kind: "failed",
115
+ reason: `broker hop handoff failed for pid ${pid}: ${cleanup}; ${writeError.message}`,
116
+ };
117
+ }
118
+ let acknowledged = false;
119
+ let acknowledgementError: string | undefined;
120
+ try {
121
+ acknowledged = await acknowledgementPromise;
122
+ } catch (error) {
123
+ acknowledgementError = error instanceof Error ? error.message : String(error);
124
+ }
125
+ if (!acknowledged) {
126
+ const cleanup = await terminateBrokerAfterFailedHandoff(reference);
127
+ return {
128
+ kind: "failed",
129
+ reason: `broker hop handoff was not acknowledged for pid ${pid}: ${cleanup}${acknowledgementError ? `; ${acknowledgementError}` : ""}`,
130
+ };
131
+ }
132
+ child.unref();
133
+ return { kind: "written" };
134
+ }
135
+
136
+ export function writeBrokerHopReplyForTest(
137
+ child: childProcess.ChildProcess,
138
+ reference: Process,
139
+ pid: number,
140
+ write: BrokerHopWriter,
141
+ waitForAcknowledgement: BrokerHopAcknowledger = async () => true,
142
+ ): Promise<{ kind: "written" } | { kind: "failed"; reason: string }> {
143
+ return writeBrokerHopReply(child, reference, pid, write, waitForAcknowledgement);
144
+ }
145
+
146
+ export function waitForBrokerHandoffAcknowledgement(input: stream.Readable): Promise<boolean> {
147
+ const acknowledgment = Promise.withResolvers<boolean>();
148
+ let received = "";
149
+ let settled = false;
150
+ const finish = (accepted: boolean): void => {
151
+ if (settled) return;
152
+ settled = true;
153
+ input.removeListener("data", onData);
154
+ input.removeListener("end", onEnd);
155
+ input.removeListener("close", onEnd);
156
+ input.removeListener("error", onEnd);
157
+ acknowledgment.resolve(accepted);
158
+ };
159
+ const onData = (chunk: Buffer | string): void => {
160
+ received += chunk.toString();
161
+ if (received.length > BROKER_HANDOFF_ACKNOWLEDGEMENT.length) {
162
+ finish(false);
163
+ return;
164
+ }
165
+ if (received.includes("\n")) finish(received === BROKER_HANDOFF_ACKNOWLEDGEMENT);
166
+ };
167
+ const onEnd = (): void => finish(false);
168
+ input.on("data", onData);
169
+ input.once("end", onEnd);
170
+ input.once("close", onEnd);
171
+ input.once("error", onEnd);
172
+ input.resume();
173
+ return acknowledgment.promise;
174
+ }
175
+
176
+ /** Wait for a validated identity ACK followed by the parent's publication decision. */
177
+ export function waitForBrokerHandoffDecision(
178
+ input: stream.Readable,
179
+ timeoutMs = BROKER_HANDOFF_DECISION_TIMEOUT_MS,
180
+ ): Promise<"commit" | "abort"> {
181
+ const decision = Promise.withResolvers<"commit" | "abort">();
182
+ let received = "";
183
+ let settled = false;
184
+ const finish = (value: "commit" | "abort"): void => {
185
+ if (settled) return;
186
+ settled = true;
187
+ clearTimeout(timer);
188
+ input.removeListener("data", onData);
189
+ input.removeListener("end", onEnd);
190
+ input.removeListener("close", onEnd);
191
+ input.removeListener("error", onEnd);
192
+ decision.resolve(value);
193
+ };
194
+ const onData = (chunk: Buffer | string): void => {
195
+ received += chunk.toString();
196
+ if (received.length > BROKER_HANDOFF_ACKNOWLEDGEMENT.length + BROKER_HANDOFF_COMMIT.length) {
197
+ finish("abort");
198
+ return;
199
+ }
200
+ if (received.length < BROKER_HANDOFF_ACKNOWLEDGEMENT.length) {
201
+ if (!BROKER_HANDOFF_ACKNOWLEDGEMENT.startsWith(received)) finish("abort");
202
+ return;
203
+ }
204
+ if (!received.startsWith(BROKER_HANDOFF_ACKNOWLEDGEMENT)) {
205
+ finish("abort");
206
+ return;
207
+ }
208
+ const action = received.slice(BROKER_HANDOFF_ACKNOWLEDGEMENT.length);
209
+ if (action === BROKER_HANDOFF_COMMIT) finish("commit");
210
+ else if (action === BROKER_HANDOFF_ABORT) finish("abort");
211
+ else if (!BROKER_HANDOFF_COMMIT.startsWith(action) && !BROKER_HANDOFF_ABORT.startsWith(action)) finish("abort");
212
+ };
213
+ const onEnd = (): void => finish("abort");
214
+ const timer = setTimeout(() => finish("abort"), timeoutMs);
215
+ input.on("data", onData);
216
+ input.once("end", onEnd);
217
+ input.once("close", onEnd);
218
+ input.once("error", onEnd);
219
+ input.resume();
220
+ return decision.promise;
221
+ }
222
+
223
+ const writeBrokerHopStdout: BrokerHopWriter = (chunk, callback): void => {
224
+ const onError = (error: Error): void => {
225
+ process.stdout.removeListener("error", onError);
226
+ callback(error);
227
+ };
228
+ process.stdout.once("error", onError);
229
+ try {
230
+ process.stdout.write(chunk, error => {
231
+ process.stdout.removeListener("error", onError);
232
+ callback(error);
233
+ });
234
+ } catch (error) {
235
+ process.stdout.removeListener("error", onError);
236
+ callback(error instanceof Error ? error : new Error(String(error)));
237
+ }
238
+ };
239
+
240
+ /** Pin only the still-running direct child so a reused PID cannot be adopted. */
241
+ function getSpawnedChildReference(pid: number): Process | undefined {
242
+ try {
243
+ const reference = nativeProcessBindings().Process.fromPid(pid);
244
+ if (!reference || reference.pid !== pid || reference.ppid !== process.pid || reference.status() !== "running")
245
+ return undefined;
246
+ return reference;
247
+ } catch {
248
+ return undefined;
249
+ }
250
+ }
251
+
252
+ async function terminateUnverifiableWindowsChild(child: childProcess.ChildProcess): Promise<string> {
253
+ if (process.platform !== "win32") return "no signal was sent without a verified process identity";
254
+ if (child.exitCode !== null || child.signalCode !== null) return "the spawned child already exited";
255
+ const exited = Promise.withResolvers<void>();
256
+ child.once("exit", exited.resolve);
257
+ child.once("close", exited.resolve);
258
+ child.on("error", () => {});
259
+ try {
260
+ child.kill("SIGKILL");
261
+ } catch {
262
+ // The process handle remains exact; inspect its exit state below.
263
+ }
264
+ await Promise.race([exited.promise, Bun.sleep(500)]);
265
+ return child.exitCode !== null || child.signalCode !== null
266
+ ? "the exact spawned child was terminated"
267
+ : "termination of the exact spawned child was not confirmed";
268
+ }
269
+
270
+ async function terminateBrokerAfterFailedHandoff(reference: Process): Promise<string> {
271
+ let cleanup: string;
272
+ try {
273
+ if (reference.status() === "running") reference.signalRoot(os.constants.signals.SIGKILL);
274
+ const exited = await reference.waitForExit({ timeoutMs: 500 });
275
+ cleanup =
276
+ exited || reference.status() !== "running" ? "broker terminated" : "broker termination was not confirmed";
277
+ } catch (error) {
278
+ cleanup = `broker termination failed: ${error instanceof Error ? error.message : String(error)}`;
279
+ }
280
+ return cleanup;
281
+ }
282
+
283
+ function fail(message: string): never {
284
+ process.stderr.write(`gjc: ${message}\n`);
285
+ process.exit(1);
286
+ }
@@ -130,6 +130,10 @@ type BoundedSourceRead =
130
130
 
131
131
  const terminal = (s: LifecycleState) => s === "terminal_ok" || s === "terminal_error";
132
132
  const final = (s: LifecycleState) => terminal(s) || s === "terminal_uncertain";
133
+ const recordedSessionIds = (entry: LifecycleLedgerEntry): string[] =>
134
+ [entry.intendedSessionId, entry.resultSessionId].filter(
135
+ (sessionId): sessionId is string => typeof sessionId === "string" && sessionId.length > 0,
136
+ );
133
137
  export interface LifecycleLedgerLimits {
134
138
  maxBytes?: number;
135
139
  maxLineBytes?: number;
@@ -795,27 +799,47 @@ export class LifecycleLedger {
795
799
  return [...this.#byIdentity.values()].findLast(entry => entry.operationKey === operationKey);
796
800
  }
797
801
  /**
798
- * Live or uncertain legacy rows predate the operation/key index. Their opaque
799
- * identities cannot establish that a different target is safe, so callers
800
- * must reject rather than create a second admission. A fresh session.create
801
- * may ignore completed terminal legacy rows so old successful operations do
802
- * not block coordinator startup. Other operations retain the legacy fence
803
- * because target-bound legacy identities cannot prove key uniqueness. For
804
- * target-bound terminal creates, the opaque legacy identity also cannot reveal
805
- * the original caller key, so this exception retires that legacy key history;
806
- * exact key-only legacy creates are still rejected by the earlier lookup.
802
+ * Live legacy rows predate the operation/key index. Their opaque identities
803
+ * cannot establish that a different target is safe, so callers must reject
804
+ * rather than create a second admission. A fresh session.create may ignore
805
+ * final legacy rows: exact legacy identities are still resolved before this
806
+ * check, and uncertain rows retain their session-scoped fences.
807
+ *
808
+ * A session-targeted request passes `sessionTarget` to ignore terminal legacy
809
+ * rows that record only other sessions, unless the row is the caller's key
810
+ * reused across targets (`keyReuseIdentities`). Terminal rows recording the
811
+ * target or no session, uncertain rows, and live rows keep the fence because
812
+ * they cannot prove the requested target is unrelated.
807
813
  */
808
814
  hasLegacyIdentity(
809
815
  excludedIdentities?: ReadonlySet<string>,
810
- options: { ignoreTerminalRows?: boolean } = {},
816
+ options: {
817
+ ignoreFinalRows?: boolean;
818
+ sessionTarget?: { sessionId: string; keyReuseIdentities: ReadonlySet<string> };
819
+ } = {},
811
820
  ): boolean {
821
+ const target = options.sessionTarget;
822
+ const unrelatedTerminal = (entry: LifecycleLedgerEntry): boolean => {
823
+ if (!target || !terminal(entry.state) || target.keyReuseIdentities.has(entry.identity)) return false;
824
+ const sessionIds = recordedSessionIds(entry);
825
+ return sessionIds.length > 0 && !sessionIds.includes(target.sessionId);
826
+ };
812
827
  return [...this.#byIdentity.values()].some(
813
828
  entry =>
814
829
  entry.operationKey === undefined &&
815
- !(options.ignoreTerminalRows && terminal(entry.state)) &&
830
+ !(options.ignoreFinalRows && final(entry.state)) &&
831
+ !unrelatedTerminal(entry) &&
816
832
  !excludedIdentities?.has(entry.identity),
817
833
  );
818
834
  }
835
+ /** Sessions recorded by terminal legacy rows, for deriving key-reuse identities. */
836
+ terminalLegacySessionIds(): Set<string> {
837
+ const sessionIds = new Set<string>();
838
+ for (const entry of this.#byIdentity.values())
839
+ if (entry.operationKey === undefined && terminal(entry.state))
840
+ for (const sessionId of recordedSessionIds(entry)) sessionIds.add(sessionId);
841
+ return sessionIds;
842
+ }
819
843
  async migrateIdentity(
820
844
  from: string,
821
845
  to: string,
@@ -944,6 +968,9 @@ export class LifecycleLedger {
944
968
  return this.#mutate(async () => {
945
969
  const previous = this.#byIdentity.get(identity);
946
970
  if (!previous) throw new Error("Unknown lifecycle identity");
971
+ // Match replay validation: proven terminal records admit no successor.
972
+ // A late failure must not append a row that will be quarantined on reopen.
973
+ if (terminal(previous.state)) return previous;
947
974
  const next = {
948
975
  ...previous,
949
976
  ...fields,
@@ -951,6 +951,14 @@ type SessionLaunch = {
951
951
  modelId?: string;
952
952
  modelPreset?: string;
953
953
  mcpServers?: SessionLifecycleMcpServer[];
954
+ /**
955
+ * Per-session environment for the hosted session (#6735). Applied to the
956
+ * session host's spawn environment (and therefore inherited by its Bash
957
+ * executor) on top of the broker's own environment, after `unsetEnv` has
958
+ * removed inherited names. Broker-reserved `GJC_*` names are rejected.
959
+ */
960
+ env?: Record<string, string>;
961
+ unsetEnv?: string[];
954
962
  /** Coordinator namespace dir; broker computes the state file path from launch.id (#2549). */
955
963
  coordinatorStateDir?: string;
956
964
  coordinatorSessionId?: string;
@@ -963,6 +971,85 @@ type SessionLaunch = {
963
971
  };
964
972
 
965
973
  const CREDENTIAL_NAME_PATTERN = /(?:token|secret|password|credential|api[_-]?key|auth|cookie)/iu;
974
+ const SESSION_ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
975
+ const SESSION_ENV_MAX_ENTRIES = 256;
976
+ const SESSION_ENV_MAX_VALUE_LENGTH = 131_072;
977
+
978
+ function isSessionEnvName(name: unknown): name is string {
979
+ return (
980
+ typeof name === "string" &&
981
+ name.length <= 256 &&
982
+ SESSION_ENV_NAME_PATTERN.test(name) &&
983
+ // GJC_* carries broker-issued launch authority (request, effect marker,
984
+ // state root, coordinator correlation); a caller must not shadow it.
985
+ !name.toUpperCase().startsWith("GJC_")
986
+ );
987
+ }
988
+
989
+ const sessionEnvKey = (name: string): string => (process.platform === "win32" ? name.toUpperCase() : name);
990
+
991
+ /**
992
+ * Validates the optional per-session `env`/`unsetEnv` pair (#6735). Returns
993
+ * null when either is malformed so the caller fails the launch closed.
994
+ */
995
+ export function lifecycleSessionEnv(input: {
996
+ env?: unknown;
997
+ unsetEnv?: unknown;
998
+ }): { env?: Record<string, string>; unsetEnv?: string[] } | null {
999
+ let env: Record<string, string> | undefined;
1000
+ if (input.env !== undefined) {
1001
+ if (input.env === null || typeof input.env !== "object" || Array.isArray(input.env)) return null;
1002
+ const entries = Object.entries(input.env as Record<string, unknown>);
1003
+ if (entries.length > SESSION_ENV_MAX_ENTRIES) return null;
1004
+ const seen = new Set<string>();
1005
+ for (const [name, value] of entries) {
1006
+ if (
1007
+ !isSessionEnvName(name) ||
1008
+ typeof value !== "string" ||
1009
+ value.length > SESSION_ENV_MAX_VALUE_LENGTH ||
1010
+ value.includes("\0") ||
1011
+ seen.has(sessionEnvKey(name))
1012
+ )
1013
+ return null;
1014
+ seen.add(sessionEnvKey(name));
1015
+ }
1016
+ env = Object.fromEntries(entries) as Record<string, string>;
1017
+ }
1018
+ let unsetEnv: string[] | undefined;
1019
+ if (input.unsetEnv !== undefined) {
1020
+ if (!Array.isArray(input.unsetEnv) || input.unsetEnv.length > SESSION_ENV_MAX_ENTRIES) return null;
1021
+ const keys = new Set<string>();
1022
+ for (const name of input.unsetEnv) {
1023
+ if (!isSessionEnvName(name) || keys.has(sessionEnvKey(name))) return null;
1024
+ keys.add(sessionEnvKey(name));
1025
+ }
1026
+ // Setting and unsetting the same name is contradictory, not a precedence question.
1027
+ if (env && Object.keys(env).some(name => keys.has(sessionEnvKey(name)))) return null;
1028
+ unsetEnv = [...(input.unsetEnv as string[])];
1029
+ }
1030
+ return { ...(env ? { env } : {}), ...(unsetEnv ? { unsetEnv } : {}) };
1031
+ }
1032
+
1033
+ /**
1034
+ * Composes the inherited part of a lifecycle session host's environment:
1035
+ * drops the process-local master capability, every `unsetEnv` name, and any
1036
+ * inherited name an explicit per-session `env` entry replaces (case-folded on
1037
+ * Windows so `Path` and `PATH` never both reach the child), then layers the
1038
+ * per-session `env` on top. Broker-issued `GJC_*` keys are applied after this.
1039
+ */
1040
+ export function lifecycleSessionHostBaseEnv(
1041
+ inherited: NodeJS.ProcessEnv | Readonly<Record<string, string | undefined>>,
1042
+ launch: { env?: Readonly<Record<string, string>>; unsetEnv?: readonly string[] },
1043
+ ): Record<string, string> {
1044
+ const removed = new Set([...(launch.unsetEnv ?? []), ...Object.keys(launch.env ?? {})].map(sessionEnvKey));
1045
+ // Valid names like `__proto__` must remain own environment entries.
1046
+ const base = Object.create(null) as Record<string, string>;
1047
+ for (const [name, value] of Object.entries(inherited)) {
1048
+ if (name === "GJC_MASTER_CAPABILITY" || typeof value !== "string" || removed.has(sessionEnvKey(name))) continue;
1049
+ base[name] = value;
1050
+ }
1051
+ return { ...base, ...launch.env };
1052
+ }
966
1053
 
967
1054
  function lifecycleLaunchKnownSecrets(launch: SessionLaunch): string[] {
968
1055
  const secrets = [
@@ -985,6 +1072,9 @@ function lifecycleLaunchKnownSecrets(launch: SessionLaunch): string[] {
985
1072
  .filter(([name, value]) => value && CREDENTIAL_NAME_PATTERN.test(name))
986
1073
  .map(([, value]) => value);
987
1074
  }),
1075
+ ...Object.entries(launch.env ?? {})
1076
+ .filter(([name, value]) => value && CREDENTIAL_NAME_PATTERN.test(name))
1077
+ .map(([, value]) => value),
988
1078
  ];
989
1079
  return [...new Set(secrets.filter(secret => typeof secret === "string" && secret.length > 0))];
990
1080
  }
@@ -5998,6 +6088,12 @@ async function launchInput(
5998
6088
  if (input.mcpServers !== undefined && !isSessionLifecycleMcpServers(input.mcpServers))
5999
6089
  return fail("invalid_input", "mcpServers must contain unique valid stdio, HTTP, or SSE server definitions.");
6000
6090
  const mcpServers = input.mcpServers as SessionLifecycleMcpServer[] | undefined;
6091
+ const sessionEnv = lifecycleSessionEnv(input);
6092
+ if (!sessionEnv)
6093
+ return fail(
6094
+ "invalid_input",
6095
+ "env must map valid non-GJC_ variable names to strings, and unsetEnv must list distinct valid names not also set in env.",
6096
+ );
6001
6097
 
6002
6098
  /**
6003
6099
  * Prepared readiness is an explicit creation-only intent. Only the two exact
@@ -6047,6 +6143,7 @@ async function launchInput(
6047
6143
  modelId,
6048
6144
  modelPreset,
6049
6145
  mcpServers,
6146
+ ...sessionEnv,
6050
6147
  worktree,
6051
6148
  worktreePlan,
6052
6149
  ...(readiness ? { readiness } : {}),
@@ -6070,6 +6167,7 @@ async function launchInput(
6070
6167
  sessionIdentity: saved.identity,
6071
6168
  modelPreset,
6072
6169
  mcpServers,
6170
+ ...sessionEnv,
6073
6171
  worktree,
6074
6172
  worktreePlan,
6075
6173
  ...(coordinatorStateDir ? { coordinatorStateDir } : {}),
@@ -6104,6 +6202,7 @@ async function launchInput(
6104
6202
  modelId,
6105
6203
  modelPreset,
6106
6204
  mcpServers,
6205
+ ...sessionEnv,
6107
6206
  worktree,
6108
6207
  worktreePlan,
6109
6208
  ...(coordinatorStateDir ? { coordinatorStateDir } : {}),
@@ -7160,12 +7259,9 @@ async function executeLifecycleResponse(
7160
7259
  stdio: ["ignore", "ignore", childStderrSink?.drainer.stdin ?? "ignore"],
7161
7260
  env: {
7162
7261
  // Master capability is process-local to direct Bash children and must
7163
- // never cross a broker lifecycle launch boundary.
7164
- ...Object.fromEntries(
7165
- Object.entries("kind" in cmd ? cmd.env : process.env).filter(
7166
- ([key]) => key !== "GJC_MASTER_CAPABILITY",
7167
- ),
7168
- ),
7262
+ // never cross a broker lifecycle launch boundary. Per-session env
7263
+ // (#6735) is layered here, below the broker-issued GJC_* keys.
7264
+ ...lifecycleSessionHostBaseEnv("kind" in cmd ? cmd.env : process.env, launch),
7169
7265
  GJC_AGENT_DIR: broker.settings.agentDir,
7170
7266
  GJC_CODING_AGENT_DIR: broker.settings.agentDir,
7171
7267
  GJC_SESSION_ID: launch.id,
@@ -0,0 +1,29 @@
1
+ import path from "node:path";
2
+ import { loadInstallationHostId, loadLegacyInstallationHostId } from "../../config/machine-identity";
3
+
4
+ export interface BrokerLockHostIdentity {
5
+ ownerHostId: string;
6
+ previousOwnerHostIds: readonly string[];
7
+ }
8
+
9
+ const brokerLockHostIdentityPromises = new Map<string, Promise<BrokerLockHostIdentity>>();
10
+
11
+ export async function brokerLockHostIdentity(agentDir: string): Promise<BrokerLockHostIdentity> {
12
+ const coordinationRoot = path.resolve(agentDir, "sdk");
13
+ const pending =
14
+ brokerLockHostIdentityPromises.get(coordinationRoot) ??
15
+ Promise.all([loadInstallationHostId({ configRootDir: coordinationRoot }), loadLegacyInstallationHostId()]).then(
16
+ ([ownerHostId, legacyHostId]) => ({
17
+ ownerHostId,
18
+ previousOwnerHostIds: legacyHostId === ownerHostId ? [] : [legacyHostId],
19
+ }),
20
+ );
21
+ brokerLockHostIdentityPromises.set(coordinationRoot, pending);
22
+ try {
23
+ return await pending;
24
+ } catch (error) {
25
+ if (brokerLockHostIdentityPromises.get(coordinationRoot) === pending)
26
+ brokerLockHostIdentityPromises.delete(coordinationRoot);
27
+ throw error;
28
+ }
29
+ }