@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
@@ -29,6 +29,7 @@ import asyncio
29
29
  import ast
30
30
  import base64
31
31
  import builtins
32
+ import codecs
32
33
  import inspect
33
34
  import io
34
35
  import json
@@ -49,9 +50,14 @@ from typing import Any, Callable
49
50
  # Frame writer
50
51
  # ---------------------------------------------------------------------------
51
52
 
52
- _RAW_STDOUT = sys.__stdout__
53
53
  _RAW_STDERR = sys.__stderr__
54
54
  _OUT_LOCK = threading.Lock()
55
+ _PROTO_FD: int | None = None
56
+ _CAP_LOCK = threading.Lock()
57
+ _FLUSH_EVENT = threading.Event()
58
+ _FLUSH_TOKEN: bytes | None = None
59
+ _CAPTURED = b""
60
+ _CAPTURE_DECODER = codecs.getincrementaldecoder("utf-8")(errors="replace")
55
61
 
56
62
 
57
63
  def _json_default(o: Any) -> Any:
@@ -61,13 +67,141 @@ def _json_default(o: Any) -> Any:
61
67
  return f"<unrepr {type(o).__name__}>"
62
68
 
63
69
 
70
+ def _write_all(fd: int, data: bytes) -> None:
71
+ view = memoryview(data)
72
+ while len(view):
73
+ written = os.write(fd, view)
74
+ if written <= 0:
75
+ raise OSError(0, "protocol write returned no bytes")
76
+ view = view[written:]
77
+
78
+
64
79
  def _emit(frame: dict) -> None:
65
80
  """Serialize a frame and write it to the host as a single NDJSON line."""
66
- line = json.dumps(frame, ensure_ascii=False, default=_json_default)
81
+ payload = (json.dumps(frame, ensure_ascii=False, default=_json_default) + "\n").encode("utf-8")
82
+ fd = _PROTO_FD if _PROTO_FD is not None else 1
67
83
  with _OUT_LOCK:
