@gajae-code/coding-agent 0.10.1 → 0.11.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 (474) hide show
  1. package/CHANGELOG.md +70 -2
  2. package/dist/types/append-only-mode.d.ts +1 -1
  3. package/dist/types/cli/args.d.ts +4 -5
  4. package/dist/types/cli/daemon-cli.d.ts +12 -1
  5. package/dist/types/cli/notify-cli.d.ts +21 -1
  6. package/dist/types/cli.d.ts +3 -0
  7. package/dist/types/commands/daemon.d.ts +39 -0
  8. package/dist/types/commands/launch.d.ts +3 -0
  9. package/dist/types/commands/notify.d.ts +28 -1
  10. package/dist/types/commands/sdk.d.ts +33 -0
  11. package/dist/types/config/atomic-yaml-patch.d.ts +85 -0
  12. package/dist/types/config/model-profile-activation.d.ts +35 -15
  13. package/dist/types/config/model-profiles.d.ts +11 -5
  14. package/dist/types/config/model-registry.d.ts +3 -3
  15. package/dist/types/config/model-resolver.d.ts +39 -6
  16. package/dist/types/config/model-selector-value.d.ts +17 -0
  17. package/dist/types/config/models-config-schema.d.ts +10 -6
  18. package/dist/types/config/settings-schema.d.ts +133 -6
  19. package/dist/types/config/settings.d.ts +63 -35
  20. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -6
  21. package/dist/types/coordinator-mcp/server.d.ts +6 -49
  22. package/dist/types/daemon/builtin.d.ts +6 -5
  23. package/dist/types/daemon/control-types.d.ts +1 -1
  24. package/dist/types/discovery/mcp-json.d.ts +12 -1
  25. package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
  26. package/dist/types/extensibility/extensions/compact-handler.d.ts +4 -2
  27. package/dist/types/extensibility/extensions/get-commands-handler.d.ts +2 -2
  28. package/dist/types/extensibility/extensions/loader.d.ts +10 -1
  29. package/dist/types/extensibility/extensions/runner.d.ts +3 -3
  30. package/dist/types/extensibility/extensions/types.d.ts +141 -16
  31. package/dist/types/extensibility/hooks/types.d.ts +1 -1
  32. package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -2
  33. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
  34. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +3 -0
  35. package/dist/types/gjc-runtime/team-runtime.d.ts +3 -4
  36. package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +13 -0
  37. package/dist/types/gjc-runtime/tmux-sessions.d.ts +4 -1
  38. package/dist/types/gjc-runtime/windows-powershell-command.d.ts +9 -0
  39. package/dist/types/harness-control-plane/operate.d.ts +5 -5
  40. package/dist/types/harness-control-plane/owner.d.ts +18 -4
  41. package/dist/types/harness-control-plane/receipts.d.ts +0 -1
  42. package/dist/types/harness-control-plane/sdk-transport.d.ts +43 -0
  43. package/dist/types/harness-control-plane/session-transport.d.ts +48 -0
  44. package/dist/types/harness-control-plane/types.d.ts +3 -4
  45. package/dist/types/lsp/index.d.ts +1 -1
  46. package/dist/types/main.d.ts +32 -38
  47. package/dist/types/modes/acp/acp-agent.d.ts +224 -53
  48. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -0
  49. package/dist/types/modes/acp/acp-mode.d.ts +6 -15
  50. package/dist/types/modes/acp/permission-mode.d.ts +4 -0
  51. package/dist/types/modes/acp/startup-options.d.ts +6 -0
  52. package/dist/types/modes/components/agent-dashboard.d.ts +3 -0
  53. package/dist/types/modes/components/gajae-pet-widget.d.ts +1 -0
  54. package/dist/types/modes/components/model-selector.d.ts +1 -0
  55. package/dist/types/modes/components/notifications-settings-editor.d.ts +135 -0
  56. package/dist/types/modes/components/pet-capability.d.ts +37 -0
  57. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  58. package/dist/types/modes/components/settings-selector.d.ts +15 -4
  59. package/dist/types/modes/components/welcome.d.ts +1 -1
  60. package/dist/types/modes/controllers/selector-controller.d.ts +35 -1
  61. package/dist/types/modes/index.d.ts +0 -4
  62. package/dist/types/modes/interactive-mode.d.ts +5 -1
  63. package/dist/types/modes/runtime-init.d.ts +2 -2
  64. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +6 -9
  65. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -6
  66. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +6 -6
  67. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +4 -9
  68. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +1 -1
  69. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +169 -30
  70. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +7 -7
  71. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +105 -0
  72. package/dist/types/modes/types.d.ts +16 -1
  73. package/dist/types/runtime-mcp/client.d.ts +2 -0
  74. package/dist/types/runtime-mcp/config.d.ts +5 -1
  75. package/dist/types/runtime-mcp/manager.d.ts +8 -1
  76. package/dist/types/runtime-mcp/types.d.ts +4 -0
  77. package/dist/types/sdk/acp/adapter.d.ts +67 -0
  78. package/dist/types/sdk/acp/index.d.ts +2 -0
  79. package/dist/types/sdk/broker/broker.d.ts +53 -0
  80. package/dist/types/sdk/broker/discovery.d.ts +30 -0
  81. package/dist/types/sdk/broker/ensure.d.ts +33 -0
  82. package/dist/types/sdk/broker/identity.d.ts +3 -0
  83. package/dist/types/sdk/broker/index.d.ts +8 -0
  84. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +95 -0
  85. package/dist/types/sdk/broker/lifecycle.d.ts +85 -0
  86. package/dist/types/sdk/broker/process-incarnation.d.ts +14 -0
  87. package/dist/types/sdk/broker/runtime.d.ts +33 -0
  88. package/dist/types/sdk/broker/session-index.d.ts +57 -0
  89. package/dist/types/sdk/broker/state-version.d.ts +8 -0
  90. package/dist/types/sdk/broker/transport.d.ts +9 -0
  91. package/dist/types/sdk/bus/chat-command-policy.d.ts +49 -0
  92. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +21 -0
  93. package/dist/types/sdk/bus/chat-daemon-control.d.ts +111 -0
  94. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +77 -0
  95. package/dist/types/sdk/bus/chat-effect-journal.d.ts +84 -0
  96. package/dist/types/{notifications → sdk/bus}/config-commands.d.ts +7 -2
  97. package/dist/types/sdk/bus/config.d.ts +274 -0
  98. package/dist/types/sdk/bus/conversation-store.d.ts +72 -0
  99. package/dist/types/sdk/bus/discord-conversation.d.ts +58 -0
  100. package/dist/types/sdk/bus/discord-daemon.d.ts +45 -0
  101. package/dist/types/sdk/bus/discord-live-provider.d.ts +70 -0
  102. package/dist/types/sdk/bus/discord-provider.d.ts +85 -0
  103. package/dist/types/{notifications → sdk/bus}/html-format.d.ts +2 -2
  104. package/dist/types/{notifications → sdk/bus}/index.d.ts +113 -13
  105. package/dist/types/{notifications → sdk/bus}/lifecycle-control-runtime.d.ts +4 -2
  106. package/dist/types/{notifications → sdk/bus}/lifecycle-orchestrator.d.ts +18 -21
  107. package/dist/types/{notifications → sdk/bus}/managed-daemon.d.ts +1 -1
  108. package/dist/types/sdk/bus/native-runtime-compatibility.d.ts +13 -0
  109. package/dist/types/sdk/bus/notification-orchestration.d.ts +170 -0
  110. package/dist/types/{notifications → sdk/bus}/notification-service.d.ts +7 -1
  111. package/dist/types/sdk/bus/session-control.d.ts +83 -0
  112. package/dist/types/sdk/bus/slack-conversation.d.ts +56 -0
  113. package/dist/types/sdk/bus/slack-daemon.d.ts +60 -0
  114. package/dist/types/sdk/bus/slack-live-provider.d.ts +59 -0
  115. package/dist/types/sdk/bus/slack-provider.d.ts +55 -0
  116. package/dist/types/{notifications → sdk/bus}/telegram-cli.d.ts +4 -4
  117. package/dist/types/{notifications → sdk/bus}/telegram-daemon-cli.d.ts +14 -5
  118. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -0
  119. package/dist/types/{notifications → sdk/bus}/telegram-daemon-control.d.ts +2 -2
  120. package/dist/types/{notifications → sdk/bus}/telegram-daemon.d.ts +55 -17
  121. package/dist/types/{notifications → sdk/bus}/telegram-reference.d.ts +2 -2
  122. package/dist/types/sdk/bus/telegram-setup.d.ts +128 -0
  123. package/dist/types/{notifications → sdk/bus}/topic-registry.d.ts +11 -0
  124. package/dist/types/sdk/cli/index.d.ts +1 -0
  125. package/dist/types/sdk/cli/session-cli.d.ts +19 -0
  126. package/dist/types/sdk/client/client.d.ts +1 -0
  127. package/dist/types/sdk/client/discovery.d.ts +27 -0
  128. package/dist/types/sdk/client/index.d.ts +2 -0
  129. package/dist/types/sdk/host/control/dispatch.d.ts +34 -0
  130. package/dist/types/sdk/host/control/index.d.ts +2 -0
  131. package/dist/types/sdk/host/control/operations.d.ts +65 -0
  132. package/dist/types/sdk/host/events.d.ts +35 -0
  133. package/dist/types/sdk/host/host.d.ts +35 -0
  134. package/dist/types/sdk/host/index.d.ts +4 -0
  135. package/dist/types/sdk/host/query/cursor.d.ts +62 -0
  136. package/dist/types/sdk/host/query/handlers.d.ts +70 -0
  137. package/dist/types/sdk/host/query/index.d.ts +3 -0
  138. package/dist/types/sdk/host/query/revision-store.d.ts +60 -0
  139. package/dist/types/sdk/host/reverse-leases.d.ts +45 -0
  140. package/dist/types/sdk/host/types.d.ts +32 -0
  141. package/dist/types/sdk/index.d.ts +6 -0
  142. package/dist/types/sdk/lifecycle-session.d.ts +14 -0
  143. package/dist/types/sdk/mcp/index.d.ts +1 -0
  144. package/dist/types/sdk/mcp/server.d.ts +37 -0
  145. package/dist/types/sdk/models.d.ts +44 -0
  146. package/dist/types/sdk/protocol/adapter-validation.d.ts +14 -0
  147. package/dist/types/sdk/protocol/operation-registry.d.ts +24 -0
  148. package/dist/types/{sdk.d.ts → sdk/session.d.ts} +44 -38
  149. package/dist/types/sdk/startup-capability.d.ts +50 -0
  150. package/dist/types/session/agent-session.d.ts +114 -21
  151. package/dist/types/session/default-model-selection.d.ts +25 -0
  152. package/dist/types/session/fallback-chain-controller.d.ts +52 -0
  153. package/dist/types/session/session-manager.d.ts +58 -1
  154. package/dist/types/setup/credential-auto-import.d.ts +36 -6
  155. package/dist/types/tools/ask-answer-registry.d.ts +1 -1
  156. package/dist/types/tools/browser/benchmark/detector-report.d.ts +63 -0
  157. package/dist/types/tools/browser/benchmark/run-suite.d.ts +18 -0
  158. package/dist/types/tools/browser/launch.d.ts +22 -0
  159. package/dist/types/tools/browser/profile-discovery.d.ts +31 -0
  160. package/dist/types/tools/browser/profile-posture.d.ts +37 -0
  161. package/dist/types/tools/browser/profile-reuse.d.ts +39 -0
  162. package/dist/types/tools/browser/profile-warmup.d.ts +39 -0
  163. package/dist/types/tools/browser/registry.d.ts +19 -0
  164. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  165. package/dist/types/tools/index.d.ts +8 -8
  166. package/package.json +34 -13
  167. package/scripts/build-sdk-package-smoke.ts +94 -0
  168. package/scripts/capture-notifications-settings-showcase.ts +435 -0
  169. package/scripts/compile-args.ts +4 -2
  170. package/scripts/g005-daemon-smoke.ts +4 -5
  171. package/scripts/g011-daemon-path-smoke.ts +5 -5
  172. package/scripts/generate-sdk-adapter-parity-manifest.ts +163 -0
  173. package/scripts/generate-sdk-operation-inventory.ts +867 -0
  174. package/scripts/generate-telegram-baseline-manifest.ts +128 -0
  175. package/scripts/manifest-schema.ts +124 -0
  176. package/scripts/run-test-manifest.ts +153 -0
  177. package/scripts/run-threat-model-check.ts +157 -0
  178. package/scripts/verify-gjc-sdk-canonicalization.ts +3467 -0
  179. package/scripts/verify-gjc-sdk-rename.ts +90 -0
  180. package/src/append-only-mode.ts +1 -1
  181. package/src/cli/args.ts +43 -12
  182. package/src/cli/auth-gateway-cli.ts +2 -2
  183. package/src/cli/config-cli.ts +2 -1
  184. package/src/cli/daemon-cli.ts +63 -14
  185. package/src/cli/list-models.ts +1 -1
  186. package/src/cli/notify-cli.ts +288 -286
  187. package/src/cli.ts +21 -2
  188. package/src/commands/daemon.ts +50 -6
  189. package/src/commands/harness.ts +93 -36
  190. package/src/commands/launch.ts +5 -2
  191. package/src/commands/mcp-serve.ts +19 -12
  192. package/src/commands/notify.ts +36 -3
  193. package/src/commands/sdk.ts +426 -0
  194. package/src/commit/agentic/agent.ts +1 -0
  195. package/src/config/atomic-yaml-patch.ts +423 -0
  196. package/src/config/model-profile-activation.ts +240 -89
  197. package/src/config/model-profiles.ts +37 -34
  198. package/src/config/model-registry.ts +51 -26
  199. package/src/config/model-resolver.ts +138 -51
  200. package/src/config/model-selector-value.ts +50 -0
  201. package/src/config/models-config-schema.ts +14 -21
  202. package/src/config/settings-schema.ts +128 -10
  203. package/src/config/settings.ts +1120 -158
  204. package/src/coordinator-mcp/model-preset.ts +74 -20
  205. package/src/coordinator-mcp/server.ts +1597 -3052
  206. package/src/daemon/builtin.ts +8 -5
  207. package/src/daemon/control-types.ts +1 -1
  208. package/src/defaults/gjc/skills/team/SKILL.md +4 -4
  209. package/src/discovery/mcp-json.ts +327 -20
  210. package/src/extensibility/custom-tools/types.ts +1 -1
  211. package/src/extensibility/extensions/compact-handler.ts +3 -3
  212. package/src/extensibility/extensions/get-commands-handler.ts +2 -2
  213. package/src/extensibility/extensions/loader.ts +69 -5
  214. package/src/extensibility/extensions/runner.ts +112 -4
  215. package/src/extensibility/extensions/types.ts +176 -17
  216. package/src/extensibility/hooks/types.ts +1 -1
  217. package/src/extensibility/slash-commands.ts +3 -3
  218. package/src/gjc-runtime/launch-tmux.ts +170 -86
  219. package/src/gjc-runtime/psmux-detect.ts +82 -4
  220. package/src/gjc-runtime/session-state-sidecar.ts +41 -31
  221. package/src/gjc-runtime/team-runtime.ts +54 -35
  222. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +53 -17
  223. package/src/gjc-runtime/tmux-owner-isolation.ts +5 -2
  224. package/src/gjc-runtime/tmux-sessions.ts +19 -13
  225. package/src/gjc-runtime/ultragoal-guard.ts +53 -6
  226. package/src/gjc-runtime/ultragoal-runtime.ts +155 -3
  227. package/src/gjc-runtime/windows-powershell-command.ts +58 -0
  228. package/src/harness-control-plane/operate.ts +9 -9
  229. package/src/harness-control-plane/owner.ts +283 -39
  230. package/src/harness-control-plane/receipts.ts +0 -1
  231. package/src/harness-control-plane/sdk-transport.ts +395 -0
  232. package/src/harness-control-plane/session-transport.ts +94 -0
  233. package/src/harness-control-plane/types.ts +1 -1
  234. package/src/internal-urls/docs-index.generated.ts +36 -37
  235. package/src/lsp/client.ts +42 -6
  236. package/src/lsp/index.ts +2 -2
  237. package/src/main.ts +299 -204
  238. package/src/modes/DESIGN.md +258 -0
  239. package/src/modes/acp/acp-agent.ts +1228 -2612
  240. package/src/modes/acp/acp-client-bridge.ts +2 -20
  241. package/src/modes/acp/acp-event-mapper.ts +18 -2
  242. package/src/modes/acp/acp-mode.ts +8 -36
  243. package/src/modes/acp/permission-mode.ts +25 -0
  244. package/src/modes/acp/startup-options.ts +6 -0
  245. package/src/modes/components/agent-dashboard.ts +37 -17
  246. package/src/modes/components/custom-editor.ts +1 -0
  247. package/src/modes/components/gajae-pet-widget.ts +169 -20
  248. package/src/modes/components/model-selector.ts +93 -35
  249. package/src/modes/components/notifications-settings-editor.ts +1379 -0
  250. package/src/modes/components/pet-capability.ts +115 -0
  251. package/src/modes/components/pet-selector.ts +4 -6
  252. package/src/modes/components/settings-selector.ts +86 -11
  253. package/src/modes/components/welcome.ts +4 -4
  254. package/src/modes/controllers/command-controller.ts +1 -2
  255. package/src/modes/controllers/event-controller.ts +11 -16
  256. package/src/modes/controllers/extension-ui-controller.ts +357 -12
  257. package/src/modes/controllers/input-controller.ts +43 -13
  258. package/src/modes/controllers/selector-controller.ts +671 -44
  259. package/src/modes/index.ts +0 -22
  260. package/src/modes/interactive-mode.ts +108 -24
  261. package/src/modes/runtime-init.ts +298 -6
  262. package/src/modes/shared/agent-wire/approval-gate.ts +9 -11
  263. package/src/modes/shared/agent-wire/deep-interview-gate.ts +10 -11
  264. package/src/modes/shared/agent-wire/event-contract.ts +7 -8
  265. package/src/modes/shared/agent-wire/event-envelope.ts +9 -17
  266. package/src/modes/shared/agent-wire/event-observation.ts +16 -7
  267. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +1254 -87
  268. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +15 -15
  269. package/src/modes/shared/agent-wire/workflow-gate-types.ts +113 -0
  270. package/src/modes/types.ts +16 -1
  271. package/src/runtime-mcp/client.ts +94 -19
  272. package/src/runtime-mcp/config.ts +27 -15
  273. package/src/runtime-mcp/manager.ts +343 -105
  274. package/src/runtime-mcp/oauth-flow.ts +42 -12
  275. package/src/runtime-mcp/transports/http.ts +49 -19
  276. package/src/runtime-mcp/transports/stdio.ts +47 -28
  277. package/src/runtime-mcp/types.ts +10 -0
  278. package/src/sdk/acp/adapter.ts +418 -0
  279. package/src/sdk/acp/index.ts +2 -0
  280. package/src/sdk/broker/broker.ts +642 -0
  281. package/src/sdk/broker/discovery.ts +94 -0
  282. package/src/sdk/broker/ensure.ts +249 -0
  283. package/src/sdk/broker/identity.ts +42 -0
  284. package/src/sdk/broker/index.ts +8 -0
  285. package/src/sdk/broker/internal-source-marker-2178.txt +1 -0
  286. package/src/sdk/broker/internal-source.bunfig.toml +1 -0
  287. package/src/sdk/broker/lifecycle-ledger.ts +286 -0
  288. package/src/sdk/broker/lifecycle.ts +2157 -0
  289. package/src/sdk/broker/process-incarnation.ts +159 -0
  290. package/src/sdk/broker/runtime.ts +164 -0
  291. package/src/sdk/broker/session-index.ts +318 -0
  292. package/src/sdk/broker/state-version.ts +23 -0
  293. package/src/sdk/broker/transport.ts +125 -0
  294. package/src/sdk/bus/chat-command-policy.ts +107 -0
  295. package/src/sdk/bus/chat-daemon-cli.ts +214 -0
  296. package/src/sdk/bus/chat-daemon-control.ts +713 -0
  297. package/src/sdk/bus/chat-daemon-runtime.ts +526 -0
  298. package/src/sdk/bus/chat-effect-journal.ts +353 -0
  299. package/src/{notifications → sdk/bus}/config-commands.ts +39 -10
  300. package/src/sdk/bus/config.ts +433 -0
  301. package/src/sdk/bus/conversation-store.ts +391 -0
  302. package/src/sdk/bus/discord-conversation.ts +85 -0
  303. package/src/sdk/bus/discord-daemon.ts +2275 -0
  304. package/src/sdk/bus/discord-live-provider.ts +521 -0
  305. package/src/sdk/bus/discord-provider.ts +56 -0
  306. package/src/{notifications → sdk/bus}/html-format.ts +2 -2
  307. package/src/sdk/bus/index.ts +3943 -0
  308. package/src/{notifications → sdk/bus}/lifecycle-commands.ts +2 -0
  309. package/src/{notifications → sdk/bus}/lifecycle-control-runtime.ts +225 -196
  310. package/src/{notifications → sdk/bus}/lifecycle-orchestrator.ts +161 -74
  311. package/src/{notifications → sdk/bus}/managed-daemon.ts +2 -2
  312. package/src/sdk/bus/native-runtime-compatibility.ts +42 -0
  313. package/src/sdk/bus/natives-augment.d.ts +38 -0
  314. package/src/sdk/bus/notification-orchestration.ts +535 -0
  315. package/src/{notifications → sdk/bus}/notification-service.ts +106 -12
  316. package/src/sdk/bus/session-control.ts +333 -0
  317. package/src/sdk/bus/slack-conversation.ts +74 -0
  318. package/src/sdk/bus/slack-daemon.ts +2144 -0
  319. package/src/sdk/bus/slack-live-provider.ts +380 -0
  320. package/src/sdk/bus/slack-provider.ts +76 -0
  321. package/src/{notifications → sdk/bus}/telegram-cli.ts +8 -8
  322. package/src/sdk/bus/telegram-daemon-cli.ts +330 -0
  323. package/src/sdk/bus/telegram-daemon-contract.ts +14 -0
  324. package/src/{notifications → sdk/bus}/telegram-daemon-control.ts +4 -4
  325. package/src/{notifications → sdk/bus}/telegram-daemon.ts +502 -54
  326. package/src/{notifications → sdk/bus}/telegram-reference.ts +2 -2
  327. package/src/sdk/bus/telegram-setup.ts +663 -0
  328. package/src/{notifications → sdk/bus}/topic-registry.ts +32 -0
  329. package/src/sdk/cli/index.ts +1 -0
  330. package/src/sdk/cli/session-cli.ts +275 -0
  331. package/src/sdk/client/client.ts +1 -0
  332. package/src/sdk/client/discovery.ts +108 -0
  333. package/src/sdk/client/index.ts +2 -0
  334. package/src/sdk/host/control/dispatch.ts +367 -0
  335. package/src/sdk/host/control/index.ts +10 -0
  336. package/src/sdk/host/control/operations.ts +63 -0
  337. package/src/sdk/host/events.ts +91 -0
  338. package/src/sdk/host/host.ts +296 -0
  339. package/src/sdk/host/index.ts +4 -0
  340. package/src/sdk/host/query/cursor.ts +189 -0
  341. package/src/sdk/host/query/handlers.ts +533 -0
  342. package/src/sdk/host/query/index.ts +3 -0
  343. package/src/sdk/host/query/revision-store.ts +1126 -0
  344. package/src/sdk/host/reverse-leases.ts +325 -0
  345. package/src/sdk/host/types.ts +29 -0
  346. package/src/sdk/index.ts +13 -0
  347. package/src/sdk/lifecycle-session.ts +39 -0
  348. package/src/sdk/mcp/index.ts +1 -0
  349. package/src/sdk/mcp/server.ts +332 -0
  350. package/src/sdk/models.ts +168 -0
  351. package/src/sdk/protocol/adapter-validation.ts +48 -0
  352. package/src/sdk/protocol/operation-inventory.generated.json +5654 -0
  353. package/src/sdk/protocol/operation-registry.ts +312 -0
  354. package/src/{sdk.ts → sdk/session.ts} +339 -212
  355. package/src/sdk/startup-capability.ts +189 -0
  356. package/src/session/agent-session.ts +2470 -866
  357. package/src/session/default-model-selection.ts +93 -0
  358. package/src/session/fallback-chain-controller.ts +136 -0
  359. package/src/session/session-manager.ts +447 -13
  360. package/src/session/session-storage.ts +8 -1
  361. package/src/setup/credential-auto-import.ts +290 -35
  362. package/src/setup/hermes/templates/operator-instructions.v1.md +3 -5
  363. package/src/slash-commands/builtin-registry.ts +33 -30
  364. package/src/task/executor.ts +62 -13
  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/telegram-send.ts +2 -2
  384. package/vendor/insane-search/MANIFEST.json +2 -1
  385. package/vendor/insane-search/engine/bias_check.py +4 -1
  386. package/vendor/insane-search/engine/phase0.py +26 -1
  387. package/dist/types/harness-control-plane/rpc-adapter.d.ts +0 -69
  388. package/dist/types/modes/bridge/auth.d.ts +0 -12
  389. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +0 -9
  390. package/dist/types/modes/bridge/bridge-mode.d.ts +0 -47
  391. package/dist/types/modes/bridge/bridge-ui-context.d.ts +0 -88
  392. package/dist/types/modes/bridge/event-stream.d.ts +0 -8
  393. package/dist/types/modes/rpc/host-tools.d.ts +0 -1
  394. package/dist/types/modes/rpc/host-uris.d.ts +0 -1
  395. package/dist/types/modes/rpc/rpc-client.d.ts +0 -299
  396. package/dist/types/modes/rpc/rpc-mode.d.ts +0 -86
  397. package/dist/types/modes/rpc/rpc-socket-security.d.ts +0 -10
  398. package/dist/types/modes/rpc/rpc-types.d.ts +0 -810
  399. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +0 -18
  400. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +0 -37
  401. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +0 -2
  402. package/dist/types/modes/shared/agent-wire/handshake.d.ts +0 -56
  403. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +0 -16
  404. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +0 -17
  405. package/dist/types/modes/shared/agent-wire/protocol.d.ts +0 -25
  406. package/dist/types/modes/shared/agent-wire/responses.d.ts +0 -4
  407. package/dist/types/modes/shared/agent-wire/scopes.d.ts +0 -18
  408. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +0 -25
  409. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +0 -42
  410. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +0 -27
  411. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +0 -29
  412. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +0 -68
  413. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +0 -166
  414. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +0 -127
  415. package/dist/types/notifications/config.d.ts +0 -132
  416. package/src/harness-control-plane/rpc-adapter.ts +0 -290
  417. package/src/modes/bridge/auth.ts +0 -41
  418. package/src/modes/bridge/bridge-client-bridge.ts +0 -47
  419. package/src/modes/bridge/bridge-mode.ts +0 -675
  420. package/src/modes/bridge/bridge-ui-context.ts +0 -200
  421. package/src/modes/bridge/event-stream.ts +0 -70
  422. package/src/modes/rpc/host-tools.ts +0 -1
  423. package/src/modes/rpc/host-uris.ts +0 -1
  424. package/src/modes/rpc/rpc-client.ts +0 -1166
  425. package/src/modes/rpc/rpc-mode.ts +0 -1029
  426. package/src/modes/rpc/rpc-socket-security.ts +0 -115
  427. package/src/modes/rpc/rpc-types.ts +0 -637
  428. package/src/modes/shared/agent-wire/command-contract.ts +0 -18
  429. package/src/modes/shared/agent-wire/command-dispatch.ts +0 -498
  430. package/src/modes/shared/agent-wire/command-validation.ts +0 -181
  431. package/src/modes/shared/agent-wire/handshake.ts +0 -157
  432. package/src/modes/shared/agent-wire/host-tool-bridge.ts +0 -197
  433. package/src/modes/shared/agent-wire/host-uri-bridge.ts +0 -236
  434. package/src/modes/shared/agent-wire/protocol.ts +0 -46
  435. package/src/modes/shared/agent-wire/responses.ts +0 -17
  436. package/src/modes/shared/agent-wire/scopes.ts +0 -94
  437. package/src/modes/shared/agent-wire/session-registry.ts +0 -109
  438. package/src/modes/shared/agent-wire/ui-request-broker.ts +0 -150
  439. package/src/modes/shared/agent-wire/ui-result.ts +0 -48
  440. package/src/modes/shared/agent-wire/unattended-action-policy.ts +0 -365
  441. package/src/modes/shared/agent-wire/unattended-audit.ts +0 -176
  442. package/src/modes/shared/agent-wire/unattended-run-controller.ts +0 -426
  443. package/src/modes/shared/agent-wire/unattended-session.ts +0 -528
  444. package/src/notifications/config.ts +0 -219
  445. package/src/notifications/index.ts +0 -1835
  446. package/src/notifications/telegram-daemon-cli.ts +0 -243
  447. package/src/tools/puppeteer/09_stealth_locale.txt +0 -46
  448. /package/dist/types/{notifications → sdk/bus}/attachment-registry.d.ts +0 -0
  449. /package/dist/types/{notifications → sdk/bus}/chat-adapters.d.ts +0 -0
  450. /package/dist/types/{notifications → sdk/bus}/daemon-paths.d.ts +0 -0
  451. /package/dist/types/{notifications → sdk/bus}/engine.d.ts +0 -0
  452. /package/dist/types/{notifications → sdk/bus}/helpers.d.ts +0 -0
  453. /package/dist/types/{notifications → sdk/bus}/lifecycle-commands.d.ts +0 -0
  454. /package/dist/types/{notifications → sdk/bus}/operator-runtime.d.ts +0 -0
  455. /package/dist/types/{notifications → sdk/bus}/rate-limit-pool.d.ts +0 -0
  456. /package/dist/types/{notifications → sdk/bus}/recent-activity.d.ts +0 -0
  457. /package/dist/types/{notifications → sdk/bus}/reply-sent-store.d.ts +0 -0
  458. /package/dist/types/{notifications → sdk/bus}/rich-draft.d.ts +0 -0
  459. /package/dist/types/{notifications → sdk/bus}/rich-render.d.ts +0 -0
  460. /package/dist/types/{notifications → sdk/bus}/threaded-inbound.d.ts +0 -0
  461. /package/dist/types/{notifications → sdk/bus}/threaded-render.d.ts +0 -0
  462. /package/src/{notifications → sdk/bus}/attachment-registry.ts +0 -0
  463. /package/src/{notifications → sdk/bus}/chat-adapters.ts +0 -0
  464. /package/src/{notifications → sdk/bus}/daemon-paths.ts +0 -0
  465. /package/src/{notifications → sdk/bus}/engine.ts +0 -0
  466. /package/src/{notifications → sdk/bus}/helpers.ts +0 -0
  467. /package/src/{notifications → sdk/bus}/operator-runtime.ts +0 -0
  468. /package/src/{notifications → sdk/bus}/rate-limit-pool.ts +0 -0
  469. /package/src/{notifications → sdk/bus}/recent-activity.ts +0 -0
  470. /package/src/{notifications → sdk/bus}/reply-sent-store.ts +0 -0
  471. /package/src/{notifications → sdk/bus}/rich-draft.ts +0 -0
  472. /package/src/{notifications → sdk/bus}/rich-render.ts +0 -0
  473. /package/src/{notifications → sdk/bus}/threaded-inbound.ts +0 -0
  474. /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.
