@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
@@ -1,6 +1,5 @@
1
1
  import { Database, type Statement } from "bun:sqlite";
2
2
  import * as fs from "node:fs";
3
- import * as fsPromises from "node:fs/promises";
4
3
  import * as path from "node:path";
5
4
  import { getHistoryDbPath, logger } from "@gajae-code/utils";
6
5
 
@@ -26,6 +25,155 @@ function escapeLikePattern(text: string): string {
26
25
  return text.replace(/[\\%_]/g, "\\$&");
27
26
  }
28
27
 
28
+ const HISTORY_SUFFIXES = ["", "-wal", "-shm"] as const;
29
+ let openingHistory: Database | undefined;
30
+
31
+ function secureHistoryPath(pathname: string, kind: "directory" | "file"): void {
32
+ if (process.platform !== "win32") return;
33
+ const native = require("@gajae-code/natives") as typeof import("@gajae-code/natives");
34
+ const applied = native.applyOwnerOnlyPathSecurity(pathname, kind);
35
+ const verified = native.verifyOwnerOnlyPathSecurity(pathname, kind);
36
+ if (applied?.ok !== true || verified?.ok !== true) {
37
+ throw new Error("history database owner-only protection could not be established");
38
+ }
39
+ }
40
+
41
+ function historyErrno(error: unknown): string | undefined {
42
+ return (error as NodeJS.ErrnoException | undefined)?.code;
43
+ }
44
+
45
+ function historyLeaf(dbPath: string, suffix: string): fs.BigIntStats | undefined {
46
+ try {
47
+ return fs.lstatSync(`${dbPath}${suffix}`, { bigint: true });
48
+ } catch (error) {
49
+ if (historyErrno(error) !== "ENOENT") throw error;
50
+ return undefined;
51
+ }
52
+ }
53
+
54
+ function assertRegularHistoryLeaf(existing: fs.BigIntStats | undefined): void {
55
+ if (existing && (existing.isSymbolicLink() || !existing.isFile())) {
56
+ throw new Error("history database path is not a regular file");
57
+ }
58
+ }
59
+
60
+ function assertHistoryLeaves(dbPath: string): Set<string> {
61
+ const present = new Set<string>();
62
+ for (const suffix of HISTORY_SUFFIXES) {
63
+ const existing = historyLeaf(dbPath, suffix);
64
+ assertRegularHistoryLeaf(existing);
65
+ if (existing) present.add(suffix);
66
+ }
67
+ return present;
68
+ }
69
+
70
+ function removeNewHistoryLeaves(): void {
71
+ // A WAL reader can stay live while BEGIN EXCLUSIVE succeeds, so failure
72
+ // cleanup must not unlink a database another opener has adopted.
73
+ try {
74
+ openingHistory?.close();
75
+ } catch {
76
+ // The caller reports the original open failure.
77
+ }
78
+ openingHistory = undefined;
79
+ }
80
+
81
+ function readHistoryDirectory(dir: string): fs.Stats | undefined {
82
+ let existing: fs.Stats;
83
+ try {
84
+ existing = fs.lstatSync(dir);
85
+ } catch (error) {
86
+ if (historyErrno(error) !== "ENOENT") throw error;
87
+ return undefined;
88
+ }
89
+ if (existing.isSymbolicLink() || !existing.isDirectory()) {
90
+ throw new Error("history database directory is not a real directory");
91
+ }
92
+ return existing;
93
+ }
94
+
95
+ function tightenHistoryDirectory(dir: string): void {
96
+ let existing = readHistoryDirectory(dir);
97
+ if (!existing) {
98
+ try {
99
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
100
+ } catch (error) {
101
+ if (historyErrno(error) !== "EEXIST") throw error;
102
+ }
103
+ existing = readHistoryDirectory(dir);
104
+ if (!existing) throw new Error("history database directory is not a real directory");
105
+ }
106
+ secureHistoryPath(dir, "directory");
107
+ if (process.platform === "win32") return;
108
+ // 0300/0700 are already owner-only. Sticky directories are shared and stay as they are.
109
+ // chmod(2) needs no read bit, so mode 0333 becomes 0700. A symlink swap is
110
+ // followed by chmod; the recheck then refuses and no database file is created.
111
+ if ((existing.mode & 0o077) === 0 || (existing.mode & 0o1000) !== 0) return;
112
+ try {
113
+ fs.chmodSync(dir, 0o700);
114
+ } catch (error) {
115
+ if (historyErrno(error) === "ELOOP") throw new Error("history database directory is not a real directory");
116
+ throw error;
117
+ }
118
+ const after = readHistoryDirectory(dir);
119
+ if (!after || (after.mode & 0o077) !== 0) {
120
+ throw new Error("history database directory could not be tightened to mode 0700");
121
+ }
122
+ }
123
+
124
+ function sameHistoryFile(left: fs.BigIntStats, right: fs.BigIntStats): boolean {
125
+ return left.dev === right.dev && left.ino === right.ino && left.nlink === right.nlink;
126
+ }
127
+
128
+ function restrictHistoryFiles(dbPath: string, createMissingDb: boolean): void {
129
+ const noFollow = process.platform === "win32" ? 0 : (fs.constants.O_NOFOLLOW ?? 0);
130
+ for (const suffix of HISTORY_SUFFIXES) {
131
+ const target = `${dbPath}${suffix}`;
132
+ let existing = historyLeaf(dbPath, suffix);
133
+ assertRegularHistoryLeaf(existing);
134
+ if (!existing && suffix === "" && createMissingDb) {
135
+ let created: number | undefined;
136
+ try {
137
+ created = fs.openSync(
138
+ target,
139
+ fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | noFollow,
140
+ 0o600,
141
+ );
142
+ fs.fchmodSync(created, 0o600);
143
+ } catch (error) {
144
+ if (historyErrno(error) === "ELOOP") throw new Error("history database path is not a regular file");
145
+ if (historyErrno(error) !== "EEXIST") throw error;
146
+ } finally {
147
+ if (created !== undefined) fs.closeSync(created);
148
+ }
149
+ existing = historyLeaf(dbPath, suffix);
150
+ assertRegularHistoryLeaf(existing);
151
+ if (!existing) throw new Error("history database path is not a regular file");
152
+ }
153
+ if (!existing) continue;
154
+ secureHistoryPath(target, "file");
155
+ if (process.platform === "win32" || (Number(existing.mode) & 0o077) === 0) continue;
156
+ let fd: number | undefined;
157
+ try {
158
+ fd = fs.openSync(target, fs.constants.O_RDONLY | noFollow);
159
+ const opened = fs.fstatSync(fd, { bigint: true });
160
+ const again = historyLeaf(dbPath, suffix);
161
+ if (!again || !opened.isFile() || !sameHistoryFile(existing, opened) || !sameHistoryFile(opened, again)) {
162
+ throw new Error("history database path is not a regular file");
163
+ }
164
+ fs.fchmodSync(fd, 0o600);
165
+ if ((Number(fs.fstatSync(fd, { bigint: true }).mode) & 0o777) !== 0o600) {
166
+ throw new Error("history database path could not be tightened to mode 0600");
167
+ }
168
+ } catch (error) {
169
+ if (historyErrno(error) === "ELOOP") throw new Error("history database path is not a regular file");
170
+ throw error;
171
+ } finally {
172
+ if (fd !== undefined) fs.closeSync(fd);
173
+ }
174
+ }
175
+ }
176
+
29
177
  class AsyncDrain<T> {
30
178
  #queue?: T[];
31
179
  #promise = Promise.resolve();
@@ -83,6 +231,7 @@ export class HistoryStorage {
83
231
 
84
232
  private constructor(dbPath: string) {
85
233
  this.#db = new Database(dbPath);
234
+ openingHistory = this.#db;
86
235
 
87
236
  const hasFts = this.#db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='history_fts'").get();
88
237
 
@@ -137,12 +286,32 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
137
286
 
138
287
  const last = this.#lastPromptStmt.get() as { prompt?: string } | undefined;
139
288
  this.#lastPromptCache = last?.prompt ?? null;
289
+ try {
290
+ restrictHistoryFiles(dbPath, false);
291
+ } catch (error) {
292
+ this.#db.close();
293
+ throw error;
294
+ }
295
+ openingHistory = undefined;
296
+ }
297
+
298
+ static #openNew(dbPath: string): HistoryStorage {
299
+ openingHistory = undefined;
300
+ assertHistoryLeaves(dbPath);
301
+ try {
302
+ tightenHistoryDirectory(path.dirname(dbPath));
303
+ assertHistoryLeaves(dbPath);
304
+ restrictHistoryFiles(dbPath, true);
305
+ return new HistoryStorage(dbPath);
306
+ } catch (error) {
307
+ removeNewHistoryLeaves();
308
+ throw error;
309
+ }
140
310
  }
141
311
 
142
312
  static open(dbPath: string = getHistoryDbPath()): HistoryStorage {
143
313
  if (!HistoryStorage.#instance) {
144
- fs.mkdirSync(path.dirname(dbPath), { recursive: true });
145
- HistoryStorage.#instance = new HistoryStorage(dbPath);
314
+ HistoryStorage.#instance = HistoryStorage.#openNew(dbPath);
146
315
  }
147
316
  return HistoryStorage.#instance;
148
317
  }
@@ -150,16 +319,17 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
150
319
  static async openAsync(dbPath: string = getHistoryDbPath()): Promise<HistoryStorage> {
151
320
  if (HistoryStorage.#instance) return HistoryStorage.#instance;
152
321
  if (!HistoryStorage.#opening) {
153
- HistoryStorage.#opening = (async () => {
154
- await fsPromises.mkdir(path.dirname(dbPath), { recursive: true });
155
- const storage = new HistoryStorage(dbPath);
156
- HistoryStorage.#instance ??= storage;
157
- return HistoryStorage.#instance;
158
- })().finally(() => {
159
- HistoryStorage.#opening = undefined;
160
- });
322
+ HistoryStorage.#opening = Promise.resolve()
323
+ .then(() => {
324
+ const storage = HistoryStorage.#openNew(dbPath);
325
+ HistoryStorage.#instance ??= storage;
326
+ return HistoryStorage.#instance;
327
+ })
328
+ .finally(() => {
329
+ HistoryStorage.#opening = undefined;
330
+ });
161
331
  }
162
- return await HistoryStorage.#opening;
332
+ return HistoryStorage.#opening;
163
333
  }