68
- _RAW_STDOUT.write(line)
69
- _RAW_STDOUT.write("\n")
70
- _RAW_STDOUT.flush()
84
+ _write_all(fd, payload)
85
+
86
+
87
+ def _token_suffix_len(buf: bytes, token: bytes) -> int:
88
+ max_n = min(len(buf), len(token) - 1)
89
+ for size in range(max_n, 0, -1):
90
+ if buf.endswith(token[:size]):
91
+ return size
92
+ return 0
93
+
94
+
95
+ def _take_captured_locked() -> tuple[list[bytes], bool]:
96
+ """Split captured fd-1 bytes. A flush token is a delimiter, not output."""
97
+ global _CAPTURED, _FLUSH_TOKEN
98
+ pieces: list[bytes] = []
99
+ flushed = False
100
+ while True:
101
+ token = _FLUSH_TOKEN
102
+ if token is None:
103
+ if _CAPTURED:
104
+ pieces.append(_CAPTURED)
105
+ _CAPTURED = b""
106
+ return pieces, flushed
107
+ idx = _CAPTURED.find(token)
108
+ if idx < 0:
109
+ keep = _token_suffix_len(_CAPTURED, token)
110
+ if len(_CAPTURED) > keep:
111
+ pieces.append(_CAPTURED[: len(_CAPTURED) - keep])
112
+ _CAPTURED = _CAPTURED[-keep:] if keep else b""
113
+ return pieces, flushed
114
+ if idx:
115
+ pieces.append(_CAPTURED[:idx])
116
+ _CAPTURED = _CAPTURED[idx + len(token) :]
117
+ _FLUSH_TOKEN = None
118
+ flushed = True
119
+
120
+
121
+ def _publish_captured(pieces: list[bytes], flushed: bool) -> None:
122
+ global _CAPTURE_DECODER
123
+ for piece in pieces:
124
+ text = _CAPTURE_DECODER.decode(piece)
125
+ if text:
126
+ _forward_captured_stdout(text)
127
+ if not flushed:
128
+ return
129
+ tail = _CAPTURE_DECODER.decode(b"", final=True)
130
+ if tail:
131
+ _forward_captured_stdout(tail)
132
+ _CAPTURE_DECODER = codecs.getincrementaldecoder("utf-8")(errors="replace")
133
+
134
+
135
+ def _forward_captured_stdout(text: str) -> None:
136
+ rid = _STATE.current_id
137
+ if not text:
138
+ return
139
+ if rid is None:
140
+ _RAW_STDERR.write(text)
141
+ _RAW_STDERR.flush()
142
+ return
143
+ _emit({"type": "stdout", "id": rid, "data": text})
144
+
145
+
146
+ def _pump_captured_stdout(read_fd: int) -> None:
147
+ global _CAPTURED
148
+ while True:
149
+ try:
150
+ chunk = os.read(read_fd, 65536)
151
+ except OSError:
152
+ return
153
+ if not chunk:
154
+ return
155
+ with _CAP_LOCK:
156
+ _CAPTURED += chunk
157
+ pieces, flushed = _take_captured_locked()
158
+ _publish_captured(pieces, flushed)
159
+ if flushed:
160
+ _FLUSH_EVENT.set()
161
+
162
+
163
+ def _capture_user_stdout() -> None:
164
+ """Move the host pipe off fd 1 so raw user writes are cell output."""
165
+ global _PROTO_FD
166
+ if _PROTO_FD is not None:
167
+ return
168
+ try:
169
+ sys.__stdout__.flush()
170
+ except Exception:
171
+ pass
172
+ proto = os.dup(1)
173
+ os.set_inheritable(proto, False)
174
+ _PROTO_FD = proto
175
+ read_fd, write_fd = os.pipe()
176
+ os.set_inheritable(read_fd, False)
177
+ os.dup2(write_fd, 1, inheritable=True)
178
+ os.close(write_fd)
179
+ threading.Thread(
180
+ target=_pump_captured_stdout,
181
+ args=(read_fd,),
182
+ name="gjc-stdout-capture",
183
+ daemon=True,
184
+ ).start()
185
+
186
+
187
+ def _flush_captured_stdout() -> None:
188
+ if _PROTO_FD is None:
189
+ return
190
+ token = b"\0gjcfd1:" + os.urandom(8).hex().encode("ascii") + b"\0"
191
+ with _CAP_LOCK:
192
+ global _FLUSH_TOKEN
193
+ _FLUSH_EVENT.clear()
194
+ _FLUSH_TOKEN = token
195
+ _write_all(1, token)
196
+ if _FLUSH_EVENT.wait(5):
197
+ return
198
+ with _CAP_LOCK:
199
+ if _FLUSH_TOKEN == token:
200
+ _FLUSH_TOKEN = None
201
+ pieces, flushed = _take_captured_locked()
202
+ else:
203
+ pieces, flushed = [], False
204
+ _publish_captured(pieces, flushed)
71
205
 
72
206
 
73
207
  # ---------------------------------------------------------------------------
@@ -902,6 +1036,10 @@ def _handle_request(req: dict) -> None:
902
1036
  _flush_matplotlib_figures()
903
1037
  except Exception:
904
1038
  pass
1039
+ try:
1040
+ _flush_captured_stdout()
1041
+ except Exception:
1042
+ pass
905
1043
 