@@ -3335,15 +3444,23 @@ export class SessionManager {
3335
3444
 
3336
3445
  restoreState(snapshot: SessionManagerStateSnapshot): void {
3337
3446
  const restoredFileEntries = [...snapshot.materializedFileEntries];
3447
+ const retainsPersistWriter =
3448
+ this.#persistWriter?.isOpen() === true &&
3449
+ this.#sessionId === snapshot.sessionId &&
3450
+ this.#sessionFile === snapshot.sessionFile &&
3451
+ this.#persistWriterPath === snapshot.sessionFile;
3338
3452
  this.#sessionId = snapshot.sessionId;
3339
3453
  this.#sessionName = snapshot.sessionName;
3340
3454
  this.#titleSource = snapshot.titleSource;
3341
3455
  this.#sessionFile = snapshot.sessionFile;
3342
3456
  this.#flushed = snapshot.flushed;
3457
+ this.#ensuredOnDisk = snapshot.ensuredOnDisk;
3343
3458
  this.#needsFullRewriteOnNextPersist = snapshot.needsFullRewriteOnNextPersist;
3344
3459
  this.#fileEntries = restoredFileEntries;
3345
- this.#persistWriter = undefined;
3346
- this.#persistWriterPath = undefined;
3460
+ if (!retainsPersistWriter) {
3461
+ this.#persistWriter = undefined;
3462
+ this.#persistWriterPath = undefined;
3463
+ }
3347
3464
  this.#persistChain = Promise.resolve();
3348
3465
  this.#persistError = undefined;
3349
3466
  this.#persistErrorReported = false;
@@ -3629,7 +3746,7 @@ export class SessionManager {
3629
3746
  }
3630
3747
 
3631
3748
  /** Sync version for initial creation (no existing writer to close) */
3632
- #newSessionSync(options?: NewSessionOptions): string | undefined {
3749
+ #newSessionSync(options?: NewSessionOptions, writeBreadcrumb = true): string | undefined {
3633
3750
  this.#persistChain = Promise.resolve();
3634
3751
  this.#persistError = undefined;
3635
3752
  this.#persistErrorReported = false;
@@ -3664,7 +3781,7 @@ export class SessionManager {
3664
3781
  if (this.persist) {
3665
3782
  const fileTimestamp = timestamp.replace(/[:.]/g, "-");
3666
3783
  this.#sessionFile = path.join(this.getSessionDir(), `${fileTimestamp}_${this.#sessionId}.jsonl`);
3667
- writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
3784
+ if (writeBreadcrumb) writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
3668
3785
  }
3669
3786
  this.#resetResidentTextBlobStore();
3670
3787
  return this.#sessionFile;
@@ -3788,7 +3905,11 @@ export class SessionManager {
3788
3905
  // shared `*.bak` glob on both real and in-memory storage backends and promote it back to
3789
3906
  // the primary on the next session-dir scan.
3790
3907
 
3791
- #replaceSessionFileAfterEpermSync(tempPath: string, targetPath: string, renameError: unknown): void {
3908
+ #replaceSessionFileAfterEpermSync(
3909
+ tempPath: string,
3910
+ targetPath: string,
3911
+ renameError: unknown,
3912
+ ): SessionFileReplacementSyncOutcome {
3792
3913
  const dir = path.resolve(targetPath, "..");
3793
3914
  const backupPath = path.join(dir, `${path.basename(targetPath)}.${Snowflake.next()}.bak`);
3794
3915
  try {
@@ -3796,7 +3917,7 @@ export class SessionManager {
3796
3917
  } catch (err) {
3797
3918
  if (isEnoent(err)) {
3798
3919
  this.storage.renameSync(tempPath, targetPath);
3799
- return;
3920
+ return { kind: "replaced" };
3800
3921
  }
3801
3922
  throw toError(renameError);
3802
3923
  }
@@ -3815,7 +3936,7 @@ export class SessionManager {
3815
3936
  { cause: originalError },
3816
3937
  );
3817
3938
  }
3818
- throw replaceError;
3939
+ return { kind: "restored_previous", error: replaceError };
3819
3940
  }
3820
3941
 
3821
3942
  try {
@@ -3829,6 +3950,7 @@ export class SessionManager {
3829
3950
  });
3830
3951
  }
3831
3952
  }
3953
+ return { kind: "replaced" };
3832
3954
  }
