@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
@@ -9,7 +9,11 @@
9
9
 
10
10
  import * as os from "node:os";
11
11
  import * as path from "node:path";
12
- import type { AuthCredentialSelector, CredentialRankingMode } from "@gajae-code/ai/core";
12
+ import type {
13
+ AuthCredentialSelector,
14
+ ConfiguredCredentialPreference,
15
+ CredentialRankingMode,
16
+ } from "@gajae-code/ai/core";
13
17
  import { $credentialEnv, getAgentDbPath, getAgentDir, getConfigRootDir, isEnoent, logger } from "@gajae-code/utils";
14
18
  import { YAML } from "bun";
15
19
  import { readSecureTokenFile } from "./secure-token-file";
@@ -27,6 +31,8 @@ export interface StartupAuthConfigSnapshot {
27
31
  credentialPinStoreIdentity?: string;
28
32
  credentialRankingMode: CredentialRankingMode;
29
33
  credentialPins: Readonly<Record<string, string>>;
34
+ /** Ordered soft per-provider preferences; only portable email/`account:` selectors are accepted. */
35
+ credentialPreferences: Readonly<Record<string, readonly ConfiguredCredentialPreference[]>>;
30
36
  }
31
37
 
32
38
  export type StartupAuthConfigErrorKind =
@@ -37,7 +43,8 @@ export type StartupAuthConfigErrorKind =
37
43
  | "invalid-broker"
38
44
  | "invalid-gateway"
39
45
  | "invalid-ranking-mode"
40
- | "invalid-credential-pins";
46
+ | "invalid-credential-pins"
47
+ | "invalid-credential-preferences";
41
48
 
42
49
  /** A malformed startup-auth config aborts resolution rather than downgrading to local authority. */