906
1044
  _emit({
907
1045
  "type": "done",
@@ -930,6 +1068,7 @@ def _emit_error(rid: str, exc: BaseException) -> None:
930
1068
 
931
1069
 
932
1070
  def main() -> None:
1071
+ _capture_user_stdout()
933
1072
  sys.stdout = _StreamProxy("stdout")
934
1073
  sys.stderr = _StreamProxy("stderr")
935
1074
  _install_idle_sigint()
@@ -7,8 +7,9 @@
7
7
  */
8
8
  import * as fs from "node:fs";
9
9
  import * as path from "node:path";
10
- import { $env, $which, getPythonEnvDir, WhichCachePolicy } from "@gajae-code/utils";
10
+ import { $env, $which, getPythonEnvDir, relativePathEscapesRoot, WhichCachePolicy } from "@gajae-code/utils";
11
11
  import { withFileLock } from "../../config/file-lock";
12
+ import { isProjectControlledPath } from "../../lsp/path-trust";
12
13
 
13
14
  export const RLM_MANAGED_PYTHON_PACKAGES: readonly string[] = ["numpy", "pandas", "matplotlib", "polars"];
14
15
 
@@ -133,6 +134,47 @@ function resolvePythonCandidateInVenv(venvPath: string): { venvPath: string; pyt
133
134
  return { venvPath, pythonPath, binDir };
134
135
  }
135
136
 
137
+ /**
138
+ * A workspace `.venv` or `venv` directory is repository content. Use it only when
139
+ * it is a real virtualenv (`pyvenv.cfg`) whose interpreter file resolves outside
140
+ * the project. An in-tree script or binary is not an interpreter.
141
+ *
142
+ * True when `candidate` is `root` or a descendant after `path.resolve`.
143
+ * A name such as `..payload` stays inside. Only `..` and a parent segment escape.
144
+ */
145
+ export function workspacePathContained(root: string, candidate: string): boolean {
146
+ const normalize = (value: string): string => {
147
+ const resolved = path.resolve(value);
148
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
149
+ };
150
+ return !relativePathEscapesRoot(path.relative(normalize(root), normalize(candidate)));
151
+ }
152
+
153
+ function isAutoDetectedWorkspaceVenv(candidate: string, cwd: string): boolean {
154
+ try {
155
+ if (!fs.statSync(path.join(candidate, "pyvenv.cfg")).isFile()) return false;
156
+ } catch {
157
+ return false;
158
+ }
159
+ let canonicalPython: string;
160
+ try {
161
+ canonicalPython = fs.realpathSync(resolvePythonCandidateInVenv(candidate).pythonPath);
162
+ } catch {
163
+ return false;
164
+ }
165
+ // HOME stops project-root discovery, so a workspace that is the home directory
166
+ // is not project-controlled. Refuse an interpreter that still resolves inside
167
+ // cwd, including a file whose name is `..payload`.
168
+ let canonicalCwd = path.resolve(cwd);
169
+ try {
170
+ canonicalCwd = fs.realpathSync(cwd);
171
+ } catch {
172
+ // Missing cwd keeps the resolved path.
173
+ }
174
+ if (workspacePathContained(canonicalCwd, canonicalPython)) return false;
175
+ return !isProjectControlledPath(canonicalPython, cwd);
176
+ }
177
+
136
178
  function resolveManagedPythonCandidate(): { venvPath: string; pythonPath: string; binDir: string } {
137
179
  return resolvePythonCandidateInVenv(resolveGlobalManagedPythonEnv());
138
180
  }
@@ -197,9 +239,7 @@ export function resolveVenvPath(cwd: string): string | undefined {
197
239
  if ($env.CONDA_PREFIX) return $env.CONDA_PREFIX;
198
240
  const candidates = [path.join(cwd, ".venv"), path.join(cwd, "venv")];
199
241
  for (const candidate of candidates) {
200
- if (fs.existsSync(candidate)) {
201
- return candidate;
202
- }
242
+ if (isAutoDetectedWorkspaceVenv(candidate, cwd)) return candidate;
203
243
  }
204
244
  return undefined;
205
245
  }
@@ -12,6 +12,7 @@ import type {
12
12
  ShellRunResult,
13
13
  } from "@gajae-code/natives";
14
14
  import { postmortem } from "@gajae-code/utils";
15
+ import { projectDeclaredSpawnHookNames, stripProjectSpawnHooks } from "@gajae-code/utils/shell-config";
15
16
  import { Settings, type ShellMinimizerSettings } from "../config/settings";
16
17
  import { formatCrashDiagnosticNotice, writeCrashReport } from "../debug/crash-diagnostics";
17
18
  import {
@@ -93,6 +94,20 @@ export function setShellFactoryForTests(factory: ShellFactory | undefined): void
93
94
  shellFactoryForTests = factory;
94
95
  }
95
96
 
97
+ function projectHookUnsetEnv(
98
+ unsetEnv: string[] | undefined,
99
+ env: Record<string, string> | undefined,
100
+ sessionEnv: Record<string, string>,
101
+ ): string[] | undefined {
102
+ // Keys present in `sessionEnv` (e.g. the trusted HOME replacement) already
103
+ // override the ambient import, so only shadow the ones it does not carry.
104
+ const hooks = projectDeclaredSpawnHookNames().filter(
105
+ name => !Object.hasOwn(env ?? {}, name) && !Object.hasOwn(sessionEnv, name),
106
+ );
107
+ if (hooks.length === 0) return unsetEnv;
108
+ return [...(unsetEnv ?? []), ...hooks.filter(name => !unsetEnv?.includes(name))];
109
+ }
110
+
96
111
  async function createShell(
97
112
  options?: ShellOptions,
98
113
  onTerminal?: (shell: Shell) => void,
@@ -414,13 +429,67 @@ async function resolveShellCwd(cwd: string | undefined): Promise<string | undefi
414
429
  }
415
430
  }
416
431
 
432
+ function presentMinimizerString(value: unknown): string | undefined {
433
+ return typeof value === "string" && value.length > 0 ? value : undefined;
434
+ }
435
+
436
+ type ShellMinimizerSource = {
437
+ getGroup(prefix: "shellMinimizer"): ShellMinimizerSettings;
438
+ getGlobal?(path: "shellMinimizer.settingsPath" | "shellMinimizer.settingsHash"): unknown;
439
+ getOverride?(path: "shellMinimizer.settingsPath"): unknown;
440
+ };
441
+
442
+ /**
443
+ * A repo `.gjc/settings.json` must not point the minimizer at a TOML file.
444
+ * The native gate loads that file only when `settingsHash` matches, and the
445
+ * pin is the user/global value. A user path or a runtime override is still
446
+ * passed through. A project path is passed only together with that pin.
447
+ */
448
+ export function resolveTrustedShellMinimizer(settings: ShellMinimizerSource): ShellMinimizerSettings {
449
+ const group = settings.getGroup("shellMinimizer");
450
+ const globalPath =
451
+ typeof settings.getGlobal === "function"
452
+ ? presentMinimizerString(settings.getGlobal("shellMinimizer.settingsPath"))
453
+ : undefined;
454
+ const globalHash =
455
+ typeof settings.getGlobal === "function"
456
+ ? presentMinimizerString(settings.getGlobal("shellMinimizer.settingsHash"))
457
+ : undefined;
458
+ const overridePath =
459
+ typeof settings.getOverride === "function"
460
+ ? presentMinimizerString(settings.getOverride("shellMinimizer.settingsPath"))
461
+ : undefined;
462
+ const effectivePath = presentMinimizerString(group.settingsPath);
463
+
464
+ let settingsPath: string | undefined;
465
+ if (overridePath && effectivePath === overridePath) {
466
+ settingsPath = overridePath;
467
+ } else if (globalPath) {
468
+ settingsPath = globalPath;
469
+ } else if (effectivePath && globalHash) {
470
+ settingsPath = effectivePath;
471
+ }
472
+
473
+ // A runtime override of a different path is operator input, not the file the
474
+ // user hash pins. Every other admitted path is checked with that pin.
475
+ const unpinnedOverride = overridePath !== undefined && settingsPath === overridePath && settingsPath !== globalPath;
476
+ return {
477
+ ...group,
478
+ settingsPath,
479
+ settingsHash: globalHash && settingsPath && !unpinnedOverride ? globalHash : undefined,
480
+ };
481
+ }
482
+
417
483
  /** Translate `ShellMinimizerSettings` into native `MinimizerOptions`, or `undefined` when disabled. */
418
484
  export function buildMinimizerOptions(group: ShellMinimizerSettings): MinimizerOptions | undefined {
419
485
  if (!group.enabled) return undefined;
420
486
  const maxCaptureBytes = Math.min(4 * 1024 * 1024, Math.max(1024, Math.trunc(group.maxCaptureBytes)));
487
+ const settingsPath = group.settingsPath || undefined;
488
+ const settingsHash = settingsPath && group.settingsHash ? group.settingsHash : undefined;
421
489
  return {
422
490
  enabled: true,
423
- settingsPath: group.settingsPath || undefined,
491
+ settingsPath,
492
+ ...(settingsHash ? { settingsHash } : {}),
424
493
  only: group.only.length > 0 ? group.only : undefined,
425
494
  except: group.except.length > 0 ? group.except : undefined,
426
495
  maxCaptureBytes,
@@ -429,12 +498,16 @@ export function buildMinimizerOptions(group: ShellMinimizerSettings): MinimizerO
429
498
 
430
499
  export async function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
431
500
  const settings = options?.settings ?? (await Settings.init());
432
- const { shell, env: shellEnv, prefix } = settings.getShellConfig();
501
+ const { shell, env: configEnv, prefix } = settings.getShellConfig();
502
+ // The shell config env is shared with other consumers (Python kernels keep
503
+ // their own documented allowlist). Only the bash tool's shell must not see
504
+ // project hook variables such as BASH_ENV or GIT_CONFIG_*.
505
+ const shellEnv = stripProjectSpawnHooks({ ...configEnv });
433
506
  const configuredPrefix = options?.ignoreShellPrefix ? undefined : prefix;
434
507
  const snapshotPath =
435
508
  !options?.disableShellSnapshot && shell.includes("bash") ? await getOrCreateSnapshot(shell, shellEnv) : null;
436
509
 
437
- const minimizer = buildMinimizerOptions(settings.getGroup("shellMinimizer"));
510
+ const minimizer = buildMinimizerOptions(resolveTrustedShellMinimizer(settings));
438
511
 
439
512
  const commandCwd = await resolveShellCwd(options?.cwd);
440
513
  const commandEnv = options?.env ? { ...NON_INTERACTIVE_ENV, ...options.env } : NON_INTERACTIVE_ENV;
@@ -580,7 +653,11 @@ export async function executeBash(command: string, options?: BashExecutorOptions
580
653
  command: finalCommand,
581
654
  cwd: commandCwd,
582
655
  env: commandEnv,
583
- unsetEnv: options?.unsetEnv,
656
+ // The native shell imports the host process environment before
657
+ // `sessionEnv`, and where it runs in-process (Windows) that environment
658
+ // holds the autoloaded project `.env`. Shadow project hook variables per
659
+ // run unless this call sets them explicitly.
660
+ unsetEnv: projectHookUnsetEnv(options?.unsetEnv, options?.env, shellEnv),
584
661
  timeoutMs: executionTimeoutMs,
585
662
  signal: runAbortController.signal,
586
663
  };
@@ -8,6 +8,7 @@ import * as path from "node:path";
8
8
  import * as readline from "node:readline";
9
9
  import type { Process as NativeProcess } from "@gajae-code/natives";
10
10
  import { isCompiledBinary } from "@gajae-code/utils/env";
11
+ import { projectSafeSpawnEnv } from "@gajae-code/utils/shell-config";
11
12
  import { BASH_SHELL_SUPERVISOR_ARG, type BashShellWorkerRequest } from "./bash-shell-worker-protocol";
12
13
 
13
14
  type NativeProcessBindings = { Process: typeof NativeProcess };
@@ -401,7 +402,9 @@ export async function runBashShellGuardian(): Promise<void> {
401
402
  const [executable, ...args] = supervisorArgv();
402
403
  const supervisor = childProcess.spawn(executable!, args, {
403
404
  detached: process.platform !== "win32",
404
- env: process.env,
405
+ // This process reloaded the project `.env` on startup; keep project hook
406
+ // variables out of the supervisor -> runtime -> native shell chain.
407
+ env: projectSafeSpawnEnv(),
405
408
  stdio: ["pipe", "inherit", "inherit"],
406
409
  windowsHide: true,
407
410
  });
@@ -8,6 +8,7 @@ import * as path from "node:path";
8
8
  import * as readline from "node:readline";
9
9
  import type { Process as NativeProcess } from "@gajae-code/natives";
10
10
  import { isCompiledBinary } from "@gajae-code/utils/env";
11
+ import { projectSafeSpawnEnv } from "@gajae-code/utils/shell-config";
11
12
  import { parseLinuxProcParentPid, parseLinuxProcStartTime, parseLinuxProcState } from "../gjc-runtime/linux-proc";
12
13
  import { processIncarnation } from "../sdk/broker/process-incarnation";
13
14
  import { fatalCatchableSignals, signalExitCode } from "./bash-shell-signals";
@@ -39,9 +40,10 @@ function darwinUniqueProcessId(pid: number, incarnation: string): string | undef
39
40
  }
40
41
 
41
42
  function runtimeArgv(): string[] {
42
- return isCompiledBinary()
43
- ? [process.execPath, BASH_SHELL_RUNTIME_ARG]
44
- : [process.execPath, path.join(import.meta.dir, "bash-shell-runtime-entry.ts")];
43
+ if (isCompiledBinary()) return [process.execPath, BASH_SHELL_RUNTIME_ARG];
44
+ // The supervisor already sanitized this environment. Do not let the source
45
+ // runtime reload a cwd `.env` that may have changed since the snapshot.
46
+ return [process.execPath, "--no-env-file", path.join(import.meta.dir, "bash-shell-runtime-entry.ts")];
45
47
  }
46
48
 
47
49
  function enableLinuxChildSubreaper(): void {
@@ -264,7 +266,9 @@ export async function runBashShellSupervisor(): Promise<void> {
264
266
  const worker = childProcess.spawn(command!, args, {
265
267
  cwd: process.cwd(),
266
268
  detached: false,
267
- env: { ...process.env, GJC_ISOLATED_SHELL_RUNTIME: "1" },
269
+ // This process reloaded the project `.env` on startup; the runtime's native
270
+ // shell imports its environment wholesale, so drop project hook variables.
271
+ env: { ...projectSafeSpawnEnv(), GJC_ISOLATED_SHELL_RUNTIME: "1" },
268
272
  stdio: ["pipe", "pipe", "pipe"],
269
273
  windowsHide: true,
270
274
  });
@@ -5,6 +5,7 @@ import * as path from "node:path";
5
5
  import * as readline from "node:readline";
6
6
  import type { ShellRunOptions } from "@gajae-code/natives";
7
7
  import { isCompiledBinary } from "@gajae-code/utils/env";
8
+ import { projectSafeSpawnEnv } from "@gajae-code/utils/shell-config";
8
9
  import {
9
10
  BASH_SHELL_WORKER_ARG,
10
11
  type BashShellWorkerRequest,
@@ -84,7 +85,10 @@ export class IsolatedShell {
84
85
  this.#child = childProcess.spawn(command!, args, {
85
86
  cwd: process.cwd(),
86
87
  detached: process.platform !== "win32",
87
- env: process.env,
88
+ // The worker chain (guardian -> supervisor -> runtime) inherits this env and
89
+ // the native shell imports it wholesale, so project hook variables must be
90
+ // gone here, not only from the per-session overlay.
91
+ env: projectSafeSpawnEnv(),
88
92
  stdio: ["pipe", "pipe", "pipe"],
89
93
  windowsHide: true,
90
94
  });
@@ -24,7 +24,6 @@ export const NON_INTERACTIVE_ENV: Readonly<Record<string, string>> = {
24
24
  EDITOR: "true",
25
25
  GIT_TERMINAL_PROMPT: "0",
26
26
  SSH_ASKPASS: "/usr/bin/false",
27
- CI: "1",
28
27
  // Package manager defaults for unattended execution.
29
28
  npm_config_yes: "true",
30
29
  npm_config_update_notifier: "false",
@@ -2,7 +2,7 @@ import * as syncFs from "node:fs";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import type { AgentState } from "@gajae-code/agent-core";
5
- import { APP_NAME, isEnoent } from "@gajae-code/utils";
5
+ import { APP_NAME, isCredentialKey, isEnoent, redactCrashSecrets } from "@gajae-code/utils";
6
6
  import { getResolvedThemeColors, getThemeExportColors } from "../../modes/theme/theme";
7
7
  import { SessionManager } from "../../session/session-manager";
8
8
  // Pre-generated template (created by scripts/generate-template.ts at publish time)
@@ -226,18 +226,18 @@ async function writeSessionHtml(
226
226
  };
227
227
  try {
228
228
  sink.write(themedTemplate.slice(0, markerOffset));
229
- writeEncoded(`{"header":${JSON.stringify(sm.getHeader())},"entries":[`);
229
+ writeEncoded(`{"header":${sessionEmbedJson(sm.getHeader())},"entries":[`);
230
230
  let first = true;
231
231
  sm.visitEntriesForExport(entry => {
232
- writeEncoded(`${first ? "" : ","}${JSON.stringify(entry)}`);
232
+ writeEncoded(`${first ? "" : ","}${sessionEmbedJson(entry)}`);
233
233
  first = false;
234
234
  });
235
- writeEncoded(`],"leafId":${JSON.stringify(sm.getLeafId())}`);
235
+ writeEncoded(`],"leafId":${sessionEmbedJson(sm.getLeafId())}`);
236
236
  if (state?.systemPrompt !== undefined)
237
- writeEncoded(`,"systemPrompt":${JSON.stringify(state.systemPrompt.join("\n\n"))}`);
237
+ writeEncoded(`,"systemPrompt":${sessionEmbedJson(state.systemPrompt.join("\n\n"))}`);
238
238
  if (state?.tools !== undefined)
239
239
  writeEncoded(
240
- `,"tools":${JSON.stringify(state.tools.map(tool => ({ name: tool.name, description: tool.description })))}`,
240
+ `,"tools":${sessionEmbedJson(state.tools.map(tool => ({ name: tool.name, description: tool.description })))}`,
241
241
  );
242
242
  writeEncoded("}");
243
243
  const final = encoder.finish();
@@ -253,6 +253,86 @@ async function writeSessionHtml(
253
253
  }
254
254
  }
255
255
 
256
+ /**
257
+ * Own `__proto__` stays a data property; a normal object would turn that assignment into the prototype.
258
+ * Credential keys mask every JSON value, and string values are scrubbed directly so JSON escaping
259
+ * cannot turn ordinary quote characters into credential-value delimiters.
260
+ */
261
+ function redactJsonValue(value: unknown): unknown {
262
+ if (typeof value === "string") return redactCrashSecrets(value);
263
+ if (Array.isArray(value)) {
264
+ const isFlatKeyValueArray =
265
+ value.length > 0 &&
266
+ value.length % 2 === 0 &&
267
+ value.every((item, index) => index % 2 === 1 || typeof item === "string");
268
+ if (isFlatKeyValueArray) {
269
+ return value.map((item, index) => {
270
+ if (index % 2 === 1) {
271
+ const key = value[index - 1];
272
+ if (typeof key === "string" && isCredentialKey(key)) return "«redacted»";
273
+ }
274
+ return redactJsonValue(item);
275
+ });
276
+ }
277
+ return value.map(item => redactJsonValue(item));
278
+ }
279
+ if (value === null || typeof value !== "object") return value;
280
+ const source = value as Record<string, unknown>;
281
+ const sourceKeys = Object.keys(source);
282
+ let hasCredentialHeaderName = false;
283
+ let hasHeaderValue = false;
284
+ for (const key of sourceKeys) {
285
+ const field = key.toLowerCase();
286
+ if (field === "value" || field === "values") hasHeaderValue = true;
287
+ if (field !== "name" && field !== "key") continue;
288
+ const headerName = source[key];
289
+ if (typeof headerName === "string" && isCredentialKey(headerName)) hasCredentialHeaderName = true;
290
+ }
291
+ const redactHeaderValue = hasCredentialHeaderName && hasHeaderValue;
292
+ const redacted = Object.create(null) as Record<string, unknown>;
293
+ const nextSuffixByKey = new Map<string, number>();
294
+ for (const key of sourceKeys) {
295
+ const redactedKey = uniqueJsonObjectKey(redacted, redactCrashSecrets(key), nextSuffixByKey);
296
+ const field = key.toLowerCase();
297
+ if (redactHeaderValue && (field === "value" || field === "values")) {
298
+ const headerValue = source[key];
299
+ redacted[redactedKey] =
300
+ field === "values" && Array.isArray(headerValue) ? headerValue.map(() => "«redacted»") : "«redacted»";
301
+ } else {
302
+ redacted[redactedKey] = redactKeyedValue(key, source[key]);
303
+ }
304
+ }
305
+ return redacted;
306
+ }
307
+
308
+ // Resume after the last collision for this base key rather than rescanning prior suffixes.
309
+ function uniqueJsonObjectKey(
310
+ target: Record<string, unknown>,
311
+ key: string,
312
+ nextSuffixByKey: Map<string, number>,
313
+ ): string {
314
+ if (!Object.hasOwn(target, key)) return key;
315
+ let suffix = nextSuffixByKey.get(key) ?? 2;
316
+ let candidate = `${key}#${suffix}`;
317
+ while (Object.hasOwn(target, candidate)) {
318
+ suffix += 1;
319
+ candidate = `${key}#${suffix}`;
320
+ }
321
+ nextSuffixByKey.set(key, suffix + 1);
322
+ return candidate;
323
+ }
324
+
325
+ function redactKeyedValue(key: string, value: unknown): unknown {
326
+ // A recognized key is authoritative for every JSON type, including a short
327
+ // string, a number, an array, or a nested object.
328
+ if (isCredentialKey(key)) return "«redacted»";
329
+ return typeof value === "string" ? redactCrashSecrets(value) : redactJsonValue(value);
330
+ }
331
+
332
+ function sessionEmbedJson(value: unknown): string {
333
+ return JSON.stringify(redactJsonValue(JSON.parse(JSON.stringify(value))));
334
+ }
335
+
256
336
  /** Export session to HTML using SessionManager and AgentState. */
257
337
  export async function exportSessionToHtml(
258
338
  sm: SessionManager,