3833
3955
 
3834
3956
  async #replaceSessionFileAfterEperm(tempPath: string, targetPath: string, renameError: unknown): Promise<void> {
@@ -3883,13 +4005,13 @@ export class SessionManager {
3883
4005
  }
3884
4006
  }
3885
4007
 
3886
- #replaceSessionFileSync(tempPath: string, targetPath: string): void {
4008
+ #replaceSessionFileSync(tempPath: string, targetPath: string): SessionFileReplacementSyncOutcome {
3887
4009
  try {
3888
4010
  this.storage.renameSync(tempPath, targetPath);
4011
+ return { kind: "replaced" };
3889
4012
  } catch (err) {
3890
4013
  if (hasFsCode(err, "EPERM")) {
3891
- this.#replaceSessionFileAfterEpermSync(tempPath, targetPath, err);
3892
- return;
4014
+ return this.#replaceSessionFileAfterEpermSync(tempPath, targetPath, err);
3893
4015
  }
3894
4016
 
3895
4017
  throw toError(err);
@@ -3906,7 +4028,8 @@ export class SessionManager {
3906
4028
  writer.writeSync(entry);
3907
4029
  }
3908
4030
  writer.closeSync();
3909
- this.#replaceSessionFileSync(tempPath, this.#sessionFile);
4031
+ const replacement = this.#replaceSessionFileSync(tempPath, this.#sessionFile);
4032
+ if (replacement.kind === "restored_previous") throw replacement.error;
3910
4033
  } catch (err) {
3911
4034
  // closeSync is now truthful and may throw; wrap the best-effort cleanup so
3912
4035
  // the original error (write/close failure) is the one surfaced, not the
@@ -3949,6 +4072,38 @@ export class SessionManager {
3949
4072
  }
3950
4073
  }
