@gajae-code/coding-agent 0.10.2 → 0.11.1

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 (476) hide show
  1. package/CHANGELOG.md +58 -2
  2. package/dist/types/append-only-mode.d.ts +1 -1
  3. package/dist/types/async/job-manager.d.ts +46 -7
  4. package/dist/types/cli/args.d.ts +4 -5
  5. package/dist/types/cli/daemon-cli.d.ts +12 -1
  6. package/dist/types/cli/notify-cli.d.ts +21 -1
  7. package/dist/types/cli.d.ts +3 -0
  8. package/dist/types/commands/daemon.d.ts +39 -0
  9. package/dist/types/commands/launch.d.ts +3 -0
  10. package/dist/types/commands/notify.d.ts +28 -1
  11. package/dist/types/commands/sdk.d.ts +33 -0
  12. package/dist/types/config/atomic-yaml-patch.d.ts +85 -0
  13. package/dist/types/config/model-profile-activation.d.ts +35 -15
  14. package/dist/types/config/model-profiles.d.ts +11 -5
  15. package/dist/types/config/model-registry.d.ts +3 -3
  16. package/dist/types/config/model-resolver.d.ts +39 -6
  17. package/dist/types/config/model-selector-value.d.ts +17 -0
  18. package/dist/types/config/models-config-schema.d.ts +10 -6
  19. package/dist/types/config/settings-schema.d.ts +133 -6
  20. package/dist/types/config/settings.d.ts +63 -35
  21. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -6
  22. package/dist/types/coordinator-mcp/server.d.ts +6 -49
  23. package/dist/types/daemon/builtin.d.ts +6 -5
  24. package/dist/types/daemon/control-types.d.ts +1 -1
  25. package/dist/types/discovery/mcp-json.d.ts +12 -1
  26. package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
  27. package/dist/types/extensibility/extensions/compact-handler.d.ts +4 -2
  28. package/dist/types/extensibility/extensions/get-commands-handler.d.ts +2 -2
  29. package/dist/types/extensibility/extensions/loader.d.ts +10 -1
  30. package/dist/types/extensibility/extensions/runner.d.ts +3 -3
  31. package/dist/types/extensibility/extensions/types.d.ts +141 -16
  32. package/dist/types/extensibility/hooks/types.d.ts +1 -1
  33. package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -2
  34. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
  35. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +3 -0
  36. package/dist/types/gjc-runtime/team-runtime.d.ts +3 -4
  37. package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +13 -0
  38. package/dist/types/gjc-runtime/tmux-sessions.d.ts +4 -1
  39. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +11 -0
  40. package/dist/types/gjc-runtime/windows-powershell-command.d.ts +9 -0
  41. package/dist/types/harness-control-plane/operate.d.ts +5 -5
  42. package/dist/types/harness-control-plane/owner.d.ts +18 -4
  43. package/dist/types/harness-control-plane/receipts.d.ts +0 -1
  44. package/dist/types/harness-control-plane/sdk-transport.d.ts +43 -0
  45. package/dist/types/harness-control-plane/session-transport.d.ts +48 -0
  46. package/dist/types/harness-control-plane/types.d.ts +3 -4
  47. package/dist/types/lsp/index.d.ts +1 -1
  48. package/dist/types/main.d.ts +32 -38
  49. package/dist/types/modes/acp/acp-agent.d.ts +224 -53
  50. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -0
  51. package/dist/types/modes/acp/acp-mode.d.ts +6 -15
  52. package/dist/types/modes/acp/permission-mode.d.ts +4 -0
  53. package/dist/types/modes/acp/startup-options.d.ts +6 -0
  54. package/dist/types/modes/components/agent-dashboard.d.ts +3 -0
  55. package/dist/types/modes/components/model-selector.d.ts +1 -0
  56. package/dist/types/modes/components/notifications-settings-editor.d.ts +135 -0
  57. package/dist/types/modes/components/settings-selector.d.ts +6 -4
  58. package/dist/types/modes/components/welcome.d.ts +1 -1
  59. package/dist/types/modes/controllers/command-controller.d.ts +2 -2
  60. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -0
  61. package/dist/types/modes/controllers/selector-controller.d.ts +35 -1
  62. package/dist/types/modes/index.d.ts +0 -4
  63. package/dist/types/modes/interactive-mode.d.ts +5 -2
  64. package/dist/types/modes/runtime-init.d.ts +2 -2
  65. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +6 -9
  66. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -6
  67. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +6 -6
  68. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +4 -9
  69. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +1 -1
  70. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +170 -30
  71. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +7 -7
  72. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +105 -0
  73. package/dist/types/modes/types.d.ts +7 -2
  74. package/dist/types/runtime-mcp/client.d.ts +2 -0
  75. package/dist/types/runtime-mcp/config.d.ts +5 -1
  76. package/dist/types/runtime-mcp/manager.d.ts +8 -1
  77. package/dist/types/runtime-mcp/types.d.ts +4 -0
  78. package/dist/types/sdk/acp/adapter.d.ts +67 -0
  79. package/dist/types/sdk/acp/index.d.ts +2 -0
  80. package/dist/types/sdk/broker/broker.d.ts +53 -0
  81. package/dist/types/sdk/broker/discovery.d.ts +30 -0
  82. package/dist/types/sdk/broker/ensure.d.ts +42 -0
  83. package/dist/types/sdk/broker/identity.d.ts +3 -0
  84. package/dist/types/sdk/broker/index.d.ts +8 -0
  85. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +95 -0
  86. package/dist/types/sdk/broker/lifecycle.d.ts +85 -0
  87. package/dist/types/sdk/broker/process-incarnation.d.ts +14 -0
  88. package/dist/types/sdk/broker/runtime.d.ts +33 -0
  89. package/dist/types/sdk/broker/session-index.d.ts +57 -0
  90. package/dist/types/sdk/broker/state-version.d.ts +8 -0
  91. package/dist/types/sdk/broker/transport.d.ts +9 -0
  92. package/dist/types/sdk/bus/chat-command-policy.d.ts +49 -0
  93. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +21 -0
  94. package/dist/types/sdk/bus/chat-daemon-control.d.ts +111 -0
  95. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +77 -0
  96. package/dist/types/sdk/bus/chat-effect-journal.d.ts +84 -0
  97. package/dist/types/{notifications → sdk/bus}/config-commands.d.ts +7 -2
  98. package/dist/types/sdk/bus/config.d.ts +274 -0
  99. package/dist/types/sdk/bus/conversation-store.d.ts +72 -0
  100. package/dist/types/sdk/bus/discord-conversation.d.ts +58 -0
  101. package/dist/types/sdk/bus/discord-daemon.d.ts +50 -0
  102. package/dist/types/sdk/bus/discord-live-provider.d.ts +70 -0
  103. package/dist/types/sdk/bus/discord-provider.d.ts +85 -0
  104. package/dist/types/{notifications → sdk/bus}/html-format.d.ts +2 -2
  105. package/dist/types/{notifications → sdk/bus}/index.d.ts +113 -13
  106. package/dist/types/{notifications → sdk/bus}/lifecycle-control-runtime.d.ts +4 -2
  107. package/dist/types/{notifications → sdk/bus}/lifecycle-orchestrator.d.ts +18 -21
  108. package/dist/types/{notifications → sdk/bus}/managed-daemon.d.ts +1 -1
  109. package/dist/types/sdk/bus/native-runtime-compatibility.d.ts +13 -0
  110. package/dist/types/sdk/bus/notification-orchestration.d.ts +170 -0
  111. package/dist/types/{notifications → sdk/bus}/notification-service.d.ts +7 -1
  112. package/dist/types/sdk/bus/session-control.d.ts +83 -0
  113. package/dist/types/sdk/bus/slack-conversation.d.ts +56 -0
  114. package/dist/types/sdk/bus/slack-daemon.d.ts +60 -0
  115. package/dist/types/sdk/bus/slack-live-provider.d.ts +59 -0
  116. package/dist/types/sdk/bus/slack-provider.d.ts +55 -0
  117. package/dist/types/{notifications → sdk/bus}/telegram-cli.d.ts +4 -4
  118. package/dist/types/{notifications → sdk/bus}/telegram-daemon-cli.d.ts +14 -5
  119. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -0
  120. package/dist/types/{notifications → sdk/bus}/telegram-daemon-control.d.ts +2 -2
  121. package/dist/types/{notifications → sdk/bus}/telegram-daemon.d.ts +78 -17
  122. package/dist/types/{notifications → sdk/bus}/telegram-reference.d.ts +2 -2
  123. package/dist/types/sdk/bus/telegram-setup.d.ts +128 -0
  124. package/dist/types/{notifications → sdk/bus}/topic-registry.d.ts +11 -0
  125. package/dist/types/sdk/cli/index.d.ts +1 -0
  126. package/dist/types/sdk/cli/session-cli.d.ts +19 -0
  127. package/dist/types/sdk/client/client.d.ts +1 -0
  128. package/dist/types/sdk/client/discovery.d.ts +27 -0
  129. package/dist/types/sdk/client/index.d.ts +2 -0
  130. package/dist/types/sdk/host/control/dispatch.d.ts +34 -0
  131. package/dist/types/sdk/host/control/index.d.ts +2 -0
  132. package/dist/types/sdk/host/control/operations.d.ts +65 -0
  133. package/dist/types/sdk/host/events.d.ts +35 -0
  134. package/dist/types/sdk/host/host.d.ts +35 -0
  135. package/dist/types/sdk/host/index.d.ts +4 -0
  136. package/dist/types/sdk/host/query/cursor.d.ts +62 -0
  137. package/dist/types/sdk/host/query/handlers.d.ts +70 -0
  138. package/dist/types/sdk/host/query/index.d.ts +3 -0
  139. package/dist/types/sdk/host/query/revision-store.d.ts +60 -0
  140. package/dist/types/sdk/host/reverse-leases.d.ts +45 -0
  141. package/dist/types/sdk/host/types.d.ts +32 -0
  142. package/dist/types/sdk/index.d.ts +6 -0
  143. package/dist/types/sdk/lifecycle-session.d.ts +14 -0
  144. package/dist/types/sdk/mcp/index.d.ts +1 -0
  145. package/dist/types/sdk/mcp/server.d.ts +37 -0
  146. package/dist/types/sdk/models.d.ts +44 -0
  147. package/dist/types/sdk/protocol/adapter-validation.d.ts +14 -0
  148. package/dist/types/sdk/protocol/operation-registry.d.ts +24 -0
  149. package/dist/types/{sdk.d.ts → sdk/session.d.ts} +44 -38
  150. package/dist/types/sdk/startup-capability.d.ts +50 -0
  151. package/dist/types/session/agent-session.d.ts +114 -21
  152. package/dist/types/session/default-model-selection.d.ts +25 -0
  153. package/dist/types/session/fallback-chain-controller.d.ts +52 -0
  154. package/dist/types/session/session-manager.d.ts +58 -1
  155. package/dist/types/setup/credential-auto-import.d.ts +36 -6
  156. package/dist/types/tools/ask-answer-registry.d.ts +1 -1
  157. package/dist/types/tools/browser/benchmark/detector-report.d.ts +63 -0
  158. package/dist/types/tools/browser/benchmark/run-suite.d.ts +18 -0
  159. package/dist/types/tools/browser/launch.d.ts +22 -0
  160. package/dist/types/tools/browser/profile-discovery.d.ts +31 -0
  161. package/dist/types/tools/browser/profile-posture.d.ts +37 -0
  162. package/dist/types/tools/browser/profile-reuse.d.ts +39 -0
  163. package/dist/types/tools/browser/profile-warmup.d.ts +39 -0
  164. package/dist/types/tools/browser/registry.d.ts +19 -0
  165. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  166. package/dist/types/tools/index.d.ts +8 -8
  167. package/dist/types/tools/subagent-render.d.ts +1 -0
  168. package/dist/types/tools/subagent.d.ts +6 -1
  169. package/package.json +34 -13
  170. package/scripts/build-sdk-package-smoke.ts +99 -0
  171. package/scripts/capture-notifications-settings-showcase.ts +435 -0
  172. package/scripts/compile-args.ts +4 -2
  173. package/scripts/g005-daemon-smoke.ts +4 -5
  174. package/scripts/g011-daemon-path-smoke.ts +5 -5
  175. package/scripts/generate-sdk-adapter-parity-manifest.ts +163 -0
  176. package/scripts/generate-sdk-operation-inventory.ts +867 -0
  177. package/scripts/generate-telegram-baseline-manifest.ts +128 -0
  178. package/scripts/manifest-schema.ts +124 -0
  179. package/scripts/run-test-manifest.ts +153 -0
  180. package/scripts/run-threat-model-check.ts +157 -0
  181. package/scripts/verify-gjc-sdk-canonicalization.ts +3467 -0
  182. package/scripts/verify-gjc-sdk-rename.ts +90 -0
  183. package/src/append-only-mode.ts +1 -1
  184. package/src/async/job-manager.ts +406 -63
  185. package/src/cli/args.ts +43 -12
  186. package/src/cli/auth-gateway-cli.ts +2 -2
  187. package/src/cli/config-cli.ts +2 -1
  188. package/src/cli/daemon-cli.ts +63 -14
  189. package/src/cli/list-models.ts +1 -1
  190. package/src/cli/notify-cli.ts +288 -286
  191. package/src/cli.ts +21 -2
  192. package/src/commands/daemon.ts +50 -6
  193. package/src/commands/harness.ts +93 -36
  194. package/src/commands/launch.ts +5 -2
  195. package/src/commands/mcp-serve.ts +19 -12
  196. package/src/commands/notify.ts +36 -3
  197. package/src/commands/sdk.ts +426 -0
  198. package/src/commit/agentic/agent.ts +1 -0
  199. package/src/config/atomic-yaml-patch.ts +423 -0
  200. package/src/config/model-profile-activation.ts +240 -89
  201. package/src/config/model-profiles.ts +18 -15
  202. package/src/config/model-registry.ts +51 -26
  203. package/src/config/model-resolver.ts +138 -51
  204. package/src/config/model-selector-value.ts +50 -0
  205. package/src/config/models-config-schema.ts +14 -21
  206. package/src/config/settings-schema.ts +128 -10
  207. package/src/config/settings.ts +1120 -158
  208. package/src/coordinator-mcp/model-preset.ts +74 -20
  209. package/src/coordinator-mcp/server.ts +1597 -3052
  210. package/src/daemon/builtin.ts +8 -5
  211. package/src/daemon/control-types.ts +1 -1
  212. package/src/defaults/gjc/skills/team/SKILL.md +4 -4
  213. package/src/discovery/mcp-json.ts +327 -20
  214. package/src/extensibility/custom-tools/types.ts +1 -1
  215. package/src/extensibility/extensions/compact-handler.ts +3 -3
  216. package/src/extensibility/extensions/get-commands-handler.ts +2 -2
  217. package/src/extensibility/extensions/loader.ts +69 -5
  218. package/src/extensibility/extensions/runner.ts +112 -4
  219. package/src/extensibility/extensions/types.ts +176 -17
  220. package/src/extensibility/hooks/types.ts +1 -1
  221. package/src/gjc-runtime/launch-tmux.ts +170 -86
  222. package/src/gjc-runtime/psmux-detect.ts +82 -4
  223. package/src/gjc-runtime/session-state-sidecar.ts +41 -31
  224. package/src/gjc-runtime/team-runtime.ts +54 -35
  225. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +53 -17
  226. package/src/gjc-runtime/tmux-owner-isolation.ts +1 -1
  227. package/src/gjc-runtime/tmux-sessions.ts +19 -13
  228. package/src/gjc-runtime/ultragoal-guard.ts +108 -14
  229. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +102 -1
  230. package/src/gjc-runtime/ultragoal-runtime.ts +227 -11
  231. package/src/gjc-runtime/windows-powershell-command.ts +58 -0
  232. package/src/harness-control-plane/operate.ts +9 -9
  233. package/src/harness-control-plane/owner.ts +283 -39
  234. package/src/harness-control-plane/receipts.ts +0 -1
  235. package/src/harness-control-plane/sdk-transport.ts +395 -0
  236. package/src/harness-control-plane/session-transport.ts +94 -0
  237. package/src/harness-control-plane/types.ts +1 -1
  238. package/src/internal-urls/docs-index.generated.ts +34 -35
  239. package/src/lsp/client.ts +42 -6
  240. package/src/lsp/index.ts +2 -2
  241. package/src/main.ts +299 -204
  242. package/src/modes/DESIGN.md +258 -0
  243. package/src/modes/acp/acp-agent.ts +1228 -2612
  244. package/src/modes/acp/acp-client-bridge.ts +2 -20
  245. package/src/modes/acp/acp-event-mapper.ts +18 -2
  246. package/src/modes/acp/acp-mode.ts +8 -36
  247. package/src/modes/acp/permission-mode.ts +25 -0
  248. package/src/modes/acp/startup-options.ts +6 -0
  249. package/src/modes/components/agent-dashboard.ts +37 -17
  250. package/src/modes/components/model-selector.ts +93 -35
  251. package/src/modes/components/notifications-settings-editor.ts +1379 -0
  252. package/src/modes/components/settings-selector.ts +60 -11
  253. package/src/modes/components/welcome.ts +4 -4
  254. package/src/modes/controllers/command-controller.ts +10 -17
  255. package/src/modes/controllers/event-controller.ts +11 -16
  256. package/src/modes/controllers/extension-ui-controller.ts +396 -28
  257. package/src/modes/controllers/selector-controller.ts +674 -36
  258. package/src/modes/index.ts +0 -22
  259. package/src/modes/interactive-mode.ts +87 -36
  260. package/src/modes/runtime-init.ts +298 -6
  261. package/src/modes/shared/agent-wire/approval-gate.ts +9 -11
  262. package/src/modes/shared/agent-wire/deep-interview-gate.ts +10 -11
  263. package/src/modes/shared/agent-wire/event-contract.ts +7 -8
  264. package/src/modes/shared/agent-wire/event-envelope.ts +9 -17
  265. package/src/modes/shared/agent-wire/event-observation.ts +16 -7
  266. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +1258 -87
  267. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +15 -15
  268. package/src/modes/shared/agent-wire/workflow-gate-types.ts +113 -0
  269. package/src/modes/types.ts +7 -2
  270. package/src/runtime-mcp/client.ts +94 -19
  271. package/src/runtime-mcp/config.ts +27 -15
  272. package/src/runtime-mcp/manager.ts +343 -105
  273. package/src/runtime-mcp/oauth-flow.ts +42 -12
  274. package/src/runtime-mcp/transports/http.ts +49 -19
  275. package/src/runtime-mcp/transports/stdio.ts +47 -28
  276. package/src/runtime-mcp/types.ts +10 -0
  277. package/src/sdk/acp/adapter.ts +418 -0
  278. package/src/sdk/acp/index.ts +2 -0
  279. package/src/sdk/broker/broker.ts +642 -0
  280. package/src/sdk/broker/discovery.ts +113 -0
  281. package/src/sdk/broker/ensure.ts +307 -0
  282. package/src/sdk/broker/identity.ts +42 -0
  283. package/src/sdk/broker/index.ts +8 -0
  284. package/src/sdk/broker/internal-source-marker-2178.txt +1 -0
  285. package/src/sdk/broker/internal-source.bunfig.toml +1 -0
  286. package/src/sdk/broker/lifecycle-ledger.ts +286 -0
  287. package/src/sdk/broker/lifecycle.ts +2157 -0
  288. package/src/sdk/broker/process-incarnation.ts +159 -0
  289. package/src/sdk/broker/runtime.ts +164 -0
  290. package/src/sdk/broker/session-index.ts +318 -0
  291. package/src/sdk/broker/state-version.ts +23 -0
  292. package/src/sdk/broker/transport.ts +125 -0
  293. package/src/sdk/bus/chat-command-policy.ts +107 -0
  294. package/src/sdk/bus/chat-daemon-cli.ts +214 -0
  295. package/src/sdk/bus/chat-daemon-control.ts +713 -0
  296. package/src/sdk/bus/chat-daemon-runtime.ts +526 -0
  297. package/src/sdk/bus/chat-effect-journal.ts +355 -0
  298. package/src/{notifications → sdk/bus}/config-commands.ts +39 -10
  299. package/src/sdk/bus/config.ts +433 -0
  300. package/src/sdk/bus/conversation-store.ts +391 -0
  301. package/src/sdk/bus/discord-conversation.ts +85 -0
  302. package/src/sdk/bus/discord-daemon.ts +2292 -0
  303. package/src/sdk/bus/discord-live-provider.ts +521 -0
  304. package/src/sdk/bus/discord-provider.ts +56 -0
  305. package/src/{notifications → sdk/bus}/html-format.ts +2 -2
  306. package/src/sdk/bus/index.ts +3979 -0
  307. package/src/{notifications → sdk/bus}/lifecycle-commands.ts +2 -0
  308. package/src/{notifications → sdk/bus}/lifecycle-control-runtime.ts +225 -196
  309. package/src/{notifications → sdk/bus}/lifecycle-orchestrator.ts +161 -74
  310. package/src/{notifications → sdk/bus}/managed-daemon.ts +2 -2
  311. package/src/sdk/bus/native-runtime-compatibility.ts +42 -0
  312. package/src/sdk/bus/natives-augment.d.ts +38 -0
  313. package/src/sdk/bus/notification-orchestration.ts +535 -0
  314. package/src/{notifications → sdk/bus}/notification-service.ts +106 -12
  315. package/src/sdk/bus/session-control.ts +333 -0
  316. package/src/sdk/bus/slack-conversation.ts +74 -0
  317. package/src/sdk/bus/slack-daemon.ts +2144 -0
  318. package/src/sdk/bus/slack-live-provider.ts +380 -0
  319. package/src/sdk/bus/slack-provider.ts +76 -0
  320. package/src/{notifications → sdk/bus}/telegram-cli.ts +8 -8
  321. package/src/sdk/bus/telegram-daemon-cli.ts +330 -0
  322. package/src/sdk/bus/telegram-daemon-contract.ts +14 -0
  323. package/src/{notifications → sdk/bus}/telegram-daemon-control.ts +4 -4
  324. package/src/{notifications → sdk/bus}/telegram-daemon.ts +603 -63
  325. package/src/{notifications → sdk/bus}/telegram-reference.ts +2 -2
  326. package/src/sdk/bus/telegram-setup.ts +663 -0
  327. package/src/{notifications → sdk/bus}/topic-registry.ts +32 -0
  328. package/src/sdk/cli/index.ts +1 -0
  329. package/src/sdk/cli/session-cli.ts +275 -0
  330. package/src/sdk/client/client.ts +1 -0
  331. package/src/sdk/client/discovery.ts +108 -0
  332. package/src/sdk/client/index.ts +2 -0
  333. package/src/sdk/host/control/dispatch.ts +367 -0
  334. package/src/sdk/host/control/index.ts +10 -0
  335. package/src/sdk/host/control/operations.ts +63 -0
  336. package/src/sdk/host/events.ts +91 -0
  337. package/src/sdk/host/host.ts +296 -0
  338. package/src/sdk/host/index.ts +4 -0
  339. package/src/sdk/host/query/cursor.ts +189 -0
  340. package/src/sdk/host/query/handlers.ts +533 -0
  341. package/src/sdk/host/query/index.ts +3 -0
  342. package/src/sdk/host/query/revision-store.ts +1126 -0
  343. package/src/sdk/host/reverse-leases.ts +325 -0
  344. package/src/sdk/host/types.ts +29 -0
  345. package/src/sdk/index.ts +13 -0
  346. package/src/sdk/lifecycle-session.ts +39 -0
  347. package/src/sdk/mcp/index.ts +1 -0
  348. package/src/sdk/mcp/server.ts +332 -0
  349. package/src/sdk/models.ts +168 -0
  350. package/src/sdk/protocol/adapter-validation.ts +48 -0
  351. package/src/sdk/protocol/operation-inventory.generated.json +5654 -0
  352. package/src/sdk/protocol/operation-registry.ts +312 -0
  353. package/src/{sdk.ts → sdk/session.ts} +339 -212
  354. package/src/sdk/startup-capability.ts +189 -0
  355. package/src/session/agent-session.ts +2650 -944
  356. package/src/session/default-model-selection.ts +93 -0
  357. package/src/session/fallback-chain-controller.ts +136 -0
  358. package/src/session/session-manager.ts +438 -11
  359. package/src/session/session-storage.ts +8 -1
  360. package/src/setup/credential-auto-import.ts +290 -35
  361. package/src/setup/hermes/templates/operator-instructions.v1.md +3 -5
  362. package/src/slash-commands/builtin-registry.ts +2 -1
  363. package/src/task/executor.ts +62 -13
  364. package/src/task/index.ts +26 -16
  365. package/src/tools/ask-answer-registry.ts +1 -1
  366. package/src/tools/ask.ts +13 -8
  367. package/src/tools/browser/STEALTH-THIRD-PARTY-NOTICES.md +26 -0
  368. package/src/tools/browser/benchmark/detector-report.ts +157 -0
  369. package/src/tools/browser/benchmark/run-suite.ts +39 -0
  370. package/src/tools/browser/launch.ts +124 -19
  371. package/src/tools/browser/profile-discovery.ts +63 -0
  372. package/src/tools/browser/profile-posture.ts +59 -0
  373. package/src/tools/browser/profile-reuse.ts +102 -0
  374. package/src/tools/browser/profile-warmup.ts +109 -0
  375. package/src/tools/browser/registry.ts +87 -11
  376. package/src/tools/browser/tab-protocol.ts +1 -0
  377. package/src/tools/browser/tab-supervisor.ts +1 -0
  378. package/src/tools/browser/tab-worker.ts +1 -1
  379. package/src/tools/browser.ts +6 -0
  380. package/src/tools/index.ts +8 -8
  381. package/src/tools/puppeteer/14_stealth_permissions.txt +57 -0
  382. package/src/tools/puppeteer/15_stealth_webrtc.txt +60 -0
  383. package/src/tools/subagent-render.ts +19 -8
  384. package/src/tools/subagent.ts +84 -32
  385. package/src/tools/telegram-send.ts +2 -2
  386. package/vendor/insane-search/MANIFEST.json +2 -1
  387. package/vendor/insane-search/engine/bias_check.py +4 -1
  388. package/vendor/insane-search/engine/phase0.py +26 -1
  389. package/dist/types/harness-control-plane/rpc-adapter.d.ts +0 -69
  390. package/dist/types/modes/bridge/auth.d.ts +0 -12
  391. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +0 -9
  392. package/dist/types/modes/bridge/bridge-mode.d.ts +0 -47
  393. package/dist/types/modes/bridge/bridge-ui-context.d.ts +0 -88
  394. package/dist/types/modes/bridge/event-stream.d.ts +0 -8
  395. package/dist/types/modes/rpc/host-tools.d.ts +0 -1
  396. package/dist/types/modes/rpc/host-uris.d.ts +0 -1
  397. package/dist/types/modes/rpc/rpc-client.d.ts +0 -299
  398. package/dist/types/modes/rpc/rpc-mode.d.ts +0 -86
  399. package/dist/types/modes/rpc/rpc-socket-security.d.ts +0 -10
  400. package/dist/types/modes/rpc/rpc-types.d.ts +0 -810
  401. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +0 -18
  402. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +0 -37
  403. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +0 -2
  404. package/dist/types/modes/shared/agent-wire/handshake.d.ts +0 -56
  405. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +0 -16
  406. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +0 -17
  407. package/dist/types/modes/shared/agent-wire/protocol.d.ts +0 -25
  408. package/dist/types/modes/shared/agent-wire/responses.d.ts +0 -4
  409. package/dist/types/modes/shared/agent-wire/scopes.d.ts +0 -18
  410. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +0 -25
  411. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +0 -42
  412. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +0 -27
  413. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +0 -29
  414. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +0 -68
  415. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +0 -166
  416. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +0 -127
  417. package/dist/types/notifications/config.d.ts +0 -132
  418. package/src/harness-control-plane/rpc-adapter.ts +0 -290
  419. package/src/modes/bridge/auth.ts +0 -41
  420. package/src/modes/bridge/bridge-client-bridge.ts +0 -47
  421. package/src/modes/bridge/bridge-mode.ts +0 -675
  422. package/src/modes/bridge/bridge-ui-context.ts +0 -200
  423. package/src/modes/bridge/event-stream.ts +0 -70
  424. package/src/modes/rpc/host-tools.ts +0 -1
  425. package/src/modes/rpc/host-uris.ts +0 -1
  426. package/src/modes/rpc/rpc-client.ts +0 -1166
  427. package/src/modes/rpc/rpc-mode.ts +0 -1029
  428. package/src/modes/rpc/rpc-socket-security.ts +0 -115
  429. package/src/modes/rpc/rpc-types.ts +0 -637
  430. package/src/modes/shared/agent-wire/command-contract.ts +0 -18
  431. package/src/modes/shared/agent-wire/command-dispatch.ts +0 -498
  432. package/src/modes/shared/agent-wire/command-validation.ts +0 -181
  433. package/src/modes/shared/agent-wire/handshake.ts +0 -157
  434. package/src/modes/shared/agent-wire/host-tool-bridge.ts +0 -197
  435. package/src/modes/shared/agent-wire/host-uri-bridge.ts +0 -236
  436. package/src/modes/shared/agent-wire/protocol.ts +0 -46
  437. package/src/modes/shared/agent-wire/responses.ts +0 -17
  438. package/src/modes/shared/agent-wire/scopes.ts +0 -94
  439. package/src/modes/shared/agent-wire/session-registry.ts +0 -109
  440. package/src/modes/shared/agent-wire/ui-request-broker.ts +0 -150
  441. package/src/modes/shared/agent-wire/ui-result.ts +0 -48
  442. package/src/modes/shared/agent-wire/unattended-action-policy.ts +0 -365
  443. package/src/modes/shared/agent-wire/unattended-audit.ts +0 -176
  444. package/src/modes/shared/agent-wire/unattended-run-controller.ts +0 -426
  445. package/src/modes/shared/agent-wire/unattended-session.ts +0 -528
  446. package/src/notifications/config.ts +0 -219
  447. package/src/notifications/index.ts +0 -1835
  448. package/src/notifications/telegram-daemon-cli.ts +0 -243
  449. package/src/tools/puppeteer/09_stealth_locale.txt +0 -46
  450. /package/dist/types/{notifications → sdk/bus}/attachment-registry.d.ts +0 -0
  451. /package/dist/types/{notifications → sdk/bus}/chat-adapters.d.ts +0 -0
  452. /package/dist/types/{notifications → sdk/bus}/daemon-paths.d.ts +0 -0
  453. /package/dist/types/{notifications → sdk/bus}/engine.d.ts +0 -0
  454. /package/dist/types/{notifications → sdk/bus}/helpers.d.ts +0 -0
  455. /package/dist/types/{notifications → sdk/bus}/lifecycle-commands.d.ts +0 -0
  456. /package/dist/types/{notifications → sdk/bus}/operator-runtime.d.ts +0 -0
  457. /package/dist/types/{notifications → sdk/bus}/rate-limit-pool.d.ts +0 -0
  458. /package/dist/types/{notifications → sdk/bus}/recent-activity.d.ts +0 -0
  459. /package/dist/types/{notifications → sdk/bus}/reply-sent-store.d.ts +0 -0
  460. /package/dist/types/{notifications → sdk/bus}/rich-draft.d.ts +0 -0
  461. /package/dist/types/{notifications → sdk/bus}/rich-render.d.ts +0 -0
  462. /package/dist/types/{notifications → sdk/bus}/threaded-inbound.d.ts +0 -0
  463. /package/dist/types/{notifications → sdk/bus}/threaded-render.d.ts +0 -0
  464. /package/src/{notifications → sdk/bus}/attachment-registry.ts +0 -0
  465. /package/src/{notifications → sdk/bus}/chat-adapters.ts +0 -0
  466. /package/src/{notifications → sdk/bus}/daemon-paths.ts +0 -0
  467. /package/src/{notifications → sdk/bus}/engine.ts +0 -0
  468. /package/src/{notifications → sdk/bus}/helpers.ts +0 -0
  469. /package/src/{notifications → sdk/bus}/operator-runtime.ts +0 -0
  470. /package/src/{notifications → sdk/bus}/rate-limit-pool.ts +0 -0
  471. /package/src/{notifications → sdk/bus}/recent-activity.ts +0 -0
  472. /package/src/{notifications → sdk/bus}/reply-sent-store.ts +0 -0
  473. /package/src/{notifications → sdk/bus}/rich-draft.ts +0 -0
  474. /package/src/{notifications → sdk/bus}/rich-render.ts +0 -0
  475. /package/src/{notifications → sdk/bus}/threaded-inbound.ts +0 -0
  476. /package/src/{notifications → sdk/bus}/threaded-render.ts +0 -0