43
50
  export class StartupAuthConfigError extends Error {
@@ -64,6 +71,7 @@ const LEGACY_LITERAL_AUTH_KEYS = new Set([
64
71
  "auth.broker.token",
65
72
  "auth.credentialRankingMode",
66
73
  "auth.credentialPins",
74
+ "auth.credentialPreferences",
67
75
  ]);
68
76
 
69
77
  function configPathForAgentDir(agentDir: string): string {
@@ -78,7 +86,7 @@ function shortenConfigPath(configPath: string): string {
78
86
  return absolutePath.startsWith(homePrefix) ? `~${absolutePath.slice(homePath.length)}` : absolutePath;
79
87
  }
80
88
 
81
- function startupAuthConfigError(agentDir: string, kind: StartupAuthConfigErrorKind): StartupAuthConfigError {
89
+ export function startupAuthConfigError(agentDir: string, kind: StartupAuthConfigErrorKind): StartupAuthConfigError {
82
90
  return new StartupAuthConfigError(kind, configPathForAgentDir(agentDir));
83
91
  }
84
92
 
@@ -100,6 +108,69 @@ export function isValidPersistedCredentialSelector(value: string): boolean {
100
108
  return parsePersistedCredentialSelector(value) !== undefined;
101
109
  }
102
110
 
111
+ /** JSON-schema pattern for a preference account; shared with {@link parsePreferenceAccount}. */
112
+ export const PREFERENCE_ACCOUNT_PATTERN = "^(email:[^@\\s]+@[^@\\s]+|account:\\S+|[^@\\s:]+@[^@\\s]+)$";
113
+
114
+ /**
115
+ * Parse one `auth.credentialPreferences` account: a bare email, `email:<address>`, or `account:<id>`. Numeric row ids
116
+ * are excluded: they are bound to one credential store, and a preference has no store-identity binding to detect a
117
+ * retargeted row.
118
+ */
119
+ export function parsePreferenceAccount(value: string): AuthCredentialSelector | undefined {
120
+ const trimmed = value.trim();
121
+ const selector = parsePersistedCredentialSelector(trimmed);
122
+ if (selector) return selector.kind === "id" ? undefined : selector;
123
+ return /^[^@\s:]+@[^@\s]+$/.test(trimmed) ? { kind: "email", value: trimmed } : undefined;
124
+ }
125
+
126
+ /** A preference cap accepts (0, 100]; 100 moves past the account only once it is blocked. */
127
+ export function isValidCredentialPreferenceCap(value: unknown): value is number {
128
+ return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= 100;
129
+ }
130
+
131
+ /** Canonical config text for one preference entry: the account, or a one-key `{ account: cap }` mapping. */
132
+ export type CredentialPreferenceEntryValue = string | Readonly<Record<string, number>>;
133
+
134
+ function parseCredentialPreferenceEntry(value: unknown): ConfiguredCredentialPreference | undefined {
135
+ if (typeof value === "string") {
136
+ const selector = parsePreferenceAccount(value);
137
+ return selector ? { selector } : undefined;
138
+ }
139
+ const entry = asRecord(value);
140
+ const fields = entry ? Object.entries(entry) : [];
141
+ if (fields.length !== 1) return undefined;
142
+ const [account, cap] = fields[0]!;
143
+ const selector = parsePreferenceAccount(account);
144
+ if (!selector || !isValidCredentialPreferenceCap(cap)) return undefined;
145
+ return cap === 100 ? { selector } : { selector, maxUsedFraction: cap / 100 };
146
+ }
147
+
148
+ function preferenceSelectorKey(selector: AuthCredentialSelector): string {
149
+ return `${selector.kind}:${selector.kind === "email" ? selector.value.toLowerCase() : selector.value}`;
150
+ }
151
+
152
+ /**
153
+ * Parse one provider's `auth.credentialPreferences` value: a single account, or a non-empty ordered list whose
154
+ * entries are an account (`me@example.com`) or an account with a usage cap in percent (`me@example.com: 75`).
155
+ * Returns undefined for any invalid value, including an account listed twice, so a typo fails closed instead of
156
+ * silently changing the order.
157
+ */
158
+ export function parseCredentialPreferenceList(value: unknown): ConfiguredCredentialPreference[] | undefined {
159
+ const rawEntries = Array.isArray(value) ? value : [value];
160
+ if (rawEntries.length === 0) return undefined;
161
+ const entries: ConfiguredCredentialPreference[] = [];
162
+ const seen = new Set<string>();
163
+ for (const rawEntry of rawEntries) {
164
+ const entry = parseCredentialPreferenceEntry(rawEntry);
165
+ if (!entry) return undefined;
166
+ const key = preferenceSelectorKey(entry.selector);
167
+ if (seen.has(key)) return undefined;
168
+ seen.add(key);
169
+ entries.push(entry);
170
+ }
171
+ return entries;
172
+ }
173
+
103
174
  function asRecord(value: unknown): Record<string, unknown> | undefined {
104
175
  return value !== null && typeof value === "object" && !Array.isArray(value)
105
176
  ? (value as Record<string, unknown>)
@@ -145,6 +216,10 @@ function throwLegacyLiteralKeyError(keys: string[]): never {
145
216
  " credentialRankingMode: balanced\n" +
146
217
  " credentialPins:\n" +
147
218
  " <provider>: id:<positive-id>\n" +
219
+ " credentialPreferences:\n" +
220
+ " <provider>:\n" +
221
+ " - <email>\n" +
222
+ " - <email>: <max used percent, above 0 and at most 100>\n" +
148
223
  "Do not copy secret values into command output; rewrite the file by hand.",
149
224
  );
150
225
  }
@@ -172,23 +247,53 @@ function readCredentialPins(
172
247
  return result;
173
248
  }
174
249
 
250
+ function readCredentialPreferences(
251
+ auth: Record<string, unknown> | undefined,
252
+ configPath: string,
253
+ ): Readonly<Record<string, readonly ConfiguredCredentialPreference[]>> {
254
+ const rawPreferences = auth?.credentialPreferences;
255
+ if (rawPreferences === undefined) return {};
256
+ const preferences = asRecord(rawPreferences);
257
+ if (!preferences) throw new StartupAuthConfigError("invalid-credential-preferences", configPath);
258
+
259
+ const result: Record<string, readonly ConfiguredCredentialPreference[]> = {};
260
+ for (const [provider, rawList] of Object.entries(preferences)) {
261
+ const normalizedProvider = provider.trim();
262
+ if (normalizedProvider.length === 0 || /[\u0000-\u001f\u007f]/u.test(normalizedProvider)) {
263
+ throw new StartupAuthConfigError("invalid-credential-preferences", configPath);
264
+ }
265
+ const entries = parseCredentialPreferenceList(rawList);
266
+ if (!entries) throw new StartupAuthConfigError("invalid-credential-preferences", configPath);
267
+ result[normalizedProvider] = entries;
268
+ }
269
+ return result;
270
+ }
271
+
175
272
  interface GlobalStartupAuthYaml {
176
273
  auth: Record<string, unknown> | undefined;
177
274
  credentialPinStoreIdentity: string | undefined;
178
275
  credentialPins: Readonly<Record<string, string>>;
276
+ credentialPreferences: Readonly<Record<string, readonly ConfiguredCredentialPreference[]>>;
179
277
  }
180
278
 
279
+ const EMPTY_GLOBAL_STARTUP_AUTH: GlobalStartupAuthYaml = {
280
+ auth: undefined,
281
+ credentialPinStoreIdentity: undefined,
282
+ credentialPins: {},
283
+ credentialPreferences: {},
284
+ };
285
+
181
286
  async function readGlobalStartupAuthYaml(agentDir: string): Promise<GlobalStartupAuthYaml> {
182
287
  const configPath = configPathForAgentDir(agentDir);
183
288
  let raw: string;
184
289
  try {
185
290
  raw = await Bun.file(configPath).text();
186
291
  } catch (error) {
187
- if (isEnoent(error)) return { auth: undefined, credentialPinStoreIdentity: undefined, credentialPins: {} };
292
+ if (isEnoent(error)) return EMPTY_GLOBAL_STARTUP_AUTH;
188
293
  logger.warn("startup auth config.yml unreadable", { path: shortenConfigPath(configPath), cause: "unreadable" });
189
294
  throw startupAuthConfigError(agentDir, "unreadable");
190
295
  }
191
- if (raw.trim() === "") return { auth: undefined, credentialPinStoreIdentity: undefined, credentialPins: {} };
296
+ if (raw.trim() === "") return EMPTY_GLOBAL_STARTUP_AUTH;
192
297
 
193
298
  let parsed: unknown;
194
299
  try {
@@ -211,7 +316,7 @@ async function readGlobalStartupAuthYaml(agentDir: string): Promise<GlobalStartu
211
316
  const legacyKeys = Object.keys(root).filter(key => LEGACY_LITERAL_AUTH_KEYS.has(key));
212
317
  if (legacyKeys.length > 0) throwLegacyLiteralKeyError(legacyKeys);
213
318
 
214
- if (root.auth === undefined) return { auth: undefined, credentialPinStoreIdentity: undefined, credentialPins: {} };
319
+ if (root.auth === undefined) return EMPTY_GLOBAL_STARTUP_AUTH;
215
320
  const auth = asRecord(root.auth);
216
321
  if (!auth) throw startupAuthConfigError(agentDir, "invalid-auth");
217
322
 
@@ -234,12 +339,13 @@ async function readGlobalStartupAuthYaml(agentDir: string): Promise<GlobalStartu
234
339
  throw startupAuthConfigError(agentDir, "invalid-ranking-mode");
235
340
  }
236
341
  const credentialPins = readCredentialPins(auth, configPath);
342
+ const credentialPreferences = readCredentialPreferences(auth, configPath);
237
343
  const rawCredentialPinStoreIdentity = auth.credentialPinStoreIdentity;
238
344
  const credentialPinStoreIdentity =
239
345
  typeof rawCredentialPinStoreIdentity === "string" && rawCredentialPinStoreIdentity.trim().length > 0
240
346
  ? rawCredentialPinStoreIdentity.trim()
241
347
  : undefined;
242
- return { auth, credentialPinStoreIdentity, credentialPins };
348
+ return { auth, credentialPinStoreIdentity, credentialPins, credentialPreferences };
243
349
  }
244
350
 
245
351
  async function readTokenFile(): Promise<string | undefined> {
@@ -253,7 +359,8 @@ async function readTokenFile(): Promise<string | undefined> {
253
359
  * cannot influence credential selection.
254
360
  */
255
361
  export async function resolveStartupAuthConfig(agentDir: string = getAgentDir()): Promise<StartupAuthConfigSnapshot> {
256
- const { auth, credentialPinStoreIdentity, credentialPins } = await readGlobalStartupAuthYaml(agentDir);
362
+ const { auth, credentialPinStoreIdentity, credentialPins, credentialPreferences } =
363
+ await readGlobalStartupAuthYaml(agentDir);
257
364
  const broker = asRecord(auth?.broker);
258
365
  const envUrl = $credentialEnv("GJC_AUTH_BROKER_URL")?.trim();
259
366
  const envToken = $credentialEnv("GJC_AUTH_BROKER_TOKEN")?.trim();
@@ -293,5 +400,6 @@ export async function resolveStartupAuthConfig(agentDir: string = getAgentDir())
293
400
  ...(credentialPinStoreIdentity ? { credentialPinStoreIdentity } : {}),
294
401
  credentialRankingMode: rankingMode,
295
402
  credentialPins,
403
+ credentialPreferences,
296
404
  };
297
405
  }
@@ -1,3 +1,4 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
1
2
  import type { AgentToolUpdateCallback } from "@gajae-code/agent-core";
2
3
 
3
4
  import { sanitizeText } from "@gajae-code/utils";
@@ -28,6 +29,78 @@ const NL = "\n";
28
29
 
29
30
  const ELLIPSIS = "…";
30
31
 
32
+ // Bound the temporary parts/join allocation in #applyColumnCap for a single
33
+ // large callback. Keep batches small enough to avoid per-line retention work.
34
+ const COLUMN_CAP_BATCH_CHARS = 64 * 1024;
35
+
36
+ const outputPlainSecrets = new AsyncLocalStorage<readonly string[]>();
37
+
38
+ /** Run `fn` while column caps can see these plain secrets. */
39
+ export function runWithOutputRedaction<T>(plainSecrets: readonly string[], fn: () => T): T {
40
+ return outputPlainSecrets.run(plainSecrets, fn);
41
+ }
42
+
43
+ /** Get the active plain secrets from AsyncLocalStorage, if any are set. */
44
+ function activePlainSecrets(): readonly string[] | undefined {
45
+ const secrets = outputPlainSecrets.getStore();
46
+ return secrets && secrets.length > 0 ? secrets : undefined;
47
+ }
48
+
49
+ /**
50
+ * Pull a column-cap end back to the start of any plain secret that crosses it.
51
+ * A secret that ends on the cut stays, so a later whole-string obfuscator can match it.
52
+ */
53
+ function columnHeadEndOutsideSecrets(segment: string, end: number, secrets: readonly string[]): number {
54
+ let cut = end;
55
+ while (cut > 0) {
56
+ let earliest = cut;
57
+ for (const secret of secrets) {
58
+ const secretChars = secret.length;
59
+ if (secretChars === 0) continue;
60
+ const windowStart = Math.max(0, cut - secretChars);
61
+ const window = segment.slice(windowStart, Math.min(segment.length, cut + secretChars));
62
+ const limit = cut - windowStart;
63
+ let from = 0;
64
+ while (from < limit) {
65
+ const rel = window.indexOf(secret, from);
66
+ if (rel === -1 || rel >= limit) break;
67
+ if (rel + secretChars > limit) earliest = Math.min(earliest, windowStart + rel);
68
+ from = rel + 1;
69
+ }
70
+ }
71
+ if (earliest === cut) break;
72
+ cut = earliest;
73
+ }
74
+ return cut;
75
+ }
76
+
77
+ /**
78
+ * Pull a column-cap end off a proper prefix of a plain secret.
79
+ * Each candidate's continuation is checked from longest to shortest.
80
+ * A longer candidate that is already finished or contradicted does not hide
81
+ * a shorter open prefix. A secret that already ends on the cut stays.
82
+ */
83
+ function columnHeadEndBeforeOpenPrefix(text: string, end: number, secrets: readonly string[]): number {
84
+ let cut = end;
85
+ const after = text.slice(end);
86
+ for (const secret of secrets) {
87
+ const secretChars = secret.length;
88
+ if (secretChars < 2 || end < 1) continue;
89
+ if (end >= secretChars && text.slice(end - secretChars, end) === secret) continue;
90
+ const maxPrefix = Math.min(secretChars - 1, end);
91
+ for (let k = maxPrefix; k >= 1; k--) {
92
+ if (text.slice(end - k, end) !== secret.slice(0, k)) continue;
93
+ const rest = secret.slice(k);
94
+ const have = Math.min(after.length, rest.length);
95
+ if (have === rest.length) continue;
96
+ if (after.slice(0, have) !== rest.slice(0, have)) continue;
97
+ cut = Math.min(cut, end - k);
98
+ break;
99
+ }
100
+ }
101
+ return cut;
102
+ }
103
+
31
104
  const ARTIFACT_WRITER_DIAGNOSTIC_MAX_BYTES = 256;
32
105
 
33
106
  /**
@@ -314,6 +387,72 @@ export function truncateLine(
314
387
  return { text: `${line.slice(0, maxChars)}…`, wasTruncated: true };
315
388
  }
316
389
 
390
+ /**
391
+ * Truncate each line to max characters without leaving a configured plain-secret
392
+ * prefix in the retained text. Prior uncapped lines remain in scope because a
393
+ * later line's cut can split a multiline secret.
394
+ */
395
+ export function truncateTextWithSecretAwareness(
396
+ text: string,
397
+ maxChars: number = DEFAULT_MAX_COLUMN,
398
+ secrets?: readonly string[],
399
+ ): { text: string; wasTruncated: boolean } {
400
+ const lines = text.split(NL);
401
+ const activeSecrets = secrets ?? activePlainSecrets();
402
+ if (!activeSecrets?.some(secret => secret.length > 0)) {
403
+ let wasTruncated = false;
404
+ const truncated = lines.map(line => {
405
+ const result = truncateLine(line, maxChars);
406
+ wasTruncated ||= result.wasTruncated;
407
+ return result.text;
408
+ });
409
+ return { text: truncated.join(NL), wasTruncated };
410
+ }
411
+
412
+ const contextChars = activeSecrets.reduce((max, secret) => Math.max(max, secret.length), 0);
413
+ let context = "";
414
+ const outputParts: string[] = [];
415
+ let wasTruncated = false;
416
+ for (let index = 0; index < lines.length; index++) {
417
+ const line = lines[index] ?? "";
418
+ const newline = index < lines.length - 1 ? NL : "";
419
+ if (line.length <= maxChars) {
420
+ outputParts.push(line, newline);
421
+ context = (context + line + newline).slice(-contextChars);
422
+ continue;
423
+ }
424
+
425
+ wasTruncated = true;
426
+ const combined = context + line;
427
+ let end = context.length + maxChars;
428
+ while (end > 0) {
429
+ const next = Math.min(
430
+ columnHeadEndOutsideSecrets(combined, end, activeSecrets),
431
+ columnHeadEndBeforeOpenPrefix(combined, end, activeSecrets),
432
+ );
433
+ if (next >= end) break;
434
+ end = next;
435
+ }
436
+
437
+ if (end < context.length) {
438
+ const retract = context.slice(end);
439
+ let output = outputParts.join("");
440
+ if (output.endsWith(retract)) {
441
+ output = output.slice(0, output.length - retract.length);
442
+ output += NL.repeat(countNewlines(retract));
443
+ } else {
444
+ output = NL.repeat(countNewlines(output));
445
+ }
446
+ outputParts.length = 0;
447
+ outputParts.push(output);
448
+ }
449
+ const keptChars = Math.max(0, end - context.length);
450
+ outputParts.push(line.slice(0, keptChars), ELLIPSIS, newline);
451
+ context = "";
452
+ }
453
+ return { text: outputParts.join(""), wasTruncated };
454
+ }
455
+
317
456
  // =============================================================================
318
457
  // Content truncation (line + byte aware, no full Buffer allocation)
319
458
  // =============================================================================
@@ -952,6 +1091,13 @@ export class OutputSink {
952
1091
  #columnEllipsisAdded = false;
953
1092
  #columnDroppedBytes = 0;
954
1093
  #columnTruncatedLines = 0;
1094
+ /** Plain secrets captured when this sink is built inside an active redaction scope. */
1095
+ readonly #plainSecrets?: readonly string[];
1096
+ readonly #plainSecretContextChars: number;
1097
+ /** Text already retained for the open line. A later chunk may shorten it. */
1098
+ #columnLine = "";
1099
+ /** Bounded suffix of prior uncapped output, preserved for multiline secrets. */
1100
+ #columnLineContext = "";
955
1101
  #file?: {
956
1102
  path: string;
957
1103
  artifactId?: string;
@@ -1010,6 +1156,8 @@ export class OutputSink {
1010
1156
  this.#artifactMaxBytes = Math.max(0, artifactMaxBytes);
1011
1157
  this.#artifactPublisher = artifactPublisher;
1012
1158
  this.#coalesceSanitize = coalesceSanitize;
1159
+ this.#plainSecrets = activePlainSecrets();
1160
+ this.#plainSecretContextChars = this.#plainSecrets?.reduce((max, secret) => Math.max(max, secret.length), 0) ?? 0;
1013
1161
  }
1014
1162
 
1015
1163
  #headText(): string {
@@ -1104,10 +1252,45 @@ export class OutputSink {
1104
1252
 
1105
1253
  if (rawBytes === 0) return;
1106
1254
 
1107
- const visibleChunk = this.#maxColumns > 0 ? this.#applyColumnCap(sanitizedChunk) : sanitizedChunk;
1108
1255
  if (this.#artifactPath && this.#maxColumns > 0) this.#enqueueFileWrite(rawChunk, rawBytes);
1109
1256
  else if (this.#artifactPublisher && this.#maxColumns > 0) this.#enqueueTerminalArtifactChunk(rawChunk, rawBytes);
1257
+
1258
+ if (this.#maxColumns > 0) {
1259
+ if (this.#plainSecrets) {
1260
+ // Secret-aware cuts can retract text from an earlier line, so retain
1261
+ // each line before processing the next one.
1262
+ let cursor = 0;
1263
+ while (cursor < sanitizedChunk.length) {
1264
+ const newline = sanitizedChunk.indexOf(NL, cursor);
1265
+ const end = newline === -1 ? sanitizedChunk.length : newline + 1;
1266
+ this.#retainVisible(this.#applyColumnCap(sanitizedChunk.slice(cursor, end)));
1267
+ cursor = end;
1268
+ }
1269
+ } else {
1270
+ // Batch ordinary output while bounding #applyColumnCap's temporary
1271
+ // parts/join allocation for large single callbacks. Retaining each
1272
+ // line separately repeats accounting millions of times.
1273
+ let cursor = 0;
1274
+ while (cursor < sanitizedChunk.length) {
1275
+ let end = Math.min(cursor + COLUMN_CAP_BATCH_CHARS, sanitizedChunk.length);
1276
+ // UTF-8 byte accounting must not observe a surrogate pair as two
1277
+ // replacement characters when a batch boundary lands between them.
1278
+ if (end < sanitizedChunk.length) {
1279
+ const before = sanitizedChunk.charCodeAt(end - 1);
1280
+ const after = sanitizedChunk.charCodeAt(end);
1281
+ if (before >= 0xd800 && before <= 0xdbff && after >= 0xdc00 && after <= 0xdfff) end--;
1282
+ }
1283
+ this.#retainVisible(this.#applyColumnCap(sanitizedChunk.slice(cursor, end)));
1284
+ cursor = end;
1285
+ }
1286
+ }
1287
+ } else {
1288
+ this.#retainVisible(sanitizedChunk);
1289
+ }
1110
1290
  if (this.#columnDroppedBytes > 0) this.#createFileSink();
1291
+ }
1292
+
1293
+ #retainVisible(visibleChunk: string): void {
1111
1294
  const visibleBytes = Buffer.byteLength(visibleChunk, "utf-8");
1112
1295
  if (visibleChunk.length > 0) {
1113
1296
  this.#processedBytes += visibleBytes;
@@ -1125,6 +1308,7 @@ export class OutputSink {
1125
1308
  return;
1126
1309
  }
1127
1310
  const headSlice = truncateHeadBytes(visibleChunk, room);
1311
+ this.#headRetentionDisabled = true;
1128
1312
  if (headSlice.bytes > 0) {
1129
1313
  this.#appendHead(headSlice.text, headSlice.bytes);
1130
1314
  tailChunk = visibleChunk.substring(headSlice.text.length);
@@ -1161,34 +1345,106 @@ export class OutputSink {
1161
1345
  if (segBytes <= remaining) {
1162
1346
  parts.push(segment);
1163
1347
  this.#currentLineBytes += segBytes;
1348
+ if (this.#plainSecrets) this.#columnLine += segment;
1164
1349
  } else {
1165
1350
  // First overflow on this line: keep what fits, append ellipsis,
1166
- // arm the skip-until-newline flag.
1351
+ // arm the skip-until-newline flag. With secrets, do not emit a
1352
+ // kept-head suffix that is still a prefix of a plain secret, and
1353
+ // do not end inside one the whole-string obfuscator must see whole.
1167
1354
  const ellipsisBytes = 3; // "…" in UTF-8
1168
1355
  const headRoom = Math.max(0, remaining - ellipsisBytes);
1169
1356
  let keptBytes = 0;
1357
+ let keptText = "";
1170
1358
  if (headRoom > 0) {
1171
1359
  const sliced = truncateHeadBytes(segment, headRoom);
1360
+ keptText = sliced.text;
1172
1361
  keptBytes = sliced.bytes;
1173
- parts.push(sliced.text);
1174
1362
  }
1363
+ const secrets = this.#plainSecrets;
1364
+ let lineBytesReset = false;
1365
+ if (secrets && segment.startsWith(keptText)) {
1366
+ const prior = this.#columnLine;
1367
+ const prefix = this.#columnLineContext + prior;
1368
+ const text = prefix + segment;
1369
+ const cut = prefix.length + keptText.length;
1370
+ let end = cut;
1371
+ while (end > 0) {
1372
+ const next = Math.min(
1373
+ columnHeadEndOutsideSecrets(text, end, secrets),
1374
+ columnHeadEndBeforeOpenPrefix(text, end, secrets),
1375
+ );
1376
+ if (next >= end) break;
1377
+ end = next;
1378
+ }
1379
+ if (end < cut) {
1380
+ if (end < prefix.length) {
1381
+ this.#retractColumnSuffix(prefix.slice(end));
1382
+ if (end < this.#columnLineContext.length) {
1383
+ this.#columnLineContext = this.#columnLineContext.slice(0, end);
1384
+ this.#columnLine = "";
1385
+ } else {
1386
+ this.#columnLine = prior.slice(0, end - this.#columnLineContext.length);
1387
+ }
1388
+ keptText = "";
1389
+ keptBytes = 0;
1390
+ } else {
1391
+ keptText = segment.slice(0, end - prefix.length);
1392
+ keptBytes = Buffer.byteLength(keptText, "utf-8");
1393
+ this.#columnLine = prior + keptText;
1394
+ }
1395
+ // columnLine already includes the bytes kept from this segment.
1396
+ this.#currentLineBytes = Buffer.byteLength(this.#columnLine, "utf-8");
1397
+ lineBytesReset = true;
1398
+ } else {
1399
+ this.#columnLine = prior + keptText;
1400
+ }
1401
+ }
1402
+ if (keptText.length > 0) parts.push(keptText);
1175
1403
  parts.push(ELLIPSIS);
1176
1404
  this.#columnDroppedBytes += segBytes - keptBytes;
1177
1405
  this.#columnTruncatedLines++;
1178
- this.#currentLineBytes += keptBytes + ellipsisBytes;
1406
+ this.#currentLineBytes += (lineBytesReset ? 0 : keptBytes) + ellipsisBytes;
1179
1407
  this.#columnEllipsisAdded = true;
1180
1408
  }
1181
1409
  }
1182
1410
  }
1183
1411
  if (nlIdx === -1) break;
1184
1412
  parts.push(NL);
1413
+ if (this.#plainSecretContextChars > 0 && !this.#columnEllipsisAdded) {
1414
+ const context = this.#columnLineContext + this.#columnLine + NL;
1415
+ this.#columnLineContext = context.slice(-this.#plainSecretContextChars);
1416
+ } else {
1417
+ this.#columnLineContext = "";
1418
+ }
1185
1419
  this.#currentLineBytes = 0;
1186
1420
  this.#columnEllipsisAdded = false;
1421
+ this.#columnLine = "";
1187
1422
  cursor = nlIdx + 1;
1188
1423
  }
1189
1424
  return parts.join("");
1190
1425
  }
1191
1426
 
1427
+ /** Remove a suffix of previously retained output after a secret-aware cut-back. */
1428
+ #retractColumnSuffix(suffix: string): void {
1429
+ if (suffix.length === 0) return;
1430
+ const bytes = Buffer.byteLength(suffix, "utf-8");
1431
+ const startByte = Math.max(0, this.#processedBytes - bytes);
1432
+ const tailStartByte = Math.max(0, this.#processedBytes - this.#bufferBytes);
1433
+ const keptTailBytes = Math.max(0, Math.min(this.#bufferBytes, startByte - tailStartByte));
1434
+ if (keptTailBytes < this.#bufferBytes) {
1435
+ const nextTail = truncateHeadBytes(this.#buffer, keptTailBytes);
1436
+ this.#setTail(nextTail.text, nextTail.bytes);
1437
+ }
1438
+ if (startByte < this.#headBytes) {
1439
+ const nextHead = truncateHeadBytes(this.#head, startByte);
1440
+ this.#head = nextHead.text;
1441
+ this.#headBytes = nextHead.bytes;
1442
+ }
1443
+ this.#processedBytes = Math.max(0, this.#processedBytes - bytes);
1444
+ this.#processedLines = Math.max(0, this.#processedLines - countNewlines(suffix));
1445
+ this.#columnDroppedBytes += bytes;
1446
+ }
1447
+
1192
1448
  #retainedSummary(text: string): { text: string; bytes: number; lines: number } {
1193
1449
  return { text, bytes: Buffer.byteLength(text, "utf-8"), lines: text.length > 0 ? countNewlines(text) : 0 };
1194
1450
  }
@@ -1458,6 +1714,8 @@ export class OutputSink {
1458
1714
  */
1459
1715
  replace(text: string): void {
1460
1716
  this.#coalesceBuf = "";
1717
+ this.#columnLine = "";
1718
+ this.#columnLineContext = "";
1461
1719
  const replacementBytes = Buffer.byteLength(text, "utf-8");
1462
1720
  this.#head = "";
1463
1721
  this.#headBytes = 0;
@@ -382,6 +382,16 @@ export function isOwnedAttemptRegistrationIncomplete(lineageIdHash: string, atte
382
382
  return incompleteOwnedAttempts.has(`${lineageIdHash}\u0000${attemptEpoch}`);
383
383
  }
384
384
 
385
+ /** Move fail-closed saturation authority with all ownership state during endpoint rekey. */
386
+ export function migrateOwnedAttemptSaturationsForEndpoint(
387
+ predecessorEndpointId: string,
388
+ successorEndpointId: string,
389
+ ): void {
390
+ for (const [attemptKey, endpointId] of saturatedAttemptEndpoints) {
391
+ if (endpointId === predecessorEndpointId) saturatedAttemptEndpoints.set(attemptKey, successorEndpointId);
392
+ }
393
+ }
394
+
385
395
  /** Exact (jobId, jobGeneration) lookup for completion-origin classification. */
386
396
  export function lookupOwnedRegistration(
387
397
  jobId: string,
@@ -841,6 +851,7 @@ export function registerOwnedIfLineaged(
841
851
  toolCallId: string | undefined,
842
852
  jobId: string,
843
853
  endpointId?: string,
854
+ ownedRegistrationEndpointId?: string,
844
855
  ): void {
845
856
  try {
846
857
  let lineage = resolveToolLineage(toolCallId, endpointId);
@@ -860,9 +871,10 @@ export function registerOwnedIfLineaged(
860
871
  const job = manager.getJob?.(jobId);
861
872
  const jobGeneration = job?.generation;
862
873
  if (!jobGeneration) return;
874
+ const registrationEndpointId = ownedRegistrationEndpointId ?? lineage.endpointId;
863
875
  registerOwnedRegistration(
864
876
  {
865
- ...(lineage.endpointId ? { endpointId: lineage.endpointId } : {}),
877
+ ...(registrationEndpointId ? { endpointId: registrationEndpointId } : {}),
866
878
  endpointGeneration: lineage.endpointGeneration,
867
879
  lineageIdHash: lineage.lineageIdHash,
868
880
  promptAttemptEpoch: lineage.promptAttemptEpoch,
@@ -877,7 +889,7 @@ export function registerOwnedIfLineaged(
877
889
  // job id bg_1) must not be judged terminal using THIS manager's
878
890
  // unrelated bg_1 record (review thread P2).
879
891
  isJobTerminal: candidate => {
880
- if ((candidate.endpointId ?? "") !== (lineage.endpointId ?? "")) return undefined;
892
+ if ((candidate.endpointId ?? "") !== (registrationEndpointId ?? "")) return undefined;
881
893
  const status = manager.getJob?.(candidate.jobId)?.status;
882
894
  return status === "completed" || status === "cancelled" || status === "failed" || status === "evicted";
883
895
  },