3951
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
+
3952
4107
  async #rewriteFile(): Promise<void> {
3953
4108
  if (!this.persist || !this.#sessionFile) return;
3954
4109
  await this.#queuePersistTask(async () => {
@@ -3981,6 +4136,109 @@ export class SessionManager {
3981
4136
  return this.persist;
3982
4137
  }
3983
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
+
3984
4242
  /**
3985
4243
  * Force-persist all current entries to disk, even when no assistant message exists yet.
3986
4244
  * Used by ACP mode where session/new must create a discoverable session immediately.
@@ -4389,6 +4647,24 @@ export class SessionManager {
4389
4647
  }
4390
4648
  }
4391
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
+
4392
4668
  /** Append a message as child of current leaf, then advance leaf. Returns entry id.
4393
4669
  * Does not allow writing CompactionSummaryMessage and BranchSummaryMessage directly.
4394
4670
  * Reason: we want these to be top-level entries in the session, not message session entries,
@@ -5422,6 +5698,11 @@ export class SessionManager {
5422
5698
  return computeDefaultSessionDir(cwd, storage, getSessionsDir(agentDir));
5423
5699
  }
5424
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
+ }
5425
5706
  /**
5426
5707
  * Create a new session.
5427
5708
  * @param cwd Working directory (stored in session header)
@@ -5513,6 +5794,159 @@ export class SessionManager {
5513
5794
  }
5514
5795
  }
5515
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
+
5516
5950
  /** Inspect a selected session without acquiring write-capable ownership. */
5517
5951
  static async inspectSessionTailReadOnly(
5518
5952
  filePath: string,