@@ -4,6 +4,7 @@ import * as fs from "node:fs";
4
4
  import * as os from "node:os";
5
5
  import * as path from "node:path";
6
6
  import { type AgentMessage, canContinuePersistedHistory } from "@gajae-code/agent-core";
7
+ import type { ConfiguredModelChainEntry as SharedConfiguredModelChainEntry } from "@gajae-code/agent-core/compaction";
7
8
  import type {
8
9
  ImageContent,
9
10
  Message,
@@ -221,6 +222,14 @@ export interface ModelChangeEntry extends SessionEntryBase {
221
222
  thinkingLevel?: string | null;
222
223
  }
223
224
 
225
+ /** Persisted configured fallback chain for one model role. */
226
+ export type ConfiguredModelChainEntry = SharedConfiguredModelChainEntry;
227
+
228
+ export type ConfiguredModelChain = Pick<
229
+ ConfiguredModelChainEntry,
230
+ "role" | "entries" | "origin" | "identity" | "explicitHead" | "cleared"
231
+ >;
232
+
224
233
  export interface ServiceTierChangeEntry extends SessionEntryBase {
225
234
  type: "service_tier_change";
226
235
  serviceTier: ServiceTier | null;
@@ -353,11 +362,31 @@ export type SessionEntry =
353
362
  | TtsrInjectionEntry
354
363
  | MCPToolSelectionEntry
355
364
  | SessionInitEntry
356
- | ModeChangeEntry;
365
+ | ModeChangeEntry
366
+ | ConfiguredModelChainEntry;
357
367
 
358
368
  /** Raw file entry (includes header) */
359
369
  export type FileEntry = SessionHeader | SessionEntry;
360
370
 
371
+ export type DefaultModelSelectionStage = {
372
+ readonly entryRevision: number;
373
+ readonly headerExportRevision: number;
374
+ readonly sessionId: string;
375
+ readonly sessionFile: string | undefined;
376
+ readonly entries: readonly FileEntry[];
377
+ readonly tempPath: string | undefined;
378
+ readonly persistsToExistingFile: boolean;
379
+ };
380
+
381
+ export type DefaultModelSelectionPromotion =
382
+ | { readonly kind: "promoted" }
383
+ | { readonly kind: "not_promoted"; readonly error?: Error }
384
+ | { readonly kind: "unknown"; readonly error: Error };
385
+
386
+ type SessionFileReplacementSyncOutcome =
387
+ | { readonly kind: "replaced" }
388
+ | { readonly kind: "restored_previous"; readonly error: Error };
389
+
361
390
  /** Tree node for getTree() - defensive copy of session structure */
362
391
  export interface SessionTreeNode {
363
392
  entry: SessionEntry;
@@ -372,6 +401,9 @@ export interface SessionContext {
372
401
  serviceTier?: ServiceTier;
373
402
  /** Model roles: { default: "provider/modelId", small: "provider/modelId", ... } */
374
403
  models: Record<string, string>;
404
+ /** Configured fallback chains for model roles on the active branch. */
405
+ configuredModelChains: Record<string, ConfiguredModelChain>;
406
+
375
407
  /** Names of TTSR rules that have been injected this session */
376
408
  injectedTtsrRules: string[];
377
409
  /** Rich TTSR rule injection records for repeat resume. */
@@ -428,6 +460,20 @@ export interface StrictSessionOpenFailure {
428
460
  reason: ResumeTailError["reason"] | "identity-mismatch";
429
461
  }
430
462
 
463
+ /** Exact transcript bytes and authority captured from one descriptor-bound read. */
464
+ export interface CapturedSessionTranscriptSnapshot {
465
+ sourcePath: string;
466
+ identity: ResumeSessionIdentity;
467
+ content: Uint8Array;
468
+ storage: SessionStorage;
469
+ }
470
+
471
+ export type StrictSessionCaptureResult =
472
+ | { kind: "captured"; snapshot: CapturedSessionTranscriptSnapshot }
473
+ | ResumeTailError;
474
+
475
+ export type StrictSessionForkResult = { kind: "forked"; manager: SessionManager } | StrictSessionOpenFailure;
476
+
431
477
  /** Result of opening an inspected session without create-or-rewrite fallback. */
432
478
  export type StrictSessionOpenResult = StrictSessionOpenSuccess | StrictSessionOpenFailure;
433
479
 
@@ -744,6 +790,31 @@ export function parseSessionEntries(content: string): FileEntry[] {
744
790
  return parseJsonlLenient<FileEntry>(content);
745
791
  }
746
792
 
793
+ function normalizeConfiguredModelChainEntry(entry: unknown): ConfiguredModelChain | undefined {
794
+ if (!entry || typeof entry !== "object") return undefined;
795
+ const candidate = entry as Partial<ConfiguredModelChainEntry>;
796
+ if (typeof candidate.role !== "string" || candidate.role.length === 0) return undefined;
797
+ if (candidate.cleared === true) {
798
+ return {
799
+ role: candidate.role,
800
+ entries: [],
801
+ origin: typeof candidate.origin === "string" ? candidate.origin : "session",
802
+ explicitHead: candidate.explicitHead === true,
803
+ cleared: true,
804
+ };
805
+ }
806
+ if (!Array.isArray(candidate.entries)) return undefined;
807
+ const entries = candidate.entries.filter((model): model is string => typeof model === "string" && model.length > 0);
808
+ if (entries.length === 0) return undefined;
809
+ return {
810
+ role: candidate.role,
811
+ entries,
812
+ origin: typeof candidate.origin === "string" ? candidate.origin : "session",
813
+ ...(typeof candidate.identity === "string" ? { identity: candidate.identity } : {}),
814
+ explicitHead: candidate.explicitHead === true,
815
+ };
816
+ }
817
+
747
818
  export function getLatestCompactionEntry(entries: SessionEntry[]): CompactionEntry | null {
748
819
  for (let i = entries.length - 1; i >= 0; i--) {
749
820
  if (entries[i].type === "compaction") {
@@ -781,6 +852,8 @@ export function buildSessionContext(
781
852
  thinkingLevel: "off",
782
853
  serviceTier: undefined,
783
854
  models: {},
855
+ configuredModelChains: {},
856
+
784
857
  injectedTtsrRules: [],
785
858
  injectedTtsrRuleRecords: [],
786
859
  ttsrMessageCount: 0,
@@ -804,6 +877,8 @@ export function buildSessionContext(
804
877
  thinkingLevel: "off",
805
878
  serviceTier: undefined,
806
879
  models: {},
880
+ configuredModelChains: {},
881
+
807
882
  injectedTtsrRules: [],
808
883
  injectedTtsrRuleRecords: [],
809
884
  ttsrMessageCount: 0,
@@ -829,6 +904,8 @@ export function buildSessionContext(
829
904
  let thinkingLevel: string | undefined = "off";
830
905
  let serviceTier: ServiceTier | undefined;
831
906
  const models: Record<string, string> = {};
907
+ const configuredModelChains: Record<string, ConfiguredModelChain> = {};
908
+
832
909
  let compaction: CompactionEntry | null = null;
833
910
  const injectedTtsrRulesSet = new Set<string>();
834
911
  const injectedTtsrRuleRecords = new Map<string, TtsrInjectionRecord>();
@@ -859,6 +936,13 @@ export function buildSessionContext(
859
936
  hasExplicitDefaultModel = true;
860
937
  }
861
938
  }
939
+ } else if (entry.type === "configured_model_chain") {
940
+ const configuredChain = normalizeConfiguredModelChainEntry(entry);
941
+ if (configuredChain?.cleared) {
942
+ delete configuredModelChains[configuredChain.role];
943
+ } else if (configuredChain) {
944
+ configuredModelChains[configuredChain.role] = configuredChain;
945
+ }
862
946
  } else if (entry.type === "service_tier_change") {
863
947
  serviceTier = entry.serviceTier ?? undefined;
864
948
  } else if (entry.type === "message" && entry.message.role === "assistant") {
@@ -897,6 +981,16 @@ export function buildSessionContext(
897
981
  }
898
982
 
899
983
  const injectedTtsrRules = Array.from(injectedTtsrRulesSet);
984
+ for (const [role, model] of Object.entries(models)) {
985
+ if (role in configuredModelChains) continue;
986
+ configuredModelChains[role] = {
987
+ role,
988
+ entries: [model],
989
+ origin: "legacy_session",
990
+ explicitHead: true,
991
+ };
992
+ }
993
+
900
994
  const injectedTtsrRuleRecordsArray = Array.from(injectedTtsrRuleRecords.values());
901
995
 
902
996
  // Build messages and collect corresponding entries
@@ -996,6 +1090,8 @@ export function buildSessionContext(
996
1090
  thinkingLevel,
997
1091
  serviceTier,
998
1092
  models,
1093
+ configuredModelChains,
1094
+
999
1095
  injectedTtsrRules,
1000
1096
  injectedTtsrRuleRecords: injectedTtsrRuleRecordsArray,
1001
1097
  ttsrMessageCount,
@@ -1014,6 +1110,13 @@ function cloneSessionContext(context: SessionContext): SessionContext {
1014
1110
  ...context,
1015
1111
  messages,
1016
1112
  models: { ...context.models },
1113
+ configuredModelChains: Object.fromEntries(
1114
+ Object.entries(context.configuredModelChains ?? {}).map(([role, chain]) => [
1115
+ role,
1116
+ { ...chain, entries: [...chain.entries] },
1117
+ ]),
1118
+ ),
1119
+
1017
1120
  injectedTtsrRules: [...context.injectedTtsrRules],
1018
1121
  injectedTtsrRuleRecords: context.injectedTtsrRuleRecords?.map(record => ({ ...record })),
1019
1122
  ttsrMessageCount: context.ttsrMessageCount,
@@ -1171,6 +1274,7 @@ function sameResumeStat(left: SessionStorageStat, right: SessionStorageStat): bo
1171
1274
 
1172
1275
  interface ResumeInspectionSnapshot {
1173
1276
  identity: ResumeSessionIdentity;
1277
+ content: Uint8Array;
1174
1278
  entries: FileEntry[];
1175
1279
  context: SessionContext;
1176
1280
  migrationApplied: boolean;
@@ -1218,6 +1322,9 @@ function hasStrictSessionSchema(entries: FileEntry[]): boolean {
1218
1322
  )
1219
1323
  return false;
1220
1324
  break;
1325
+ case "configured_model_chain":
1326
+ if (!normalizeConfiguredModelChainEntry(value)) return false;
1327
+ break;
1221
1328
  case "compaction":
1222
1329
  if (
1223
1330
  typeof value.summary !== "string" ||
@@ -1344,7 +1451,7 @@ function inspectResumeSessionFile(
1344
1451
  mtimeNs: snapshot.mtimeNs,
1345
1452
  sha256: crypto.createHash("sha256").update(bytes).digest("hex"),
1346
1453
  };
1347
- return { identity, entries, context, migrationApplied };
1454
+ return { identity, content: Uint8Array.from(bytes), entries, context, migrationApplied };
1348
1455
  } catch {
1349
1456
  return { kind: "error", reason: "malformed" };
1350
1457
  }
@@ -3131,6 +3238,7 @@ interface SessionManagerStateSnapshot {
3131
3238
  titleSource: "auto" | "user" | undefined;
3132
3239
  sessionFile: string | undefined;
3133
3240
  flushed: boolean;
3241
+ ensuredOnDisk: boolean;
3134
3242
  needsFullRewriteOnNextPersist: boolean;
3135
3243
  fileEntries: FileEntry[];
3136
3244
  materializedFileEntries: FileEntry[];
@@ -3323,6 +3431,7 @@ export class SessionManager {
3323
3431
  titleSource: this.#titleSource,
3324
3432
  sessionFile: this.#sessionFile,
3325
3433
  flushed: this.#flushed,
3434
+ ensuredOnDisk: this.#ensuredOnDisk,
3326
3435
  needsFullRewriteOnNextPersist: this.#needsFullRewriteOnNextPersist,
3327
3436
  // Snapshot entry objects by reference: switch/reload replaces the active entry array,
3328
3437
  // so rollback does not need structured cloning of extension/custom details.
@@ -3345,6 +3454,7 @@ export class SessionManager {
3345
3454
  this.#titleSource = snapshot.titleSource;
3346
3455
  this.#sessionFile = snapshot.sessionFile;
3347
3456
  this.#flushed = snapshot.flushed;
3457
+ this.#ensuredOnDisk = snapshot.ensuredOnDisk;
3348
3458
  this.#needsFullRewriteOnNextPersist = snapshot.needsFullRewriteOnNextPersist;
3349
3459
  this.#fileEntries = restoredFileEntries;
3350
3460
  if (!retainsPersistWriter) {
@@ -3636,7 +3746,7 @@ export class SessionManager {
3636
3746
  }
3637
3747
 
3638
3748
  /** Sync version for initial creation (no existing writer to close) */
3639
- #newSessionSync(options?: NewSessionOptions): string | undefined {
3749
+ #newSessionSync(options?: NewSessionOptions, writeBreadcrumb = true): string | undefined {
3640
3750
  this.#persistChain = Promise.resolve();
3641
3751
  this.#persistError = undefined;
3642
3752
  this.#persistErrorReported = false;
@@ -3671,7 +3781,7 @@ export class SessionManager {
3671
3781
  if (this.persist) {
3672
3782
  const fileTimestamp = timestamp.replace(/[:.]/g, "-");
3673
3783
  this.#sessionFile = path.join(this.getSessionDir(), `${fileTimestamp}_${this.#sessionId}.jsonl`);
3674
- writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
3784
+ if (writeBreadcrumb) writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
3675
3785
  }
3676
3786
  this.#resetResidentTextBlobStore();
3677
3787
  return this.#sessionFile;
@@ -3795,7 +3905,11 @@ export class SessionManager {
3795
3905
  // shared `*.bak` glob on both real and in-memory storage backends and promote it back to
3796
3906
  // the primary on the next session-dir scan.
3797
3907
 
3798
- #replaceSessionFileAfterEpermSync(tempPath: string, targetPath: string, renameError: unknown): void {
3908
+ #replaceSessionFileAfterEpermSync(
3909
+ tempPath: string,
3910
+ targetPath: string,
3911
+ renameError: unknown,
3912
+ ): SessionFileReplacementSyncOutcome {
3799
3913
  const dir = path.resolve(targetPath, "..");
3800
3914
  const backupPath = path.join(dir, `${path.basename(targetPath)}.${Snowflake.next()}.bak`);
3801
3915
  try {
@@ -3803,7 +3917,7 @@ export class SessionManager {
3803
3917
  } catch (err) {
3804
3918
  if (isEnoent(err)) {
3805
3919
  this.storage.renameSync(tempPath, targetPath);
3806
- return;
3920
+ return { kind: "replaced" };
3807
3921
  }
3808
3922
  throw toError(renameError);
3809
3923
  }
@@ -3822,7 +3936,7 @@ export class SessionManager {
3822
3936
  { cause: originalError },
3823
3937
  );
3824
3938
  }
3825
- throw replaceError;
3939
+ return { kind: "restored_previous", error: replaceError };
3826
3940
  }
3827
3941
 
3828
3942
  try {
@@ -3836,6 +3950,7 @@ export class SessionManager {
3836
3950
  });
3837
3951
  }
3838
3952
  }
3953
+ return { kind: "replaced" };
3839
3954
  }
3840
3955
 
3841
3956
  async #replaceSessionFileAfterEperm(tempPath: string, targetPath: string, renameError: unknown): Promise<void> {
@@ -3890,13 +4005,13 @@ export class SessionManager {
3890
4005
  }
3891
4006
  }
3892
4007
 
3893
- #replaceSessionFileSync(tempPath: string, targetPath: string): void {
4008
+ #replaceSessionFileSync(tempPath: string, targetPath: string): SessionFileReplacementSyncOutcome {
3894
4009
  try {
3895
4010
  this.storage.renameSync(tempPath, targetPath);
4011
+ return { kind: "replaced" };
3896
4012
  } catch (err) {
3897
4013
  if (hasFsCode(err, "EPERM")) {
3898
- this.#replaceSessionFileAfterEpermSync(tempPath, targetPath, err);
3899
- return;
4014
+ return this.#replaceSessionFileAfterEpermSync(tempPath, targetPath, err);
3900
4015
  }
3901
4016
 
3902
4017
  throw toError(err);
@@ -3913,7 +4028,8 @@ export class SessionManager {
3913
4028
  writer.writeSync(entry);
3914
4029
  }
3915
4030
  writer.closeSync();
3916
- this.#replaceSessionFileSync(tempPath, this.#sessionFile);
4031
+ const replacement = this.#replaceSessionFileSync(tempPath, this.#sessionFile);
4032
+ if (replacement.kind === "restored_previous") throw replacement.error;
3917
4033
  } catch (err) {
3918
4034
  // closeSync is now truthful and may throw; wrap the best-effort cleanup so
3919
4035
  // the original error (write/close failure) is the one surfaced, not the
@@ -3956,6 +4072,38 @@ export class SessionManager {
3956
4072
  }
3957
4073
  }
3958
4074
 
4075
+ async #writeStagedDefaultModelSelection(
4076
+ entries: readonly FileEntry[],
4077
+ sessionFile: string,
4078
+ ): Promise<string | undefined> {
4079
+ if (!this.storage.existsSync(sessionFile)) return undefined;
4080
+ const dir = path.resolve(sessionFile, "..");
4081
+ const tempPath = path.join(dir, `.${path.basename(sessionFile)}.${Snowflake.next()}.default-selection.tmp`);
4082
+ const writer = new NdjsonFileWriter(this.storage, tempPath, { flags: "w" });
4083
+ try {
4084
+ const persistedEntries = await Promise.all(
4085
+ materializeResidentEntriesForPersistenceSync([...entries], this.#residentBlobStores()).map(entry =>
4086
+ prepareEntryForPersistence(entry, this.#blobStore),
4087
+ ),
4088
+ );
4089
+ for (const entry of persistedEntries) {
4090
+ await writer.write(entry);
4091
+ }
4092
+ await writer.flush();
4093
+ await writer.fsync();
4094
+ await writer.close();
4095
+ return tempPath;
4096
+ } catch (error) {
4097
+ try {
4098
+ await writer.close();
4099
+ } catch {}
4100
+ try {
4101
+ await this.storage.unlink(tempPath);
4102
+ } catch {}
4103
+ throw toError(error);
4104
+ }
4105
+ }
4106
+
3959
4107
  async #rewriteFile(): Promise<void> {
3960
4108
  if (!this.persist || !this.#sessionFile) return;
3961
4109
  await this.#queuePersistTask(async () => {
@@ -3988,6 +4136,109 @@ export class SessionManager {
3988
4136
  return this.persist;
3989
4137
  }
3990
4138
 
4139
+ async stageDefaultModelSelection(
4140
+ model: string,
4141
+ thinkingLevel: string | undefined,
4142
+ options?: { readonly appendThinkingLevel: boolean },
4143
+ ): Promise<DefaultModelSelectionStage> {
4144
+ const entryRevision = this.#entryRevision;
4145
+ const headerExportRevision = this.#headerExportRevision;
4146
+ const sessionId = this.#sessionId;
4147
+ const sessionFile = this.#sessionFile;
4148
+ const entryIds = new Map(this.#byId);
4149
+ let parentId = this.#leafId;
4150
+ const entries = [...this.#fileEntries];
4151
+ const temporaryEntry: ModelChangeEntry = {
4152
+ type: "model_change",
4153
+ id: generateId(entryIds),
4154
+ parentId,
4155
+ timestamp: new Date().toISOString(),
4156
+ model,
4157
+ role: "temporary",
4158
+ };
4159
+ entryIds.set(temporaryEntry.id, temporaryEntry);
4160
+ entries.push(temporaryEntry);
4161
+ parentId = temporaryEntry.id;
4162
+ if (options?.appendThinkingLevel) {
4163
+ const thinkingEntry: ThinkingLevelChangeEntry = {
4164
+ type: "thinking_level_change",
4165
+ id: generateId(entryIds),
4166
+ parentId,
4167
+ timestamp: new Date().toISOString(),
4168
+ thinkingLevel: thinkingLevel ?? null,
4169
+ };
4170
+ entryIds.set(thinkingEntry.id, thinkingEntry);
4171
+ entries.push(thinkingEntry);
4172
+ parentId = thinkingEntry.id;
4173
+ }
4174
+ const modelEntry: ModelChangeEntry = {
4175
+ type: "model_change",
4176
+ id: generateId(entryIds),
4177
+ parentId,
4178
+ timestamp: new Date().toISOString(),
4179
+ model,
4180
+ role: "default",
4181
+ };
4182
+ entries.push(modelEntry);
4183
+ const persistsToExistingFile = this.persist && sessionFile !== undefined && this.storage.existsSync(sessionFile);
4184
+ const tempPath = persistsToExistingFile
4185
+ ? await this.#writeStagedDefaultModelSelection(entries, sessionFile)
4186
+ : undefined;
4187
+ return {
4188
+ entryRevision,
4189
+ headerExportRevision,
4190
+ sessionId,
4191
+ sessionFile,
4192
+ entries,
4193
+ tempPath,
4194
+ persistsToExistingFile,
4195
+ };
4196
+ }
4197
+
4198
+ promoteDefaultModelSelection(stage: DefaultModelSelectionStage): DefaultModelSelectionPromotion {
4199
+ if (
4200
+ stage.entryRevision !== this.#entryRevision ||
4201
+ stage.headerExportRevision !== this.#headerExportRevision ||
4202
+ stage.sessionId !== this.#sessionId ||
4203
+ stage.sessionFile !== this.#sessionFile
4204
+ ) {
4205
+ return { kind: "not_promoted" };
4206
+ }
4207
+ if (stage.persistsToExistingFile) {
4208
+ if (!stage.tempPath || !this.#sessionFile)
4209
+ return { kind: "unknown", error: new Error("Missing staged session replacement") };
4210
+ try {
4211
+ this.#closePersistWriterInternalSync();
4212
+ const replacement = this.#replaceSessionFileSync(stage.tempPath, this.#sessionFile);
4213
+ if (replacement.kind === "restored_previous") {
4214
+ return { kind: "not_promoted", error: replacement.error };
4215
+ }
4216
+ } catch (error) {
4217
+ if (this.#persistWriter?.getCloseState() === "close_failed_retryable") {
4218
+ return { kind: "not_promoted", error: new Error("Session replacement could not be completed.") };
4219
+ }
4220
+ return { kind: "unknown", error: toError(error) };
4221
+ }
4222
+ }
4223
+ this.#fileEntries = [...stage.entries];
4224
+ this.#resetResidentTextBlobStore();
4225
+ this.#reexternalizeFileEntriesForResidentStore();
4226
+ this.#needsFullRewriteOnNextPersist = false;
4227
+ this.#flushed = stage.persistsToExistingFile;
4228
+ this.#ensuredOnDisk = stage.persistsToExistingFile;
4229
+ this.#bumpAllRevisions();
4230
+ return { kind: "promoted" };
4231
+ }
4232
+
4233
+ async discardDefaultModelSelectionStage(stage: DefaultModelSelectionStage): Promise<void> {
4234
+ if (!stage.tempPath) return;
4235
+ try {
4236
+ await this.storage.unlink(stage.tempPath);
4237
+ } catch (error) {
4238
+ if (!isEnoent(error)) throw toError(error);
4239
+ }
4240
+ }
4241
+
3991
4242
  /**
3992
4243
  * Force-persist all current entries to disk, even when no assistant message exists yet.
3993
4244
  * Used by ACP mode where session/new must create a discoverable session immediately.
@@ -4396,6 +4647,24 @@ export class SessionManager {
4396
4647
  }
4397
4648
  }
4398
4649
 
4650
+ /** Append a configured fallback chain as child of current leaf, then advance leaf. Returns entry id. */
4651
+ appendConfiguredModelChain(chain: ConfiguredModelChain): string {
4652
+ const entry: ConfiguredModelChainEntry = {
4653
+ type: "configured_model_chain",
4654
+ id: generateId(this.#byId),
4655
+ parentId: this.#leafId,
4656
+ timestamp: new Date().toISOString(),
4657
+ role: chain.role,
4658
+ entries: [...chain.entries],
4659
+ origin: chain.origin,
4660
+ identity: chain.identity,
4661
+ explicitHead: chain.explicitHead,
4662
+ cleared: chain.cleared,
4663
+ };
4664
+ this.#appendEntry(entry);
4665
+ return entry.id;
4666
+ }
4667
+
4399
4668
  /** Append a message as child of current leaf, then advance leaf. Returns entry id.
4400
4669
  * Does not allow writing CompactionSummaryMessage and BranchSummaryMessage directly.
4401
4670
  * Reason: we want these to be top-level entries in the session, not message session entries,
@@ -5429,6 +5698,11 @@ export class SessionManager {
5429
5698
  return computeDefaultSessionDir(cwd, storage, getSessionsDir(agentDir));
5430
5699
  }
5431
5700
 
5701
+ /** Resolve the default session directory without creating or migrating storage. */
5702
+ static getDefaultSessionDirReadOnly(cwd: string, agentDir?: string): string {
5703
+ const { encodedDirName } = getDefaultSessionDirName(cwd);
5704
+ return path.join(getSessionsDir(agentDir), encodedDirName);
5705
+ }
5432
5706
  /**
5433
5707
  * Create a new session.
5434
5708
  * @param cwd Working directory (stored in session header)
@@ -5520,6 +5794,159 @@ export class SessionManager {
5520
5794
  }
5521
5795
  }
5522
5796
 
5797
+ /** Capture exact source content for a strict fork without granting write ownership. */
5798
+ static captureTranscriptStrict(
5799
+ filePath: string,
5800
+ storage: SessionStorage = new FileSessionStorage(),
5801
+ ): StrictSessionCaptureResult {
5802
+ const inspected = inspectResumeSessionFile(filePath, storage);
5803
+ if ("kind" in inspected) return inspected;
5804
+ return {
5805
+ kind: "captured",
5806
+ snapshot: {
5807
+ sourcePath: path.resolve(filePath),
5808
+ identity: inspected.identity,
5809
+ content: Uint8Array.from(inspected.content),
5810
+ storage,
5811
+ },
5812
+ };
5813
+ }
5814
+
5815
+ /**
5816
+ * Fork strictly from captured source bytes. The source pathname is used only to
5817
+ * revalidate captured authority before destination initialization and transcript
5818
+ * persistence; destination history always comes from the captured bytes.
5819
+ */
5820
+ static async forkFromCaptured(
5821
+ snapshot: CapturedSessionTranscriptSnapshot,
5822
+ cwd: string,
5823
+ sessionDir?: string,
5824
+ ): Promise<StrictSessionForkResult> {
5825
+ if (crypto.createHash("sha256").update(snapshot.content).digest("hex") !== snapshot.identity.sha256) {
5826
+ return { kind: "error", reason: "identity-mismatch" };
5827
+ }
5828
+
5829
+ let forkEntries: FileEntry[] = [];
5830
+ try {
5831
+ const content = new TextDecoder("utf-8", { fatal: true }).decode(snapshot.content);
5832
+ for (const line of content.split(/\r?\n/)) {
5833
+ if (line.length > 0) JSON.parse(line);
5834
+ }
5835
+ forkEntries = parseSessionEntries(content);
5836
+ const sourceHeader = forkEntries[0] as SessionHeader | undefined;
5837
+ if (sourceHeader?.type !== "session" || sourceHeader.id !== snapshot.identity.sessionId)
5838
+ return { kind: "error", reason: "identity-mismatch" };
5839
+ migrateToCurrentVersion(forkEntries);
5840
+ if (!hasStrictSessionSchema(forkEntries)) return { kind: "error", reason: "malformed" };
5841
+ } catch {
5842
+ return { kind: "error", reason: "malformed" };
5843
+ }
5844
+
5845
+ const revalidated = inspectResumeSessionFile(snapshot.sourcePath, snapshot.storage);
5846
+ if ("kind" in revalidated) return revalidated;
5847
+ if (!sameResumeIdentity(snapshot.identity, revalidated.identity)) {
5848
+ return { kind: "error", reason: "identity-mismatch" };
5849
+ }
5850
+
5851
+ const dir = sessionDir ?? SessionManager.getDefaultSessionDir(cwd, undefined, snapshot.storage);
5852
+ const removeSessionDirOnFailure =
5853
+ snapshot.storage instanceof FileSessionStorage && !snapshot.storage.existsSync(dir);
5854
+ let manager: SessionManager | undefined;
5855
+ let authorityFailure: StrictSessionOpenFailure | undefined;
5856
+ try {
5857
+ manager = new SessionManager(cwd, dir, true, snapshot.storage);
5858
+ await resolveBlobRefsInEntries(forkEntries, manager.#blobStore);
5859
+ manager.#fileEntries = forkEntries;
5860
+ const sourceHeader = manager.#fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
5861
+ const historyEntries = manager.#fileEntries.filter(entry => entry.type !== "session") as SessionEntry[];
5862
+ manager.#newSessionSync({ parentSession: sourceHeader?.id }, false);
5863
+ manager.#resetResidentTextBlobStore();
5864
+ const newHeader = manager.#fileEntries[0] as SessionHeader;
5865
+ newHeader.title = sourceHeader?.title;
5866
+ newHeader.titleSource = sourceHeader?.titleSource;
5867
+ const residentBlobStores = manager.#residentBlobStores();
5868
+ manager.#fileEntries = [
5869
+ newHeader,
5870
+ ...historyEntries.map(entry => prepareEntryForResidentSync(entry, residentBlobStores) as SessionEntry),
5871
+ ];
5872
+ manager.#sessionName = newHeader.title;
5873
+ manager.#titleSource = newHeader.titleSource;
5874
+ manager.sanitizeLoadedOpenAIResponsesReplayMetadata();
5875
+ manager.#buildIndex();
5876
+ manager.#bumpAllRevisions();
5877
+ const beforeWrite = inspectResumeSessionFile(snapshot.sourcePath, snapshot.storage);
5878
+ if ("kind" in beforeWrite) {
5879
+ authorityFailure = beforeWrite;
5880
+ throw new Error("Captured fork source authority changed before destination write.");
5881
+ }
5882
+ if (!sameResumeIdentity(snapshot.identity, beforeWrite.identity)) {
5883
+ authorityFailure = { kind: "error", reason: "identity-mismatch" };
5884
+ throw new Error("Captured fork source authority changed before destination write.");
5885
+ }
5886
+ await manager.#rewriteFile();
5887
+ if (manager.#sessionFile) writeTerminalBreadcrumb(manager.cwd, manager.#sessionFile);
5888
+ return { kind: "forked", manager };
5889
+ } catch (error) {
5890
+ if (manager) {
5891
+ const sessionFile = manager.#sessionFile;
5892
+ const cleanupErrors: Error[] = [];
5893
+ try {
5894
+ await manager.close();
5895
+ } catch (cleanupError) {
5896
+ cleanupErrors.push(toError(cleanupError));
5897
+ }
5898
+ if (sessionFile) {
5899
+ try {
5900
+ await manager.storage.unlink(sessionFile);
5901
+ } catch (cleanupError) {
5902
+ if (!isEnoent(cleanupError)) cleanupErrors.push(toError(cleanupError));
5903
+ }
5904
+ if (manager.storage instanceof FileSessionStorage) {
5905
+ try {
5906
+ await fs.promises.rm(sessionFile.slice(0, -6), { recursive: true, force: true });
5907
+ } catch (cleanupError) {
5908
+ cleanupErrors.push(toError(cleanupError));
5909
+ }
5910
+ try {
5911
+ const sessionFileName = path.basename(sessionFile);
5912
+ const transientPrefix = `.${sessionFileName}.`;
5913
+ const backupPrefix = `${sessionFileName}.`;
5914
+ for (const name of await fs.promises.readdir(path.dirname(sessionFile))) {
5915
+ if (
5916
+ (name.startsWith(transientPrefix) && name.endsWith(".tmp")) ||
5917
+ (name.startsWith(backupPrefix) && name.endsWith(".bak"))
5918
+ ) {
5919
+ await fs.promises.rm(path.join(path.dirname(sessionFile), name), {
5920
+ recursive: true,
5921
+ force: true,
5922
+ });
5923
+ }
5924
+ }
5925
+ } catch (cleanupError) {
5926
+ if (!isEnoent(cleanupError)) cleanupErrors.push(toError(cleanupError));
5927
+ }
5928
+ }
5929
+ }
5930
+ if (removeSessionDirOnFailure) {
5931
+ try {
5932
+ await fs.promises.rmdir(dir);
5933
+ } catch (cleanupError) {
5934
+ const code = (cleanupError as NodeJS.ErrnoException).code;
5935
+ if (code !== "ENOENT" && code !== "ENOTEMPTY" && code !== "EEXIST")
5936
+ cleanupErrors.push(toError(cleanupError));
5937
+ }
5938
+ }
5939
+ if (cleanupErrors.length > 0) {
5940
+ throw new Error(`Failed to clean up fork destination: ${cleanupErrors[0]!.message}`, {
5941
+ cause: toError(error),
5942
+ });
5943
+ }
5944
+ if (authorityFailure) return authorityFailure;
5945
+ }
5946
+ throw error;
5947
+ }
5948
+ }
5949
+
5523
5950
  /** Inspect a selected session without acquiring write-capable ownership. */
5524
5951
  static async inspectSessionTailReadOnly(
5525
5952
  filePath: string,
@@ -4,6 +4,13 @@ import * as path from "node:path";
4
4
  import { isEnoent, pathIsWithin, peekFile, toError } from "@gajae-code/utils";
5
5
 
6
6
  const utf8Decoder = new TextDecoder("utf-8");
7
+ function canonicalPathSync(value: string): string {
8
+ try {
9
+ return fs.realpathSync.native(value);
10
+ } catch {
11
+ return path.resolve(value);
12
+ }
13
+ }
7
14
 
8
15
  export interface SessionStorageStat {
9
16
  dev: bigint;
@@ -614,7 +621,7 @@ export class FileSessionStorage implements SessionStorage {
614
621
  if (typeof header.cwd !== "string") {
615
622
  throw new SessionDeleteVerificationError("cwd", "Transcript header is missing a cwd");
616
623
  }
617
- if (path.resolve(header.cwd) !== path.resolve(expectedCwd)) {
624
+ if (canonicalPathSync(header.cwd) !== canonicalPathSync(expectedCwd)) {
618
625
  throw new SessionDeleteVerificationError("cwd", "Transcript header cwd does not match authorization");
619
626
  }
620
627
  return { snapshot };