164
334
 
165
335
  /** @internal Reset the singleton — test-only. */
@@ -228,14 +228,22 @@ function bindManagedWriteAuthority(scope: ManagedScope, authority: ManagedCandid
228
228
  function assertRetainedManagedDirectoryIdentity(scope: ManagedScope): void {
229
229
  const expected = managedDirectoryIdentities.get(scope);
230
230
  if (!expected) return;
231
- const current = fs.lstatSync(scope.directoryPath, { bigint: true });
232
- if (
233
- !current.isDirectory() ||
234
- current.isSymbolicLink() ||
235
- current.dev !== expected.dev ||
236
- current.ino !== expected.ino
237
- )
238
- throw new Error("Managed session directory changed");
231
+ assertSameManagedDirectoryIdentity(fs.lstatSync(scope.directoryPath, { bigint: true }), expected);
232
+ }
233
+
234
+ /**
235
+ * Fence a managed directory to the identity it was bound with. The decision is
236
+ * the same for every mismatch; only the reported reason differs. The same
237
+ * inode on a different `dev` is the shape a remount, drive-letter change or
238
+ * volume-serial change leaves behind (#6750). It is still refused, because an
239
+ * inode is only unique within one volume, but it is named so an operator is
240
+ * not sent looking for a corrupted binding.
241
+ */
242
+ function assertSameManagedDirectoryIdentity(current: fs.BigIntStats, expected: { dev: bigint; ino: bigint }): void {
243
+ if (!current.isDirectory() || current.isSymbolicLink()) throw new Error("Managed session directory changed");
244
+ if (current.dev === expected.dev && current.ino === expected.ino) return;
245
+ if (current.ino === expected.ino) throw new Error("volume_identity_changed");
246
+ throw new Error("Managed session directory changed");
239
247
  }
240
248
 
241
249
  export function managedDirectoryAuthorityForScope(scope: ManagedScope): RecoveryFsRoot | undefined {
@@ -299,6 +307,7 @@ export type ManagedScopeErrorCode =
299
307
  | "managed_gc_journal_capacity_exceeded"
300
308
  | "atomic_unavailable"
301
309
  | "invalid_request"
310
+ | "permission_denied"
302
311
  | "durability_failed"
303
312
  | "durability_not_provable"
304
313
  | "capacity_exceeded";
@@ -318,7 +327,22 @@ export type ManagedScopeResolution =
318
327
  * propagating them keeps startup redaction intact while telling the operator
319
328
  * which invariant actually failed instead of a blanket `binding_invalid`.
320
329
  */
321
- const managedScopeIdentityFailureCodes = new Set(["identity_mismatch", "reparse_point", "not_directory", "not_file"]);
330
+ const managedScopeIdentityFailureCodes = new Set([
331
+ "identity_mismatch",
332
+ "volume_identity_changed",
333
+ "reparse_point",
334
+ "not_directory",
335
+ "not_file",
336
+ ]);
337
+
338
+ /**
339
+ * Fixed, path-free tokens from the replacement-cleanup receipt preflight that
340
+ * runs before every no-replace publish. A stale receipt the preflight refuses
341
+ * (for example one whose filename encodes a device number that changed after a
342
+ * reboot, #6820) blocks an otherwise healthy binding publish; reporting it as
343
+ * `binding_invalid` sends operators to the binding instead of the receipt.
344
+ */
345
+ const managedScopeReceiptFailureCodes = new Set(["managed_replace_cleanup_receipt_invalid"]);
322
346
 
323
347
  /**
324
348
  * Fixed, path-free errno tokens. A managed scope that fails to prepare because
@@ -342,6 +366,7 @@ const managedScopeErrnoFailureCodes = new Set([
342
366
  const managedScopeFailureCodes = new Set<ManagedScopeErrorCode>([
343
367
  "atomic_unavailable",
344
368
  "invalid_request",
369
+ "permission_denied",
345
370
  "durability_failed",
346
371
  "durability_not_provable",
347
372
  "migration_busy",
@@ -374,6 +399,7 @@ function isManagedScopeCapacityMessage(message: string): boolean {
374
399
  function managedScopeResidualClassification(error: unknown): string | undefined {
375
400
  if (!(error instanceof Error)) return undefined;
376
401
  if (managedScopeIdentityFailureCodes.has(error.message)) return error.message;
402
+ if (managedScopeReceiptFailureCodes.has(error.message)) return error.message;
377
403
  const errno = (error as NodeJS.ErrnoException).code;
378
404
  return errno !== undefined && managedScopeErrnoFailureCodes.has(errno) ? errno : undefined;
379
405
  }
@@ -441,6 +467,7 @@ export type ManagedOpenFailure =
441
467
  | "managed_gc_journal_capacity_exceeded"
442
468
  | "durability_failed"
443
469
  | "atomic_unavailable"
470
+ | "permission_denied"
444
471
  | "invalid_request"
445
472
  | "durability_not_provable"
446
473
  | "migration_retired"
@@ -505,6 +532,7 @@ type NativeIdentity =
505
532
  type CanonicalNativeIdentity = Extract<NativeIdentity, { ok: true }>;
506
533
 
507
534
  type NativeIdentityFailureCode =
535
+ | "access_denied"
508
536
  | "not_found"
509
537
  | "not_directory"
510
538
  | "not_utf8"
@@ -615,6 +643,12 @@ function nativeFailure(code: NativeIdentityFailureCode): ManagedScopeResolution
615
643
  return { kind: "error", code: "cwd_missing", message: "The workspace directory does not exist." };
616
644
  if (code === "not_directory")
617
645
  return { kind: "error", code: "cwd_not_directory", message: "The workspace path is not a directory." };
646
+ if (code === "access_denied")
647
+ return {
648
+ kind: "error",
649
+ code: "permission_denied",
650
+ message: "Permission was denied while identifying the workspace directory.",
651
+ };
618
652
  if (code === "network_unsupported") {
619
653
  return {
620
654
  kind: "error",
@@ -625,6 +659,10 @@ function nativeFailure(code: NativeIdentityFailureCode): ManagedScopeResolution
625
659
  return { kind: "error", code: "identity_unavailable", message: "Workspace directory identity is unavailable." };
626
660
  }
627
661
 
662
+ function nativeIdentityDiagnosticCode(code: NativeIdentityFailureCode): string {
663
+ return code === "access_denied" ? "permission_denied" : `cwd_${code}`;
664
+ }
665
+
628
666
  function bindingFor(scope: ManagedScope): Binding {
629
667
  return {
630
668
  schemaVersion: 1,
@@ -1104,7 +1142,7 @@ function inspectCandidate(
1104
1142
  if (header.type !== "session" || typeof header.id !== "string" || typeof header.cwd !== "string")
1105
1143
  return { code: "invalid_header" };
1106
1144
  const cwdIdentity = identityFor(header.cwd);
1107
- if (!cwdIdentity.ok) return { code: `cwd_${cwdIdentity.code}` };
1145
+ if (!cwdIdentity.ok) return { code: nativeIdentityDiagnosticCode(cwdIdentity.code) };
1108
1146
  const named = fs.lstatSync(filePath, { bigint: true });
1109
1147
  if (
1110
1148
  !named.isFile() ||
@@ -1166,7 +1204,8 @@ function listDirectoryCandidates(
1166
1204
  try {
1167
1205
  directoryStat = fs.lstatSync(directory);
1168
1206
  } catch (error) {
1169
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
1207
+ const code = (error as NodeJS.ErrnoException).code;
1208
+ if (code === "ENOENT" || (provenance === "legacy" && code === "ENAMETOOLONG")) return [];
1170
1209
  throw error;
1171
1210
  }
1172
1211
  if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) throw new Error("unsafe directory");
@@ -1868,13 +1907,7 @@ export function prepareManagedSessionScopeForWriteSync(
1868
1907
  managedDirectoryAuthorities.set(scope, retainedAuthority);
1869
1908
  stage = "scope_revalidate";
1870
1909
  const directoryStat = fs.lstatSync(scope.directoryPath, { bigint: true });
1871
- if (
1872
- !directoryStat.isDirectory() ||
1873
- directoryStat.isSymbolicLink() ||
1874
- directoryStat.dev !== preparedDirectory.dev ||
1875
- directoryStat.ino !== preparedDirectory.ino
1876
- )
1877
- throw new Error("Managed session directory changed");
1910
+ assertSameManagedDirectoryIdentity(directoryStat, preparedDirectory);
1878
1911
  managedDirectoryIdentities.set(scope, { dev: preparedDirectory.dev, ino: preparedDirectory.ino });
1879
1912
  if (validated) {
1880
1913
  if (validated.kind !== "error") throw new Error("Unexpected managed scope binding result");
@@ -1950,7 +1983,7 @@ export function listManagedCandidates(scope: ManagedScope): ManagedCandidateList
1950
1983
  const candidateIdentity = identityFor(candidate.cwd);
1951
1984
  if (!candidateIdentity.ok) {
1952
1985
  invalid.push({
1953
- code: `cwd_${candidateIdentity.code}`,
1986
+ code: nativeIdentityDiagnosticCode(candidateIdentity.code),
1954
1987
  ...(safeManagedCandidateSessionId(candidate.sessionId) === undefined
1955
1988
  ? {}
1956
1989
  : { sessionId: candidate.sessionId }),
@@ -1970,7 +2003,8 @@ export function listManagedCandidates(scope: ManagedScope): ManagedCandidateList
1970
2003
  }
1971
2004
  }
1972
2005
  }
1973
- const visible = owned.filter(candidate => !isRetired(scope, candidate));
2006
+ const retired = owned.length > 0 ? retiredTargetKeys(scope) : new Set<string>();
2007
+ const visible = owned.filter(candidate => !isRetiredIn(retired, candidate));
1974
2008
  const active = visible.filter(
1975
2009
  candidate =>
1976
2010
  candidate.provenance === "v2" ||
@@ -2041,6 +2075,7 @@ function expectedFailure(error: unknown): ManagedOpenFailure {
2041
2075
  message === "managed_gc_journal_capacity_exceeded" ||
2042
2076
  message === "durability_failed" ||
2043
2077
  message === "atomic_unavailable" ||
2078
+ message === "permission_denied" ||
2044
2079
  message === "invalid_request" ||
2045
2080
  message === "durability_not_provable" ||
2046
2081
  message === "migration_retired"
@@ -2692,11 +2727,14 @@ function managedGcStreamDirectoryInventory(
2692
2727
  countEntry: (entry: fs.Dirent) => boolean,
2693
2728
  retain: (entry: fs.Dirent) => boolean,
2694
2729
  operation: ManagedGcInventoryOperation,
2730
+ verificationBaseline?: ManagedGcDirectoryInventory,
2695
2731
  ): ManagedGcDirectoryInventory {
2696
2732
  managedGcReserveInventoryWork(operation);
2697
2733
  const beforeStat = fs.lstatSync(pathname, { bigint: true });
2698
2734
  if (!beforeStat.isDirectory() || beforeStat.isSymbolicLink()) throw new Error("managed_gc_scope_authority_mismatch");
2699
2735
  const before = managedGcDirectoryEvidence(beforeStat);
2736
+ if (verificationBaseline && !managedGcSameDirectoryEvidence(before, verificationBaseline.evidence))
2737
+ throw new Error("managed_gc_scope_authority_mismatch");
2700
2738
  const scan = (collect: boolean): { count: number; physicalEntryCount: number; entries: fs.Dirent[] } => {
2701
2739
  managedGcReserveInventoryWork(operation);
2702
2740
  const entries: fs.Dirent[] = [];
@@ -2742,7 +2780,9 @@ function managedGcStreamDirectoryInventory(
2742
2780
  managedGcReserveInventoryWork(operation);
2743
2781
  return { count, physicalEntryCount, entries };
2744
2782
  };
2745
- const counted = scan(false);
2783
+ const counted = verificationBaseline
2784
+ ? { count: verificationBaseline.entryCount, physicalEntryCount: verificationBaseline.physicalEntryCount }
2785
+ : scan(false);
2746
2786
  managedGcReserveInventoryWork(operation);
2747
2787
  const afterCount = fs.lstatSync(pathname, { bigint: true });
2748
2788
  if (
@@ -2776,6 +2816,7 @@ function managedGcReceiptDirectoryInventory(
2776
2816
  scope: ManagedScope,
2777
2817
  store: ManagedSessionDescendantStore,
2778
2818
  operation: ManagedGcInventoryOperation,
2819
+ verificationBaseline?: ManagedGcDirectoryInventory,
2779
2820
  ): ManagedGcDirectoryInventory {
2780
2821
  const relativePath = MANAGED_GC_RETIREMENT_RECEIPTS;
2781
2822
  managedGcReserveInventoryWork(
@@ -2789,6 +2830,7 @@ function managedGcReceiptDirectoryInventory(
2789
2830
  entry => entry.name.startsWith(MANAGED_GC_RETIREMENT_PREFIX),
2790
2831
  entry => entry.name.startsWith(MANAGED_GC_RETIREMENT_PREFIX),
2791
2832
  operation,
2833
+ verificationBaseline,
2792
2834
  );
2793
2835
  managedGcReserveInventoryWork(operation);
2794
2836
  const after = store.captureDirectoryIdentity(relativePath);
@@ -3606,14 +3648,20 @@ export async function readManagedGcSessionRetirementReceiptReadOnly(
3606
3648
  if (!hasFsCode(error, "ENOENT")) throw error;
3607
3649
  receiptDirectoryPresent = false;
3608
3650
  }
3609
- if (receiptDirectoryPresent)
3651
+ if (receiptDirectoryPresent) {
3652
+ const inventory = managedGcReceiptDirectoryInventory(scope, store, operation);
3610
3653
  receipt = readManagedGcSessionRetirementReceiptUnlocked(
3611
3654
  scope,
3612
3655
  transcriptPath,
3613
3656
  trusted,
3614
3657
  store,
3615
3658
  operation,
3659
+ inventory,
3616
3660
  ).receipt;
3661
+ const after = managedGcReceiptDirectoryInventory(scope, store, operation, inventory);
3662
+ if (!managedGcSameReceiptDirectoryInventory(inventory, after))
3663
+ throw new Error("task_artifact_owner_continuation_authority_mismatch");
3664
+ }
3617
3665
  } catch (error) {
3618
3666
  primaryError = error;
3619
3667
  hasPrimaryError = true;
@@ -6282,8 +6330,96 @@ function tombstonePathContaining(scope: ManagedScope, candidate: ManagedCandidat
6282
6330
  return undefined;
6283
6331
  }
6284
6332
 
6285
- function isRetired(scope: ManagedScope, candidate: ManagedCandidate): boolean {
6333
+ /**
6334
+ * Identity fields a retired tombstone target must match exactly for a candidate
6335
+ * to be hidden. `dev`, `ino`, and `mtimeNs` are persisted as decimal strings.
6336
+ */
6337
+ type RetiredTargetIdentity = readonly [
6338
+ path: string,
6339
+ sessionId: string,
6340
+ cwd: string,
6341
+ canonicalPath: string,
6342
+ dev: string,
6343
+ ino: string,
6344
+ size: number,
6345
+ mtimeNs: string,
6346
+ sha256: string,
6347
+ ];
6348
+
6349
+ function candidateRetiredIdentity(candidate: ManagedCandidate): RetiredTargetIdentity {
6350
+ return [
6351
+ candidate.path,
6352
+ candidate.sessionId,
6353
+ candidate.cwd,
6354
+ candidate.identity.canonicalPath,
6355
+ String(candidate.identity.dev),
6356
+ String(candidate.identity.ino),
6357
+ candidate.identity.size,
6358
+ String(candidate.identity.mtimeNs),
6359
+ candidate.identity.sha256,
6360
+ ];
6361
+ }
6362
+
6363
+ /** Returns the target's identity only when every field has the type a candidate can carry. */
6364
+ function parseRetiredTargetIdentity(target: unknown): RetiredTargetIdentity | undefined {
6365
+ if (!target || typeof target !== "object") return undefined;
6366
+ const value = target as { path?: unknown; sessionId?: unknown; cwd?: unknown; identity?: unknown };
6367
+ if (!value.identity || typeof value.identity !== "object") return undefined;
6368
+ const identity = value.identity as {
6369
+ canonicalPath?: unknown;
6370
+ dev?: unknown;
6371
+ ino?: unknown;
6372
+ size?: unknown;
6373
+ mtimeNs?: unknown;
6374
+ sha256?: unknown;
6375
+ };
6376
+ if (
6377
+ typeof value.path !== "string" ||
6378
+ typeof value.sessionId !== "string" ||
6379
+ typeof value.cwd !== "string" ||
6380
+ typeof identity.canonicalPath !== "string" ||
6381
+ typeof identity.dev !== "string" ||
6382
+ typeof identity.ino !== "string" ||
6383
+ typeof identity.size !== "number" ||
6384
+ typeof identity.mtimeNs !== "string" ||
6385
+ typeof identity.sha256 !== "string"
6386
+ )
6387
+ return undefined;
6388
+ return [
6389
+ value.path,
6390
+ value.sessionId,
6391
+ value.cwd,
6392
+ identity.canonicalPath,
6393
+ identity.dev,
6394
+ identity.ino,
6395
+ identity.size,
6396
+ identity.mtimeNs,
6397
+ identity.sha256,
6398
+ ];
6399
+ }
6400
+
6401
+ /**
6402
+ * Key equality matches strict equality of every member: strings and finite
6403
+ * numbers encode distinctly. A non-finite tombstone `size` encodes as `null`,
6404
+ * which no candidate produces.
6405
+ */
6406
+ function retiredIdentityKey(identity: RetiredTargetIdentity): string {
6407
+ return JSON.stringify(identity);
6408
+ }
6409
+
6410
+ /**
6411
+ * Snapshot of the identities retired by a scope's tombstones.
6412
+ *
6413
+ * Tombstones are visited in directory order and the walk stops at the first
6414
+ * unreadable or malformed one: it grants no retirement authority, and neither do
6415
+ * the tombstones after it. Listing builds this once and checks every candidate
6416
+ * against it, so its cost is linear in tombstones rather than in
6417
+ * candidates x tombstones.
6418
+ */
6419
+ function retiredTargetKeys(scope: ManagedScope): ReadonlySet<string> {
6420
+ const keys = new Set<string>();
6286
6421
  const directory = path.join(managedInternalDirectory(scope), MANAGED_TOMBSTONES_DIRECTORY);
6422
+ const digest = scopeDigest(scope.platform, scope.canonicalCwd);
6287
6423
  try {
6288
6424
  for (const name of fs.readdirSync(directory)) {
6289
6425
  const pathname = path.join(directory, name);
@@ -6293,47 +6429,27 @@ function isRetired(scope: ManagedScope, candidate: ManagedCandidate): boolean {
6293
6429
  if (
6294
6430
  record.schemaVersion !== 2 ||
6295
6431
  record.state !== "retired" ||
6296
- record.scope !== scopeDigest(scope.platform, scope.canonicalCwd) ||
6432
+ record.scope !== digest ||
6297
6433
  !Array.isArray(record.targets)
6298
6434
  )
6299
6435
  continue;
6300
- if (
6301
- record.targets.some(target => {
6302
- if (!target || typeof target !== "object") return false;
6303
- const value = target as {
6304
- path?: unknown;
6305
- sessionId?: unknown;
6306
- cwd?: unknown;
6307
- identity?: {
6308
- canonicalPath?: unknown;
6309
- dev?: unknown;
6310
- ino?: unknown;
6311
- size?: unknown;
6312
- mtimeNs?: unknown;
6313
- sha256?: unknown;
6314
- };
6315
- };
6316
- const identity = value.identity;
6317
- if (!identity) return false;
6318
- return (
6319
- value.path === candidate.path &&
6320
- value.sessionId === candidate.sessionId &&
6321
- value.cwd === candidate.cwd &&
6322
- identity.canonicalPath === candidate.identity.canonicalPath &&
6323
- identity.dev === String(candidate.identity.dev) &&
6324
- identity.ino === String(candidate.identity.ino) &&
6325
- identity.size === candidate.identity.size &&
6326
- identity.mtimeNs === String(candidate.identity.mtimeNs) &&
6327
- identity.sha256 === candidate.identity.sha256
6328
- );
6329
- })
6330
- )
6331
- return true;
6436
+ for (const target of record.targets) {
6437
+ const identity = parseRetiredTargetIdentity(target);
6438
+ if (identity) keys.add(retiredIdentityKey(identity));
6439
+ }
6332
6440
  }
6333
6441
  } catch {
6334
6442
  /* a missing/malformed tombstone grants no retirement authority */
6335
6443
  }
6336
- return false;
6444
+ return keys;
6445
+ }
6446
+
6447
+ function isRetiredIn(retired: ReadonlySet<string>, candidate: ManagedCandidate): boolean {
6448
+ return retired.has(retiredIdentityKey(candidateRetiredIdentity(candidate)));
6449
+ }
6450
+
6451
+ function isRetired(scope: ManagedScope, candidate: ManagedCandidate): boolean {
6452
+ return isRetiredIn(retiredTargetKeys(scope), candidate);
6337
6453
  }
6338
6454
 
6339
6455
  type ArtifactManifestEntry =