@gajae-code/coding-agent 0.13.3 → 0.14.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 (599) hide show
  1. package/CHANGELOG.md +184 -2
  2. package/README.md +2 -0
  3. package/bin/gajaessi.js +4 -0
  4. package/dist/types/async/job-manager.d.ts +55 -2
  5. package/dist/types/async/support.d.ts +14 -0
  6. package/dist/types/capability/mcp.d.ts +6 -0
  7. package/dist/types/capability/types.d.ts +6 -0
  8. package/dist/types/cli/accounts-cli.d.ts +19 -0
  9. package/dist/types/cli/args.d.ts +3 -0
  10. package/dist/types/cli/config-cli.d.ts +6 -0
  11. package/dist/types/cli/crash-cli.d.ts +10 -0
  12. package/dist/types/cli/customize-doctor.d.ts +145 -0
  13. package/dist/types/cli/daemon-cli.d.ts +3 -1
  14. package/dist/types/cli/mcp-cli.d.ts +11 -0
  15. package/dist/types/cli/notify-cli.d.ts +1 -1
  16. package/dist/types/cli/root-flags.d.ts +12 -0
  17. package/dist/types/cli/setup-cli.d.ts +3 -1
  18. package/dist/types/cli/skills-cli.d.ts +2 -1
  19. package/dist/types/cli/update-cli.d.ts +19 -1
  20. package/dist/types/cli.d.ts +6 -0
  21. package/dist/types/commands/accounts.d.ts +40 -0
  22. package/dist/types/commands/crash.d.ts +24 -0
  23. package/dist/types/commands/customize.d.ts +22 -0
  24. package/dist/types/commands/daemon.d.ts +1 -1
  25. package/dist/types/commands/launch.d.ts +6 -0
  26. package/dist/types/commands/setup.d.ts +6 -0
  27. package/dist/types/commands/skills.d.ts +6 -1
  28. package/dist/types/commands/update.d.ts +7 -0
  29. package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
  30. package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
  31. package/dist/types/config/config-schema-version.d.ts +10 -0
  32. package/dist/types/config/file-lock.d.ts +18 -0
  33. package/dist/types/config/model-profile-activation.d.ts +3 -1
  34. package/dist/types/config/model-registry.d.ts +9 -1
  35. package/dist/types/config/models-config-schema.d.ts +2 -0
  36. package/dist/types/config/settings-schema.d.ts +70 -6
  37. package/dist/types/config/settings.d.ts +63 -0
  38. package/dist/types/config/skill-settings-defaults.d.ts +26 -0
  39. package/dist/types/config/task-delegation.d.ts +39 -0
  40. package/dist/types/config/update-channel.d.ts +10 -0
  41. package/dist/types/coordinator-mcp/server.d.ts +5 -1
  42. package/dist/types/crash/index-store.d.ts +87 -0
  43. package/dist/types/crash/nudge.d.ts +50 -0
  44. package/dist/types/crash/record-loader.d.ts +17 -0
  45. package/dist/types/crash/report.d.ts +134 -0
  46. package/dist/types/crash/sanitize.d.ts +25 -0
  47. package/dist/types/cursor.d.ts +13 -2
  48. package/dist/types/customization/import.d.ts +37 -0
  49. package/dist/types/customization/index.d.ts +4 -0
  50. package/dist/types/customization/inventory.d.ts +25 -0
  51. package/dist/types/customization/mutations.d.ts +32 -0
  52. package/dist/types/customization/types.d.ts +115 -0
  53. package/dist/types/daemon/control-types.d.ts +8 -1
  54. package/dist/types/daemon/operator-contract.d.ts +4 -4
  55. package/dist/types/discovery/claude.d.ts +39 -1
  56. package/dist/types/discovery/codex.d.ts +39 -1
  57. package/dist/types/discovery/index.d.ts +2 -0
  58. package/dist/types/discovery/mcp-compat.d.ts +30 -0
  59. package/dist/types/edit/renderer.d.ts +23 -0
  60. package/dist/types/extensibility/extensions/runner.d.ts +3 -0
  61. package/dist/types/extensibility/extensions/types.d.ts +21 -4
  62. package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
  63. package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
  64. package/dist/types/extensibility/hooks/loader.d.ts +19 -5
  65. package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
  66. package/dist/types/extensibility/skill-management.d.ts +107 -0
  67. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
  68. package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
  69. package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
  70. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
  71. package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
  72. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
  73. package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
  74. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
  75. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
  76. package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
  77. package/dist/types/hooks/events.d.ts +60 -0
  78. package/dist/types/hooks/normalize.d.ts +83 -0
  79. package/dist/types/hooks/skill-state.d.ts +9 -2
  80. package/dist/types/main.d.ts +1 -1
  81. package/dist/types/modes/acp/abort-scope.d.ts +13 -0
  82. package/dist/types/modes/acp/acp-agent.d.ts +7 -4
  83. package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
  84. package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
  85. package/dist/types/modes/components/customization/index.d.ts +1 -0
  86. package/dist/types/modes/components/gajae-pet-widget.d.ts +8 -30
  87. package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
  88. package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
  89. package/dist/types/modes/components/oauth-selector.d.ts +10 -4
  90. package/dist/types/modes/components/pet-capability.d.ts +11 -25
  91. package/dist/types/modes/components/status-line/gh.d.ts +6 -2
  92. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  93. package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
  94. package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
  95. package/dist/types/modes/interactive-mode.d.ts +1 -0
  96. package/dist/types/modes/types.d.ts +1 -0
  97. package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
  98. package/dist/types/runtime-mcp/config.d.ts +9 -0
  99. package/dist/types/runtime-mcp/manager.d.ts +26 -1
  100. package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
  101. package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
  102. package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
  103. package/dist/types/runtime-mcp/protocol.d.ts +278 -0
  104. package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
  105. package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
  106. package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
  107. package/dist/types/runtime-mcp/types.d.ts +102 -10
  108. package/dist/types/sdk/acp/adapter.d.ts +26 -5
  109. package/dist/types/sdk/broker/broker.d.ts +3 -55
  110. package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
  111. package/dist/types/sdk/broker/session-index.d.ts +14 -5
  112. package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
  113. package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
  114. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
  115. package/dist/types/sdk/bus/config.d.ts +11 -0
  116. package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
  117. package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
  118. package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
  119. package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
  120. package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
  121. package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
  122. package/dist/types/sdk/bus/index.d.ts +85 -99
  123. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
  124. package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
  125. package/dist/types/sdk/bus/notification-service.d.ts +52 -0
  126. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
  127. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
  128. package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
  129. package/dist/types/sdk/bus/session-control.d.ts +25 -1
  130. package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
  131. package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
  132. package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
  133. package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
  134. package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
  135. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
  136. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
  137. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
  138. package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
  139. package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
  140. package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
  141. package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
  142. package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
  143. package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
  144. package/dist/types/sdk/cli/rows.d.ts +2 -11
  145. package/dist/types/sdk/cli/session-cli.d.ts +15 -25
  146. package/dist/types/sdk/client/client.d.ts +97 -1
  147. package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
  148. package/dist/types/sdk/host/control/index.d.ts +1 -1
  149. package/dist/types/sdk/host/control/operations.d.ts +18 -5
  150. package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
  151. package/dist/types/sdk/host/host.d.ts +15 -1
  152. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  153. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  154. package/dist/types/sdk/host/session-runtime.d.ts +127 -7
  155. package/dist/types/sdk/host/types.d.ts +1 -1
  156. package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
  157. package/dist/types/sdk/index.d.ts +2 -1
  158. package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
  159. package/dist/types/sdk/lifecycle/client.d.ts +59 -0
  160. package/dist/types/sdk/lifecycle/index.d.ts +3 -0
  161. package/dist/types/sdk/lifecycle/service.d.ts +232 -0
  162. package/dist/types/sdk/mcp/server.d.ts +9 -6
  163. package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
  164. package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
  165. package/dist/types/sdk/prompt-status.d.ts +10 -3
  166. package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
  167. package/dist/types/sdk/receipt-state.d.ts +17 -0
  168. package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
  169. package/dist/types/sdk/router/index.d.ts +1 -0
  170. package/dist/types/sdk/router/session-router.d.ts +152 -0
  171. package/dist/types/sdk/session-list.d.ts +22 -0
  172. package/dist/types/sdk/session-reconnect.d.ts +43 -1
  173. package/dist/types/sdk/session.d.ts +34 -1
  174. package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
  175. package/dist/types/session/account-inventory.d.ts +86 -0
  176. package/dist/types/session/agent-session.d.ts +145 -6
  177. package/dist/types/session/agent-storage.d.ts +10 -0
  178. package/dist/types/session/coordinator-tool-label.d.ts +28 -0
  179. package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
  180. package/dist/types/session/messages.d.ts +1 -1
  181. package/dist/types/session/secure-token-file.d.ts +3 -0
  182. package/dist/types/session/session-manager.d.ts +40 -1
  183. package/dist/types/session/startup-auth-config.d.ts +44 -0
  184. package/dist/types/session/terminal-abort.d.ts +378 -0
  185. package/dist/types/session/yield-queue.d.ts +7 -0
  186. package/dist/types/session-import/claude.d.ts +26 -0
  187. package/dist/types/session-import/codex.d.ts +88 -0
  188. package/dist/types/session-import/command.d.ts +35 -0
  189. package/dist/types/session-import/detect.d.ts +22 -0
  190. package/dist/types/session-import/index.d.ts +7 -0
  191. package/dist/types/session-import/provider-codex.d.ts +22 -0
  192. package/dist/types/session-import/provider-service.d.ts +38 -0
  193. package/dist/types/session-import/redact.d.ts +23 -0
  194. package/dist/types/session-import/service.d.ts +47 -0
  195. package/dist/types/session-import/types.d.ts +120 -0
  196. package/dist/types/setup/paseo/check.d.ts +16 -0
  197. package/dist/types/setup/paseo/install-saga.d.ts +115 -0
  198. package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
  199. package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
  200. package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
  201. package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
  202. package/dist/types/setup/paseo/provider-config.d.ts +35 -0
  203. package/dist/types/setup/paseo/remove.d.ts +14 -0
  204. package/dist/types/setup/paseo/result-types.d.ts +59 -0
  205. package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
  206. package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
  207. package/dist/types/skill-state/active-state.d.ts +5 -1
  208. package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
  209. package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
  210. package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
  211. package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
  212. package/dist/types/slash-commands/types.d.ts +7 -1
  213. package/dist/types/task/executor.d.ts +10 -0
  214. package/dist/types/task/index.d.ts +3 -1
  215. package/dist/types/tools/ask-answer-registry.d.ts +6 -3
  216. package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
  217. package/dist/types/tools/bash.d.ts +2 -6
  218. package/dist/types/tools/browser/attach.d.ts +11 -0
  219. package/dist/types/tools/browser/launch.d.ts +20 -0
  220. package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
  221. package/dist/types/tools/browser/render.d.ts +1 -0
  222. package/dist/types/tools/browser.d.ts +3 -1
  223. package/dist/types/tools/image-gen.d.ts +9 -2
  224. package/dist/types/tools/index.d.ts +8 -0
  225. package/dist/types/tools/monitor.d.ts +1 -1
  226. package/dist/types/tools/skill-discovery.d.ts +8 -1
  227. package/dist/types/tools/subagent.d.ts +2 -0
  228. package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
  229. package/dist/types/utils/herdr-pane.d.ts +76 -0
  230. package/package.json +19 -9
  231. package/scripts/build-binary.ts +2 -1
  232. package/scripts/build-sdk-package-smoke.ts +2 -13
  233. package/scripts/capture-sticky-viewport-showcase.ts +0 -1
  234. package/scripts/compile-args.ts +2 -0
  235. package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
  236. package/scripts/generate-sdk-operation-inventory.ts +28 -0
  237. package/scripts/generate-telegram-baseline-manifest.ts +1 -0
  238. package/scripts/generate-tool-catalog.ts +6 -4
  239. package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
  240. package/src/async/job-manager.ts +352 -17
  241. package/src/async/support.ts +25 -0
  242. package/src/autoresearch/dashboard.ts +28 -13
  243. package/src/capability/index.ts +3 -1
  244. package/src/capability/mcp.ts +6 -0
  245. package/src/capability/types.ts +6 -0
  246. package/src/cli/accounts-cli.ts +395 -0
  247. package/src/cli/args.ts +23 -0
  248. package/src/cli/auth-broker-cli.ts +136 -66
  249. package/src/cli/auth-gateway-cli.ts +94 -70
  250. package/src/cli/config-cli.ts +27 -2
  251. package/src/cli/crash-cli.ts +78 -0
  252. package/src/cli/customize-doctor.ts +1605 -0
  253. package/src/cli/daemon-cli.ts +12 -3
  254. package/src/cli/fast-help.ts +1 -0
  255. package/src/cli/mcp-cli.ts +128 -60
  256. package/src/cli/notify-cli.ts +8 -14
  257. package/src/cli/root-flags.ts +8 -0
  258. package/src/cli/setup-cli.ts +145 -0
  259. package/src/cli/skills-cli.ts +38 -2
  260. package/src/cli/update-cli.ts +167 -12
  261. package/src/cli.ts +4 -0
  262. package/src/commands/accounts.ts +71 -0
  263. package/src/commands/crash.ts +35 -0
  264. package/src/commands/customize.ts +84 -0
  265. package/src/commands/daemon.ts +2 -2
  266. package/src/commands/deep-interview.ts +2 -0
  267. package/src/commands/launch.ts +1 -0
  268. package/src/commands/mcp.ts +12 -7
  269. package/src/commands/ralplan.ts +2 -0
  270. package/src/commands/sdk.ts +65 -32
  271. package/src/commands/setup.ts +5 -0
  272. package/src/commands/skills.ts +14 -4
  273. package/src/commands/ultragoal.ts +10 -1
  274. package/src/commands/update.ts +26 -15
  275. package/src/config/atomic-yaml-patch-worker.ts +57 -0
  276. package/src/config/atomic-yaml-patch.ts +769 -29
  277. package/src/config/config-schema-version.ts +10 -0
  278. package/src/config/file-lock.ts +70 -1
  279. package/src/config/mcp-schema.json +52 -14
  280. package/src/config/model-profile-activation.ts +20 -1
  281. package/src/config/model-profiles.ts +29 -19
  282. package/src/config/model-registry.ts +132 -21
  283. package/src/config/models-config-schema.ts +1 -1
  284. package/src/config/settings-schema.ts +76 -6
  285. package/src/config/settings.ts +4038 -199
  286. package/src/config/skill-settings-defaults.ts +41 -3
  287. package/src/config/task-delegation.ts +87 -0
  288. package/src/config/update-channel.ts +23 -0
  289. package/src/coordinator-mcp/server.ts +377 -308
  290. package/src/crash/index-store.ts +723 -0
  291. package/src/crash/nudge.ts +89 -0
  292. package/src/crash/record-loader.ts +149 -0
  293. package/src/crash/report.ts +551 -0
  294. package/src/crash/sanitize.ts +129 -0
  295. package/src/cursor.ts +36 -10
  296. package/src/customization/import.ts +946 -0
  297. package/src/customization/index.ts +4 -0
  298. package/src/customization/inventory.ts +335 -0
  299. package/src/customization/mutations.ts +141 -0
  300. package/src/customization/types.ts +223 -0
  301. package/src/daemon/control-types.ts +8 -1
  302. package/src/daemon/operator-contract.ts +6 -6
  303. package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
  304. package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
  305. package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
  306. package/src/discovery/claude-hooks.ts +38 -0
  307. package/src/discovery/claude.ts +142 -209
  308. package/src/discovery/codex-hooks.ts +38 -0
  309. package/src/discovery/codex.ts +132 -198
  310. package/src/discovery/helpers.ts +8 -1
  311. package/src/discovery/index.ts +2 -0
  312. package/src/discovery/mcp-compat.ts +234 -0
  313. package/src/discovery/mcp-json.ts +6 -0
  314. package/src/edit/renderer.ts +40 -0
  315. package/src/edit/streaming.ts +4 -2
  316. package/src/exec/bash-executor.ts +24 -20
  317. package/src/export/html/index.ts +51 -2
  318. package/src/extensibility/extensions/loader.ts +9 -1
  319. package/src/extensibility/extensions/runner.ts +17 -4
  320. package/src/extensibility/extensions/types.ts +31 -6
  321. package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
  322. package/src/extensibility/gjc-plugins/installer.ts +9 -5
  323. package/src/extensibility/gjc-plugins/observability.ts +8 -2
  324. package/src/extensibility/gjc-plugins/registry.ts +13 -2
  325. package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
  326. package/src/extensibility/gjc-plugins/schema.ts +222 -39
  327. package/src/extensibility/hooks/loader.ts +154 -10
  328. package/src/extensibility/runtime-skill-discovery.ts +257 -60
  329. package/src/extensibility/skill-management.ts +368 -0
  330. package/src/extensibility/skills.ts +52 -16
  331. package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
  332. package/src/gjc-runtime/launch-worktree.ts +56 -2
  333. package/src/gjc-runtime/linux-proc.ts +7 -1
  334. package/src/gjc-runtime/ralplan-runtime.ts +100 -164
  335. package/src/gjc-runtime/session-state-lock.ts +992 -0
  336. package/src/gjc-runtime/session-state-sidecar.ts +644 -142
  337. package/src/gjc-runtime/state-writer.ts +39 -14
  338. package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
  339. package/src/gjc-runtime/ultragoal-guard.ts +11 -4
  340. package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
  341. package/src/gjc-runtime/workflow-settings.ts +596 -0
  342. package/src/goals/tools/goal-tool.ts +6 -1
  343. package/src/hooks/events.ts +291 -0
  344. package/src/hooks/native-skill-hook.ts +4 -0
  345. package/src/hooks/normalize.ts +369 -0
  346. package/src/hooks/skill-state.ts +165 -46
  347. package/src/internal-urls/docs-index.generated.ts +40 -32
  348. package/src/main.ts +32 -5
  349. package/src/migrate/mcp-mapper.ts +25 -2
  350. package/src/modes/acp/abort-scope.ts +29 -0
  351. package/src/modes/acp/acp-agent.ts +423 -98
  352. package/src/modes/components/assistant-message.ts +6 -1
  353. package/src/modes/components/customization/customization-dashboard.ts +443 -0
  354. package/src/modes/components/customization/import-wizard.ts +372 -0
  355. package/src/modes/components/customization/index.ts +1 -0
  356. package/src/modes/components/gajae-pet-widget.ts +539 -213
  357. package/src/modes/components/irc-sidebar.ts +11 -0
  358. package/src/modes/components/iterm-pet-transport.ts +585 -0
  359. package/src/modes/components/oauth-selector.ts +277 -86
  360. package/src/modes/components/pet-capability.ts +39 -40
  361. package/src/modes/components/settings-selector.ts +9 -0
  362. package/src/modes/components/status-line/gh.ts +45 -1
  363. package/src/modes/components/status-line/segments.ts +16 -1
  364. package/src/modes/components/tool-status-header.ts +13 -3
  365. package/src/modes/controllers/command-controller.ts +20 -8
  366. package/src/modes/controllers/extension-ui-controller.ts +56 -257
  367. package/src/modes/controllers/input-controller.ts +10 -0
  368. package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
  369. package/src/modes/controllers/selector-controller.ts +177 -72
  370. package/src/modes/controllers/stt-controller.ts +3 -3
  371. package/src/modes/interactive-mode.ts +88 -16
  372. package/src/modes/print-mode.ts +35 -6
  373. package/src/modes/runtime-init.ts +21 -68
  374. package/src/modes/types.ts +1 -0
  375. package/src/modes/utils/injected-user-submission.ts +25 -0
  376. package/src/modes/utils/terminal-bell.ts +15 -1
  377. package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
  378. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  379. package/src/prompts/agents/critic.md +1 -1
  380. package/src/prompts/system/rlm-research.md +1 -1
  381. package/src/prompts/system/system-prompt.md +0 -2
  382. package/src/prompts/tools/bash.md +7 -1
  383. package/src/prompts/tools/browser.md +1 -1
  384. package/src/prompts/tools/cron.md +6 -6
  385. package/src/prompts/tools/read.md +0 -2
  386. package/src/prompts/tools/skill-discovery.md +4 -2
  387. package/src/runtime-mcp/client.ts +427 -23
  388. package/src/runtime-mcp/config.ts +37 -5
  389. package/src/runtime-mcp/manager-pool.test.ts +56 -0
  390. package/src/runtime-mcp/manager.ts +52 -2
  391. package/src/runtime-mcp/oauth-discovery.ts +13 -0
  392. package/src/runtime-mcp/oauth-flow.ts +46 -1
  393. package/src/runtime-mcp/pool-key.ts +5 -1
  394. package/src/runtime-mcp/pool.test.ts +9 -0
  395. package/src/runtime-mcp/protocol.test.ts +353 -0
  396. package/src/runtime-mcp/protocol.ts +624 -0
  397. package/src/runtime-mcp/scope-config.ts +78 -0
  398. package/src/runtime-mcp/tool-bridge.ts +44 -10
  399. package/src/runtime-mcp/transports/http.ts +84 -13
  400. package/src/runtime-mcp/types.ts +123 -12
  401. package/src/sdk/acp/adapter.ts +328 -97
  402. package/src/sdk/broker/broker.ts +160 -741
  403. package/src/sdk/broker/ensure.ts +9 -1
  404. package/src/sdk/broker/lifecycle.ts +510 -116
  405. package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
  406. package/src/sdk/broker/process-incarnation.ts +19 -0
  407. package/src/sdk/broker/session-index.ts +516 -117
  408. package/src/sdk/broker/startup-failure.ts +28 -5
  409. package/src/sdk/broker/transport.ts +1 -13
  410. package/src/sdk/bus/chat-daemon-cli.ts +4 -2
  411. package/src/sdk/bus/chat-daemon-control.ts +67 -6
  412. package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
  413. package/src/sdk/bus/config.ts +13 -0
  414. package/src/sdk/bus/conversation-store.ts +59 -171
  415. package/src/sdk/bus/daemon-paths.ts +20 -0
  416. package/src/sdk/bus/discord-conversation.ts +2 -0
  417. package/src/sdk/bus/discord-daemon.ts +524 -126
  418. package/src/sdk/bus/discord-live-provider.ts +206 -79
  419. package/src/sdk/bus/existing-thread-readiness.ts +4 -97
  420. package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
  421. package/src/sdk/bus/index.ts +2480 -456
  422. package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
  423. package/src/sdk/bus/lifecycle-commands.ts +2 -2
  424. package/src/sdk/bus/managed-daemon.ts +2 -145
  425. package/src/sdk/bus/notification-service.ts +196 -0
  426. package/src/sdk/bus/operator-runtime.ts +1 -1
  427. package/src/sdk/bus/prompt-reconciliation.ts +24 -15
  428. package/src/sdk/bus/reconciliation-store.ts +553 -24
  429. package/src/sdk/bus/session-control.ts +49 -3
  430. package/src/sdk/bus/slack-conversation.ts +4 -0
  431. package/src/sdk/bus/slack-daemon.ts +733 -134
  432. package/src/sdk/bus/slack-live-provider.ts +65 -15
  433. package/src/sdk/bus/slack-provider.ts +4 -0
  434. package/src/sdk/bus/slack-thread-binding.ts +9 -104
  435. package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
  436. package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
  437. package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
  438. package/src/sdk/bus/telegram-daemon-control.ts +3 -13
  439. package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
  440. package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
  441. package/src/sdk/bus/telegram-daemon.ts +2552 -2280
  442. package/src/sdk/bus/telegram-reference.ts +1 -226
  443. package/src/sdk/bus/telegram-setup.ts +2 -2
  444. package/src/sdk/bus/threaded-render.ts +1 -0
  445. package/src/sdk/bus/topic-registry.ts +279 -146
  446. package/src/sdk/cli/rows.ts +2 -35
  447. package/src/sdk/cli/session-cli.ts +776 -700
  448. package/src/sdk/client/client.ts +892 -1
  449. package/src/sdk/host/control/dispatch.ts +93 -58
  450. package/src/sdk/host/control/index.ts +1 -1
  451. package/src/sdk/host/control/operations.ts +18 -10
  452. package/src/sdk/host/control/runtime-gate.ts +40 -0
  453. package/src/sdk/host/host.ts +63 -12
  454. package/src/sdk/host/query/handlers.ts +66 -7
  455. package/src/sdk/host/reverse-leases.ts +2 -2
  456. package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
  457. package/src/sdk/host/session-runtime.test.ts +2787 -34
  458. package/src/sdk/host/session-runtime.ts +1489 -101
  459. package/src/sdk/host/surface-policy.ts +12 -8
  460. package/src/sdk/host/types.ts +1 -1
  461. package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
  462. package/src/sdk/host/websocket-transport.ts +85 -50
  463. package/src/sdk/index.ts +2 -1
  464. package/src/sdk/lifecycle/broker-client.ts +53 -0
  465. package/src/sdk/lifecycle/client.ts +234 -0
  466. package/src/sdk/lifecycle/index.ts +3 -0
  467. package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
  468. package/src/sdk/lifecycle/service.ts +720 -0
  469. package/src/sdk/mcp/server.ts +226 -188
  470. package/src/sdk/prompt-deadline-lease.ts +55 -0
  471. package/src/sdk/prompt-deadline-manager.ts +148 -0
  472. package/src/sdk/prompt-status.ts +11 -3
  473. package/src/sdk/protocol/operation-inventory.generated.json +272 -129
  474. package/src/sdk/protocol/operation-registry.ts +71 -8
  475. package/src/sdk/receipt-state.ts +43 -0
  476. package/src/sdk/reconciliation-extensions.ts +14 -0
  477. package/src/sdk/router/index.ts +1 -0
  478. package/src/sdk/router/session-router.ts +1658 -0
  479. package/src/sdk/session-list.ts +93 -0
  480. package/src/sdk/session-reconnect.ts +45 -1
  481. package/src/sdk/session.ts +716 -120
  482. package/src/sdk/transport/serve-cli.ts +18 -34
  483. package/src/session/account-inventory.ts +603 -0
  484. package/src/session/agent-session.ts +2857 -582
  485. package/src/session/agent-storage.ts +35 -0
  486. package/src/session/blob-store.ts +32 -1
  487. package/src/session/coordinator-tool-label.ts +40 -0
  488. package/src/session/internal/managed-session-scope.ts +4 -0
  489. package/src/session/internal/managed-session-storage.ts +173 -12
  490. package/src/session/messages.ts +1 -1
  491. package/src/session/secure-token-file.ts +80 -0
  492. package/src/session/session-manager.ts +730 -58
  493. package/src/session/session-storage.ts +25 -16
  494. package/src/session/startup-auth-config.ts +297 -0
  495. package/src/session/terminal-abort.ts +1253 -0
  496. package/src/session/yield-queue.ts +57 -15
  497. package/src/session-import/claude.ts +334 -0
  498. package/src/session-import/codex.ts +1033 -0
  499. package/src/session-import/command.ts +77 -0
  500. package/src/session-import/detect.ts +133 -0
  501. package/src/session-import/index.ts +7 -0
  502. package/src/session-import/provider-codex.ts +327 -0
  503. package/src/session-import/provider-service.ts +457 -0
  504. package/src/session-import/redact.ts +128 -0
  505. package/src/session-import/service.ts +1264 -0
  506. package/src/session-import/types.ts +148 -0
  507. package/src/setup/paseo/check.ts +213 -0
  508. package/src/setup/paseo/install-saga.ts +293 -0
  509. package/src/setup/paseo/json-publisher.ts +263 -0
  510. package/src/setup/paseo/orchestration-preferences.ts +86 -0
  511. package/src/setup/paseo/paseo-ownership.ts +224 -0
  512. package/src/setup/paseo/paseo-setup.ts +253 -0
  513. package/src/setup/paseo/provider-config.ts +139 -0
  514. package/src/setup/paseo/remove.ts +183 -0
  515. package/src/setup/paseo/result-types.ts +79 -0
  516. package/src/setup/paseo/setup-deps.ts +175 -0
  517. package/src/setup/paseo/skills-bridge.ts +260 -0
  518. package/src/skill-state/active-state.ts +21 -11
  519. package/src/slash-commands/acp-builtins.ts +9 -1
  520. package/src/slash-commands/builtin-registry.ts +98 -16
  521. package/src/slash-commands/helpers/credential-switch.ts +143 -0
  522. package/src/slash-commands/helpers/mcp.ts +9 -3
  523. package/src/slash-commands/helpers/usage-report.ts +61 -56
  524. package/src/slash-commands/types.ts +7 -2
  525. package/src/task/executor.ts +30 -11
  526. package/src/task/index.ts +66 -7
  527. package/src/task/render.ts +23 -9
  528. package/src/tools/ask-answer-registry.ts +23 -7
  529. package/src/tools/ask.ts +8 -4
  530. package/src/tools/bash-sleep-advisory.ts +115 -0
  531. package/src/tools/bash.ts +98 -18
  532. package/src/tools/browser/attach.ts +71 -3
  533. package/src/tools/browser/launch.ts +86 -13
  534. package/src/tools/browser/profile-discovery.ts +57 -7
  535. package/src/tools/browser/render.ts +8 -1
  536. package/src/tools/browser.ts +130 -21
  537. package/src/tools/computer.enforcement.test.ts +1 -1
  538. package/src/tools/computer.ts +3 -2
  539. package/src/tools/cron.ts +12 -1
  540. package/src/tools/image-gen.ts +27 -16
  541. package/src/tools/index.ts +8 -0
  542. package/src/tools/job.ts +33 -6
  543. package/src/tools/monitor.ts +41 -2
  544. package/src/tools/skill-discovery.ts +21 -29
  545. package/src/tools/subagent.ts +142 -24
  546. package/src/tools/tool-catalog.generated.ts +6 -6
  547. package/src/tools/ultragoal-ask-guard.ts +10 -3
  548. package/src/utils/herdr-pane.ts +345 -0
  549. package/src/utils/title-generator.ts +2 -0
  550. package/src/web/scrapers/index.ts +6 -4
  551. package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
  552. package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
  553. package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
  554. package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
  555. package/dist/types/runtime/lazy-service.test.d.ts +0 -1
  556. package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
  557. package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
  558. package/dist/types/runtime-mcp/config.test.d.ts +0 -1
  559. package/dist/types/runtime-mcp/http.test.d.ts +0 -1
  560. package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
  561. package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
  562. package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
  563. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
  564. package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
  565. package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
  566. package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
  567. package/dist/types/sdk/elevation/audit.d.ts +0 -93
  568. package/dist/types/sdk/elevation/capability.d.ts +0 -3
  569. package/dist/types/sdk/elevation/digest.d.ts +0 -72
  570. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
  571. package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
  572. package/dist/types/sdk/elevation/owner.d.ts +0 -48
  573. package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
  574. package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
  575. package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
  576. package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
  577. package/dist/types/session/auth-broker-config.d.ts +0 -13
  578. package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
  579. package/dist/types/skills/skills.test.d.ts +0 -1
  580. package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
  581. package/dist/types/task/token-log.test.d.ts +0 -1
  582. package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
  583. package/dist/types/tools/descriptors.test.d.ts +0 -1
  584. package/scripts/g005-daemon-smoke.ts +0 -180
  585. package/scripts/g011-daemon-path-smoke.ts +0 -153
  586. package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
  587. package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
  588. package/src/sdk/bus/telegram-cli.ts +0 -212
  589. package/src/sdk/elevation/allowlist.ts +0 -54
  590. package/src/sdk/elevation/audit.ts +0 -208
  591. package/src/sdk/elevation/capability.ts +0 -41
  592. package/src/sdk/elevation/digest.ts +0 -129
  593. package/src/sdk/elevation/dispatch-receipt.ts +0 -184
  594. package/src/sdk/elevation/grant-ledger.ts +0 -1142
  595. package/src/sdk/elevation/owner.ts +0 -109
  596. package/src/session/auth-broker-config.ts +0 -109
  597. /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
  598. /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
  599. /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
@@ -0,0 +1,1264 @@
1
+ import { createHash, type Hash, randomUUID } from "node:crypto";
2
+ import * as nodeFs from "node:fs";
3
+ import * as fs from "node:fs/promises";
4
+ import * as path from "node:path";
5
+ import { createInterface } from "node:readline";
6
+ import { getAgentDir, getSessionsDir } from "@gajae-code/utils";
7
+ import {
8
+ listManagedCandidates,
9
+ managedRootForScope,
10
+ prepareManagedSessionScopeForWriteSync,
11
+ resolveManagedScopeForWrite,
12
+ } from "../session/internal/managed-session-scope";
13
+ import {
14
+ acquireManagedLock,
15
+ ManagedPublishError,
16
+ ManagedSessionDescendantStore,
17
+ type ManagedStorageLock,
18
+ ManagedTreeMoveOutcomeError,
19
+ } from "../session/internal/managed-session-storage";
20
+ import { type ResumeSessionIdentity, SessionManager } from "../session/session-manager";
21
+ import {
22
+ assertCodexWorkspaceIdentity,
23
+ CODEX_CONVERTER_VERSION,
24
+ CODEX_IMPORT_BATCH_LIMIT,
25
+ CODEX_MAPPING_VERSION,
26
+ CODEX_PROVIDER_ID,
27
+ CODEX_SANITIZER_VERSION,
28
+ type CodexConversion,
29
+ CodexImportError,
30
+ type CodexMappedEvent,
31
+ type CodexSessionSource,
32
+ closeCodexSessionAuthorities,
33
+ convertCodexSession,
34
+ discoverCodexSessions,
35
+ sanitizeImportedString,
36
+ } from "./codex";
37
+
38
+ const TARGET_TRANSCRIPT_MAX_BYTES = 128 * 1024 * 1024;
39
+ const TARGET_LINE_MAX_BYTES = 16 * 1024 * 1024;
40
+ const IMPORT_INTERNAL_DIRECTORY = ".gjc-managed-session-internal";
41
+ const IMPORT_LOCKS_DIRECTORY = "locks";
42
+ const IMPORT_STAGING_DIRECTORY = "import-staging";
43
+ const IMPORT_RECOVERY_RECEIPT = "artifact-recovery.json";
44
+ const IMPORT_BUFFER_BYTES = 1024 * 1024;
45
+ // The header, model, and provenance records are durable alongside the staged body.
46
+ const TRANSCRIPT_PREFIX_RESERVE_BYTES = 1024 * 1024;
47
+ const IMPORT_ARTIFACT_DIRECTORY =
48
+ /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z_[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/u;
49
+
50
+ export type SessionImportErrorCode =
51
+ | CodexImportError["code"]
52
+ | "invalid_request"
53
+ | "binding_invalid"
54
+ | "content_too_large"
55
+ | "destination_conflict"
56
+ | "publish_uncertain"
57
+ | "atomic_unavailable"
58
+ | "durability_not_provable"
59
+ | "durability_failed"
60
+ | "identity_mismatch"
61
+ | "validation_failed"
62
+ | "io_failed"
63
+ | "internal_failed";
64
+
65
+ export interface SessionImportFailure {
66
+ status: "failed";
67
+ provider: "codex";
68
+ sourceSessionId?: string;
69
+ code: SessionImportErrorCode;
70
+ phase: string;
71
+ retryable: boolean;
72
+ message: string;
73
+ limitBytes?: number;
74
+ observedBytes?: number;
75
+ causeCode?: string;
76
+ }
77
+
78
+ export interface SessionImportSuccess {
79
+ status: "imported" | "existing";
80
+ provider: "codex";
81
+ sourceSessionId: string;
82
+ targetSessionId: string;
83
+ targetPath: string;
84
+ sourceBytes: number;
85
+ transcriptBytes: number;
86
+ mappedEvents: number;
87
+ quarantinedEvents: number;
88
+ droppedEvents: number;
89
+ quarantineTruncated: boolean;
90
+ redactedValues: number;
91
+ }
92
+
93
+ export type SessionImportResult = SessionImportSuccess | SessionImportFailure;
94
+
95
+ export interface SessionImportBatchResult {
96
+ provider: "codex";
97
+ status: "success" | "partial" | "failed" | "existing";
98
+ results: SessionImportResult[];
99
+ }
100
+
101
+ interface ImportProvenance {
102
+ schemaVersion: 1;
103
+ providerId: typeof CODEX_PROVIDER_ID;
104
+ sourceSessionId: string;
105
+ sourceSha256: string;
106
+ converterVersion: number;
107
+ sanitizerVersion: number;
108
+ mappingVersion: number;
109
+ targetSessionId: string;
110
+ sourceBytes: number;
111
+ transcriptBytes: number;
112
+ counts: CodexConversion["counts"];
113
+ quarantine: { present: boolean; truncated: boolean; sha256?: string };
114
+ source: { workspaceSha256: string; cliVersion?: string; modelProvider?: string };
115
+ }
116
+
117
+ interface ImportManifest {
118
+ schemaVersion: 1;
119
+ provenance: ImportProvenance;
120
+ transcriptSha256: string;
121
+ }
122
+ interface ImportRecoveryReceipt {
123
+ schemaVersion: 1;
124
+ artifactDirectory: string;
125
+ importKey: string;
126
+ targetSessionId: string;
127
+ }
128
+
129
+ type RecoveryReceiptInspection = { kind: "absent" } | { kind: "present"; receipt: ImportRecoveryReceipt };
130
+
131
+ type TranscriptProvenanceInspection = { kind: "absent" } | { kind: "present"; provenance: ImportProvenance | null };
132
+
133
+ function recoveryUncertain(): Error {
134
+ return new Error("recovery_uncertain");
135
+ }
136
+
137
+ interface StagedTranscript {
138
+ relativePath: string;
139
+ absolutePath: string;
140
+ bytes: number;
141
+ sha256: string;
142
+ provenance: ImportProvenance;
143
+ }
144
+
145
+ function entryId(): string {
146
+ return randomUUID().replaceAll("-", "");
147
+ }
148
+
149
+ function timestampMs(timestamp: string): number {
150
+ const value = Date.parse(timestamp);
151
+ return Number.isFinite(value) ? value : 0;
152
+ }
153
+
154
+ function assistantEnvelope(content: unknown[], timestamp: string, stopReason: "stop" | "toolUse") {
155
+ return {
156
+ role: "assistant",
157
+ content,
158
+ api: "openai-codex-responses",
159
+ provider: CODEX_PROVIDER_ID,
160
+ model: "codex-imported-history",
161
+ usage: {
162
+ input: 0,
163
+ output: 0,
164
+ cacheRead: 0,
165
+ cacheWrite: 0,
166
+ totalTokens: 0,
167
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
168
+ },
169
+ stopReason,
170
+ timestamp: timestampMs(timestamp),
171
+ };
172
+ }
173
+
174
+ function importKey(conversion: CodexConversion): string {
175
+ return [
176
+ CODEX_PROVIDER_ID,
177
+ conversion.source.id,
178
+ conversion.sourceSha256,
179
+ CODEX_CONVERTER_VERSION,
180
+ CODEX_SANITIZER_VERSION,
181
+ CODEX_MAPPING_VERSION,
182
+ ].join(":");
183
+ }
184
+ function provenanceKey(provenance: ImportProvenance): string {
185
+ return [
186
+ provenance.providerId,
187
+ provenance.sourceSessionId,
188
+ provenance.sourceSha256,
189
+ provenance.converterVersion,
190
+ provenance.sanitizerVersion,
191
+ provenance.mappingVersion,
192
+ ].join(":");
193
+ }
194
+
195
+ function stableTargetSessionId(key: string): string {
196
+ const digest = createHash("sha256").update(key).digest("hex");
197
+ return `${digest.slice(0, 8)}-${digest.slice(8, 12)}-${digest.slice(12, 16)}-${digest.slice(16, 20)}-${digest.slice(20, 32)}`;
198
+ }
199
+
200
+ function makeEntry(
201
+ id: string,
202
+ parentId: string | null,
203
+ timestamp: string,
204
+ value: Record<string, unknown>,
205
+ ): Record<string, unknown> & { id: string; parentId: string | null; timestamp: string } {
206
+ return { ...value, id, parentId, timestamp };
207
+ }
208
+
209
+ function quarantineBytes(conversion: CodexConversion): Buffer | null {
210
+ if (conversion.quarantine.length === 0) return null;
211
+ return Buffer.from(`${conversion.quarantine.map(record => JSON.stringify(record)).join("\n")}\n`, "utf8");
212
+ }
213
+
214
+ function serializedJsonLine(value: unknown): Buffer {
215
+ return Buffer.from(`${JSON.stringify(value)}\n`, "utf8");
216
+ }
217
+
218
+ class BufferedFileWriter {
219
+ readonly #handle: fs.FileHandle;
220
+ #pending: Buffer[] = [];
221
+ #pendingBytes = 0;
222
+ bytes = 0;
223
+
224
+ constructor(handle: fs.FileHandle) {
225
+ this.#handle = handle;
226
+ }
227
+
228
+ async write(bytes: Uint8Array): Promise<void> {
229
+ const copy = Buffer.from(bytes);
230
+ this.#pending.push(copy);
231
+ this.#pendingBytes += copy.byteLength;
232
+ this.bytes += copy.byteLength;
233
+ if (this.#pendingBytes >= IMPORT_BUFFER_BYTES) await this.flush();
234
+ }
235
+
236
+ async flush(): Promise<void> {
237
+ if (this.#pendingBytes === 0) return;
238
+ await this.#handle.write(Buffer.concat(this.#pending, this.#pendingBytes));
239
+ this.#pending = [];
240
+ this.#pendingBytes = 0;
241
+ }
242
+ }
243
+
244
+ async function streamFileIntoWriter(sourcePath: string, writer: BufferedFileWriter, hash: Hash): Promise<void> {
245
+ const source = await fs.open(sourcePath, nodeFs.constants.O_RDONLY | nodeFs.constants.O_NOFOLLOW);
246
+ try {
247
+ const stat = await source.stat();
248
+ if (!stat.isFile() || stat.nlink !== 1) throw new Error("source_untrusted");
249
+ const chunk = Buffer.allocUnsafe(IMPORT_BUFFER_BYTES);
250
+ let position = 0;
251
+ for (;;) {
252
+ const { bytesRead } = await source.read(chunk, 0, chunk.length, position);
253
+ if (bytesRead === 0) break;
254
+ const bytes = chunk.subarray(0, bytesRead);
255
+ hash.update(bytes);
256
+ await writer.write(bytes);
257
+ position += bytesRead;
258
+ }
259
+ } finally {
260
+ await source.close();
261
+ }
262
+ }
263
+
264
+ async function validateExactV5(file: string, expectedSessionId: string, expectedBytes: number): Promise<void> {
265
+ const stat = await fs.lstat(file);
266
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size !== expectedBytes)
267
+ throw new Error("validation_failed");
268
+ const stream = nodeFs.createReadStream(file);
269
+ const reader = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
270
+ let lineNumber = 0;
271
+ let priorId: string | null = null;
272
+ const entryIds = new Set<string>();
273
+ const toolCallIds = new Set<string>();
274
+ try {
275
+ for await (const line of reader) {
276
+ lineNumber++;
277
+ if (Buffer.byteLength(line, "utf8") > TARGET_LINE_MAX_BYTES)
278
+ throw new CodexImportError(
279
+ "content_too_large",
280
+ "source_event",
281
+ "Converted GJC transcript contains an oversized entry.",
282
+ TARGET_LINE_MAX_BYTES,
283
+ Buffer.byteLength(line, "utf8"),
284
+ );
285
+ let record: Record<string, unknown>;
286
+ try {
287
+ record = JSON.parse(line) as Record<string, unknown>;
288
+ } catch {
289
+ throw new Error("validation_failed");
290
+ }
291
+ if (lineNumber === 1) {
292
+ if (record.type !== "session" || record.version !== 5 || record.id !== expectedSessionId)
293
+ throw new Error("validation_failed");
294
+ entryIds.add(expectedSessionId);
295
+ continue;
296
+ }
297
+ if (
298
+ typeof record.id !== "string" ||
299
+ entryIds.has(record.id) ||
300
+ record.parentId !== priorId ||
301
+ typeof record.timestamp !== "string" ||
302
+ !Number.isFinite(Date.parse(record.timestamp))
303
+ )
304
+ throw new Error("validation_failed");
305
+ entryIds.add(record.id);
306
+ if (record.type === "model_change") {
307
+ if (lineNumber !== 2 || typeof record.model !== "string") throw new Error("validation_failed");
308
+ } else if (record.type === "custom") {
309
+ if (
310
+ lineNumber !== 3 ||
311
+ record.customType !== "session-import-provenance" ||
312
+ !record.data ||
313
+ typeof record.data !== "object"
314
+ )
315
+ throw new Error("validation_failed");
316
+ } else if (record.type === "message") {
317
+ const message = record.message;
318
+ if (!message || typeof message !== "object") throw new Error("validation_failed");
319
+ const value = message as Record<string, unknown>;
320
+ const role = String(value.role);
321
+ const content = value.content;
322
+ if (!["user", "assistant", "toolResult"].includes(role)) throw new Error("validation_failed");
323
+ if (role === "user" && typeof content === "string") {
324
+ if (content.length === 0) throw new Error("validation_failed");
325
+ } else if (!Array.isArray(content)) throw new Error("validation_failed");
326
+ if (role === "toolResult") {
327
+ if (
328
+ typeof value.toolCallId !== "string" ||
329
+ !toolCallIds.has(value.toolCallId) ||
330
+ typeof value.toolName !== "string"
331
+ )
332
+ throw new Error("validation_failed");
333
+ }
334
+ if (!Array.isArray(content)) {
335
+ priorId = record.id;
336
+ continue;
337
+ }
338
+ for (const itemValue of content) {
339
+ if (!itemValue || typeof itemValue !== "object") throw new Error("validation_failed");
340
+ const item = itemValue as Record<string, unknown>;
341
+ if (item.type === "text") {
342
+ if (typeof item.text !== "string") throw new Error("validation_failed");
343
+ } else if (role === "assistant" && item.type === "toolCall") {
344
+ if (
345
+ typeof item.id !== "string" ||
346
+ toolCallIds.has(item.id) ||
347
+ typeof item.name !== "string" ||
348
+ !item.arguments ||
349
+ typeof item.arguments !== "object"
350
+ )
351
+ throw new Error("validation_failed");
352
+ toolCallIds.add(item.id);
353
+ } else throw new Error("validation_failed");
354
+ }
355
+ } else throw new Error("validation_failed");
356
+ priorId = record.id;
357
+ }
358
+ } finally {
359
+ reader.close();
360
+ stream.destroy();
361
+ }
362
+ if (lineNumber < 3) throw new Error("validation_failed");
363
+ }
364
+
365
+ async function hashFileWithPrefix(
366
+ file: string,
367
+ prefixBytes: number,
368
+ expected: ResumeSessionIdentity,
369
+ ): Promise<{ totalBytes: number; prefixSha256: string; fullSha256: string }> {
370
+ const canonicalPath = await fs.realpath(file);
371
+ if (canonicalPath !== expected.canonicalPath) throw new Error("validation_failed");
372
+ const handle = await fs.open(file, nodeFs.constants.O_RDONLY | nodeFs.constants.O_NOFOLLOW);
373
+ const prefixDigest = createHash("sha256");
374
+ const fullDigest = createHash("sha256");
375
+ let totalBytes = 0;
376
+ try {
377
+ const initial = await handle.stat({ bigint: true });
378
+ if (
379
+ !initial.isFile() ||
380
+ initial.nlink !== 1n ||
381
+ initial.dev !== expected.dev ||
382
+ initial.ino !== expected.ino ||
383
+ Number(initial.size) !== expected.size ||
384
+ initial.mtimeNs !== expected.mtimeNs ||
385
+ initial.size < BigInt(prefixBytes)
386
+ )
387
+ throw new Error("validation_failed");
388
+ const chunk = Buffer.allocUnsafe(IMPORT_BUFFER_BYTES);
389
+ for (;;) {
390
+ const result = await handle.read(chunk, 0, chunk.length, totalBytes);
391
+ if (result.bytesRead === 0) break;
392
+ const bytes = chunk.subarray(0, result.bytesRead);
393
+ fullDigest.update(bytes);
394
+ if (totalBytes < prefixBytes)
395
+ prefixDigest.update(bytes.subarray(0, Math.min(bytes.length, prefixBytes - totalBytes)));
396
+ totalBytes += result.bytesRead;
397
+ }
398
+ const terminal = await handle.stat({ bigint: true });
399
+ const named = await fs.lstat(file, { bigint: true });
400
+ if (
401
+ totalBytes !== expected.size ||
402
+ terminal.dev !== initial.dev ||
403
+ terminal.ino !== initial.ino ||
404
+ terminal.size !== initial.size ||
405
+ terminal.mtimeNs !== initial.mtimeNs ||
406
+ terminal.ctimeNs !== initial.ctimeNs ||
407
+ terminal.nlink !== 1n ||
408
+ named.dev !== initial.dev ||
409
+ named.ino !== initial.ino ||
410
+ named.size !== initial.size ||
411
+ named.mtimeNs !== initial.mtimeNs ||
412
+ named.ctimeNs !== initial.ctimeNs ||
413
+ named.nlink !== 1n ||
414
+ named.isSymbolicLink() ||
415
+ (await fs.realpath(file)) !== expected.canonicalPath
416
+ )
417
+ throw new Error("validation_failed");
418
+ } finally {
419
+ await handle.close();
420
+ }
421
+ return {
422
+ totalBytes,
423
+ prefixSha256: prefixDigest.digest("hex"),
424
+ fullSha256: fullDigest.digest("hex"),
425
+ };
426
+ }
427
+
428
+ async function hashBoundedAttachment(file: string, maxBytes: number): Promise<string | null> {
429
+ const handle = await fs.open(file, nodeFs.constants.O_RDONLY | nodeFs.constants.O_NOFOLLOW).catch(() => null);
430
+ if (!handle) return null;
431
+ const digest = createHash("sha256");
432
+ let offset = 0;
433
+ try {
434
+ const initial = await handle.stat({ bigint: true });
435
+ if (!initial.isFile() || initial.nlink !== 1n || initial.size > BigInt(maxBytes)) return null;
436
+ const chunk = Buffer.allocUnsafe(IMPORT_BUFFER_BYTES);
437
+ for (;;) {
438
+ const result = await handle.read(chunk, 0, chunk.length, offset);
439
+ if (result.bytesRead === 0) break;
440
+ digest.update(chunk.subarray(0, result.bytesRead));
441
+ offset += result.bytesRead;
442
+ }
443
+ const terminal = await handle.stat({ bigint: true });
444
+ const named = await fs.lstat(file, { bigint: true });
445
+ if (
446
+ BigInt(offset) !== initial.size ||
447
+ terminal.dev !== initial.dev ||
448
+ terminal.ino !== initial.ino ||
449
+ terminal.size !== initial.size ||
450
+ terminal.mtimeNs !== initial.mtimeNs ||
451
+ terminal.ctimeNs !== initial.ctimeNs ||
452
+ terminal.nlink !== 1n ||
453
+ named.dev !== initial.dev ||
454
+ named.ino !== initial.ino ||
455
+ named.size !== initial.size ||
456
+ named.mtimeNs !== initial.mtimeNs ||
457
+ named.ctimeNs !== initial.ctimeNs ||
458
+ named.nlink !== 1n ||
459
+ named.isSymbolicLink()
460
+ )
461
+ return null;
462
+ return digest.digest("hex");
463
+ } finally {
464
+ await handle.close();
465
+ }
466
+ }
467
+
468
+ function isManifest(value: unknown): value is ImportManifest {
469
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
470
+ const manifest = value as Partial<ImportManifest>;
471
+ return manifest.schemaVersion === 1 && manifest.provenance?.schemaVersion === 1;
472
+ }
473
+
474
+ async function readManifest(file: string): Promise<ImportManifest | null> {
475
+ try {
476
+ const value = JSON.parse(await fs.readFile(file, "utf8")) as unknown;
477
+ return isManifest(value) ? value : null;
478
+ } catch {
479
+ return null;
480
+ }
481
+ }
482
+ async function readTranscriptProvenance(file: string): Promise<TranscriptProvenanceInspection> {
483
+ let handle: fs.FileHandle;
484
+ try {
485
+ handle = await fs.open(file, nodeFs.constants.O_RDONLY | nodeFs.constants.O_NOFOLLOW);
486
+ } catch (error) {
487
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return { kind: "absent" };
488
+ throw recoveryUncertain();
489
+ }
490
+
491
+ try {
492
+ const initial = await handle.stat({ bigint: true });
493
+ if (!initial.isFile() || initial.nlink !== 1n) throw recoveryUncertain();
494
+ const buffer = Buffer.alloc(512 * 1024);
495
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
496
+ const text = buffer.subarray(0, bytesRead).toString("utf8");
497
+ const lines = text.split("\n");
498
+ const completeLineCount = text.endsWith("\n") || bytesRead < buffer.length ? lines.length : lines.length - 1;
499
+ let provenance: ImportProvenance | null = null;
500
+ for (let index = 0; index < completeLineCount; index++) {
501
+ const line = lines[index];
502
+ if (!line) continue;
503
+ let value: unknown;
504
+ try {
505
+ value = JSON.parse(line) as unknown;
506
+ } catch {
507
+ throw recoveryUncertain();
508
+ }
509
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw recoveryUncertain();
510
+ const entry = value as { customType?: unknown; data?: unknown };
511
+ if (entry.customType !== "session-import-provenance") continue;
512
+ if (
513
+ !entry.data ||
514
+ typeof entry.data !== "object" ||
515
+ Array.isArray(entry.data) ||
516
+ (entry.data as { schemaVersion?: unknown }).schemaVersion !== 1
517
+ )
518
+ throw recoveryUncertain();
519
+ if (provenance) throw recoveryUncertain();
520
+ provenance = entry.data as ImportProvenance;
521
+ }
522
+ const terminal = await handle.stat({ bigint: true });
523
+ const named = await fs.lstat(file, { bigint: true });
524
+ if (
525
+ !terminal.isFile() ||
526
+ terminal.nlink !== 1n ||
527
+ terminal.dev !== initial.dev ||
528
+ terminal.ino !== initial.ino ||
529
+ terminal.size !== initial.size ||
530
+ terminal.mtimeNs !== initial.mtimeNs ||
531
+ terminal.ctimeNs !== initial.ctimeNs ||
532
+ named.isSymbolicLink() ||
533
+ !named.isFile() ||
534
+ named.nlink !== 1n ||
535
+ named.dev !== initial.dev ||
536
+ named.ino !== initial.ino ||
537
+ named.size !== initial.size ||
538
+ named.mtimeNs !== initial.mtimeNs ||
539
+ named.ctimeNs !== initial.ctimeNs
540
+ )
541
+ throw recoveryUncertain();
542
+ return { kind: "present", provenance };
543
+ } catch {
544
+ throw recoveryUncertain();
545
+ } finally {
546
+ await handle.close().catch(() => undefined);
547
+ }
548
+ }
549
+
550
+ async function findExisting(
551
+ scope: Parameters<typeof listManagedCandidates>[0],
552
+ key: string,
553
+ ): Promise<SessionImportSuccess | null> {
554
+ const listing = listManagedCandidates(scope);
555
+ if (listing.kind !== "complete" || listing.invalid.length > 0) throw new Error("binding_invalid");
556
+
557
+ for (const candidate of listing.owned) {
558
+ const artifactDirectory = candidate.path.slice(0, -6);
559
+ const manifestPath = path.join(artifactDirectory, "codex-import-manifest.json");
560
+ const manifest = await readManifest(manifestPath);
561
+ if (!manifest) continue;
562
+ const provenance = manifest.provenance;
563
+ const candidateKey = provenanceKey(provenance);
564
+ if (candidateKey !== key || provenance.targetSessionId !== candidate.sessionId) continue;
565
+ const transcriptInspection = await readTranscriptProvenance(candidate.path);
566
+ if (transcriptInspection.kind === "absent") continue;
567
+ const transcriptProvenance = transcriptInspection.provenance;
568
+ if (!transcriptProvenance || JSON.stringify(transcriptProvenance) !== JSON.stringify(provenance)) continue;
569
+ const quarantinePath = path.join(artifactDirectory, "codex-quarantine.jsonl");
570
+ if (provenance.quarantine.present) {
571
+ if (
572
+ !provenance.quarantine.sha256 ||
573
+ (await hashBoundedAttachment(quarantinePath, 8 * 1024 * 1024)) !== provenance.quarantine.sha256
574
+ )
575
+ continue;
576
+ } else if (await fs.lstat(quarantinePath).catch(() => null)) continue;
577
+ const inspection = await SessionManager.inspectSessionTailReadOnly(candidate.path);
578
+ if (inspection.kind === "error" || inspection.identity.sessionId !== candidate.sessionId) continue;
579
+ const observed = await hashFileWithPrefix(candidate.path, provenance.transcriptBytes, inspection.identity).catch(
580
+ () => null,
581
+ );
582
+ if (
583
+ !observed ||
584
+ observed.prefixSha256 !== manifest.transcriptSha256 ||
585
+ observed.fullSha256 !== inspection.identity.sha256
586
+ )
587
+ continue;
588
+ return {
589
+ status: "existing",
590
+ provider: "codex",
591
+ sourceSessionId: provenance.sourceSessionId,
592
+ targetSessionId: candidate.sessionId,
593
+ targetPath: candidate.path,
594
+ sourceBytes: provenance.sourceBytes,
595
+ transcriptBytes: observed.totalBytes,
596
+ mappedEvents: provenance.counts.mapped,
597
+ quarantinedEvents: provenance.counts.quarantined,
598
+ droppedEvents: provenance.counts.dropped,
599
+ quarantineTruncated: false,
600
+ redactedValues: provenance.counts.redacted,
601
+ };
602
+ }
603
+ return null;
604
+ }
605
+
606
+ function classifyFailure(error: unknown, sourceSessionId?: string): SessionImportFailure {
607
+ if (error instanceof CodexImportError) {
608
+ return {
609
+ status: "failed",
610
+ provider: "codex",
611
+ sourceSessionId,
612
+ code: error.code,
613
+ phase: error.phase,
614
+ retryable: error.code === "source_changed",
615
+ message: error.message,
616
+ limitBytes: error.limitBytes,
617
+ observedBytes: error.observedBytes,
618
+ };
619
+ }
620
+ if (error instanceof ManagedPublishError) {
621
+ const code: SessionImportErrorCode = !error.stagingCleanupSafe
622
+ ? "publish_uncertain"
623
+ : error.classification === "io_error"
624
+ ? "io_failed"
625
+ : error.classification;
626
+ return {
627
+ status: "failed",
628
+ provider: "codex",
629
+ sourceSessionId,
630
+ code,
631
+ phase: "publish",
632
+ retryable: code === "publish_uncertain" || code === "destination_conflict" || code === "io_failed",
633
+ message:
634
+ code === "publish_uncertain"
635
+ ? "The session publication outcome is uncertain; retry will reconcile it."
636
+ : `Session publication failed: ${code}.`,
637
+ causeCode: error.classification,
638
+ };
639
+ }
640
+ const message = error instanceof Error ? error.message : "Unknown import failure";
641
+ if (message === "recovery_uncertain")
642
+ return {
643
+ status: "failed",
644
+ provider: "codex",
645
+ sourceSessionId,
646
+ code: "publish_uncertain",
647
+ phase: "internal",
648
+ retryable: true,
649
+ message: "Import recovery state is uncertain; retry will reconcile it.",
650
+ causeCode: message,
651
+ };
652
+ if (
653
+ message === "content_too_large" ||
654
+ message === "artifact_capacity_exceeded" ||
655
+ message.includes("capacity_exceeded")
656
+ ) {
657
+ return {
658
+ status: "failed",
659
+ provider: "codex",
660
+ sourceSessionId,
661
+ code: "content_too_large",
662
+ phase: "artifact",
663
+ retryable: false,
664
+ message: "Imported session artifacts exceed the managed storage limit.",
665
+ causeCode: message,
666
+ };
667
+ }
668
+ if (message.includes("destination_conflict"))
669
+ return {
670
+ status: "failed",
671
+ provider: "codex",
672
+ sourceSessionId,
673
+ code: "destination_conflict",
674
+ phase: "publish",
675
+ retryable: true,
676
+ message: "The imported session destination already exists.",
677
+ };
678
+ if (message === "validation_failed")
679
+ return {
680
+ status: "failed",
681
+ provider: "codex",
682
+ sourceSessionId,
683
+ code: "validation_failed",
684
+ phase: "validation",
685
+ retryable: false,
686
+ message: "The converted session failed exact v5 validation.",
687
+ };
688
+ return {
689
+ status: "failed",
690
+ provider: "codex",
691
+ sourceSessionId,
692
+ code: message === "binding_invalid" ? "binding_invalid" : "internal_failed",
693
+ phase: "internal",
694
+ retryable: false,
695
+ message: message === "binding_invalid" ? "Managed session binding is invalid." : "Session import failed.",
696
+ causeCode: message,
697
+ };
698
+ }
699
+
700
+ async function removeStaging(store: ManagedSessionDescendantStore, relativePath: string): Promise<void> {
701
+ try {
702
+ const snapshot = store.captureTree(relativePath);
703
+ store.removeTreeExpected(relativePath, snapshot);
704
+ } catch {
705
+ // Exact managed cleanup is best-effort; recovery owns ambiguous staging.
706
+ }
707
+ }
708
+ async function readRecoveryReceipt(file: string): Promise<RecoveryReceiptInspection> {
709
+ let handle: fs.FileHandle;
710
+ try {
711
+ handle = await fs.open(file, nodeFs.constants.O_RDONLY | nodeFs.constants.O_NOFOLLOW);
712
+ } catch (error) {
713
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return { kind: "absent" };
714
+ throw recoveryUncertain();
715
+ }
716
+
717
+ try {
718
+ const stat = await handle.stat({ bigint: true });
719
+ if (!stat.isFile() || stat.nlink !== 1n || stat.size > 64n * 1024n) throw recoveryUncertain();
720
+ const value = JSON.parse(await handle.readFile("utf8")) as unknown;
721
+ const record = value as Partial<ImportRecoveryReceipt>;
722
+ if (
723
+ !value ||
724
+ typeof value !== "object" ||
725
+ Array.isArray(value) ||
726
+ record.schemaVersion !== 1 ||
727
+ typeof record.importKey !== "string" ||
728
+ typeof record.targetSessionId !== "string" ||
729
+ typeof record.artifactDirectory !== "string" ||
730
+ !IMPORT_ARTIFACT_DIRECTORY.test(record.artifactDirectory)
731
+ )
732
+ throw recoveryUncertain();
733
+ return { kind: "present", receipt: record as ImportRecoveryReceipt };
734
+ } catch {
735
+ throw recoveryUncertain();
736
+ } finally {
737
+ await handle.close().catch(() => undefined);
738
+ }
739
+ }
740
+ function readManifestFromSnapshot(
741
+ store: ManagedSessionDescendantStore,
742
+ relativePath: string,
743
+ snapshot: ReturnType<ManagedSessionDescendantStore["captureTree"]>,
744
+ ): ImportManifest | null {
745
+ const manifestRelative = "codex-import-manifest.json";
746
+ const entry = snapshot.entries.find(candidate => candidate.relativePath === manifestRelative);
747
+ if (!entry) return null;
748
+ if (entry.kind !== "file" || !entry.sha256) throw new Error("destination_conflict");
749
+ const file = store.readExpected(`${relativePath}/${manifestRelative}`);
750
+ if (
751
+ !file ||
752
+ file.identity.dev.toString() !== entry.dev ||
753
+ file.identity.ino.toString() !== entry.ino ||
754
+ file.identity.size.toString() !== entry.size ||
755
+ createHash("sha256").update(file.bytes).digest("hex") !== entry.sha256
756
+ )
757
+ throw new Error("destination_conflict");
758
+ try {
759
+ const value = JSON.parse(file.bytes.toString("utf8")) as unknown;
760
+ return isManifest(value) ? value : null;
761
+ } catch {
762
+ return null;
763
+ }
764
+ }
765
+
766
+ async function reconcileStaleArtifactDirectory(
767
+ store: ManagedSessionDescendantStore,
768
+ scopeDirectory: string,
769
+ relativePath: string,
770
+ transcriptPath: string,
771
+ expectedKey: string,
772
+ expectedTargetSessionId: string,
773
+ ): Promise<void> {
774
+ const absolutePath = path.join(scopeDirectory, relativePath);
775
+ const stat = await fs.lstat(absolutePath).catch(error => {
776
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
777
+ throw recoveryUncertain();
778
+ });
779
+ if (!stat) return;
780
+ if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error("destination_conflict");
781
+ const transcriptStat = await fs.lstat(transcriptPath).catch(error => {
782
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
783
+ throw recoveryUncertain();
784
+ });
785
+ if (transcriptStat) throw new Error("destination_conflict");
786
+ const snapshot = store.captureTree(relativePath);
787
+ const manifest = readManifestFromSnapshot(store, relativePath, snapshot);
788
+ if (manifest) {
789
+ if (
790
+ provenanceKey(manifest.provenance) !== expectedKey ||
791
+ manifest.provenance.targetSessionId !== expectedTargetSessionId
792
+ )
793
+ throw new Error("destination_conflict");
794
+ } else if (snapshot.entries.some(entry => entry.relativePath !== "")) {
795
+ throw new Error("destination_conflict");
796
+ }
797
+ store.removeTreeExpected(relativePath, snapshot);
798
+ const remaining = await fs.lstat(absolutePath).catch(error => {
799
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
800
+ throw recoveryUncertain();
801
+ });
802
+ if (remaining) throw new Error("destination_conflict");
803
+ }
804
+ async function reconcileImportStaging(store: ManagedSessionDescendantStore, scopeDirectory: string): Promise<void> {
805
+ const stagingRoot = path.join(scopeDirectory, IMPORT_INTERNAL_DIRECTORY, IMPORT_STAGING_DIRECTORY);
806
+ const entries = await fs.readdir(stagingRoot, { withFileTypes: true }).catch(error => {
807
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
808
+ throw error;
809
+ });
810
+ for (const entry of entries) {
811
+ if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error("identity_mismatch");
812
+ const relativePath = `${IMPORT_INTERNAL_DIRECTORY}/${IMPORT_STAGING_DIRECTORY}/${entry.name}`;
813
+ const receiptInspection = await readRecoveryReceipt(path.join(stagingRoot, entry.name, IMPORT_RECOVERY_RECEIPT));
814
+ if (receiptInspection.kind === "absent") continue;
815
+ const receipt = receiptInspection.receipt;
816
+ const transcriptPath = path.join(scopeDirectory, `${receipt.artifactDirectory}.jsonl`);
817
+ const transcriptInspection = await readTranscriptProvenance(transcriptPath);
818
+ if (transcriptInspection.kind === "absent") {
819
+ await reconcileStaleArtifactDirectory(
820
+ store,
821
+ scopeDirectory,
822
+ receipt.artifactDirectory,
823
+ transcriptPath,
824
+ receipt.importKey,
825
+ receipt.targetSessionId,
826
+ );
827
+ } else {
828
+ const provenance = transcriptInspection.provenance;
829
+ if (
830
+ !provenance ||
831
+ provenanceKey(provenance) !== receipt.importKey ||
832
+ provenance.targetSessionId !== receipt.targetSessionId
833
+ )
834
+ throw recoveryUncertain();
835
+ }
836
+ const snapshot = store.captureTree(relativePath);
837
+ store.removeTreeExpected(relativePath, snapshot);
838
+ }
839
+ }
840
+
841
+ async function reconcileWorkspaceImports(cwd: string): Promise<void> {
842
+ const agentDir = getAgentDir();
843
+ const sessionsRoot = getSessionsDir(agentDir);
844
+ const resolved = resolveManagedScopeForWrite({ cwd, agentDir, sessionsRoot });
845
+ if (resolved.kind === "error") throw new Error(resolved.code);
846
+ const prepared = prepareManagedSessionScopeForWriteSync(resolved.scope);
847
+ if (prepared.kind === "error") throw new Error(prepared.code);
848
+ const root = managedRootForScope(resolved.scope);
849
+ const store = new ManagedSessionDescendantStore(root, resolved.scope.directoryPath);
850
+ let lock: ManagedStorageLock | undefined;
851
+ try {
852
+ lock = await acquireManagedLock(
853
+ path.join(resolved.scope.directoryPath, IMPORT_INTERNAL_DIRECTORY, IMPORT_LOCKS_DIRECTORY),
854
+ "import-session-global",
855
+ root,
856
+ );
857
+ await reconcileImportStaging(store, resolved.scope.directoryPath);
858
+ } finally {
859
+ if (lock) await lock.release();
860
+ store.close();
861
+ }
862
+ }
863
+ async function retryCleanupPending<T>(operation: () => Promise<T>): Promise<T> {
864
+ let lastError: unknown;
865
+ for (let attempt = 0; attempt < 8; attempt++) {
866
+ try {
867
+ return await operation();
868
+ } catch (error) {
869
+ if (!(error instanceof Error) || error.message !== "cleanup_pending") throw error;
870
+ lastError = error;
871
+ }
872
+ }
873
+ throw lastError;
874
+ }
875
+
876
+ async function stageConversion(
877
+ store: ManagedSessionDescendantStore,
878
+ attemptRelative: string,
879
+ conversionSource: CodexConversion["source"],
880
+ ): Promise<{ conversion: CodexConversion; transcript: StagedTranscript }> {
881
+ const attemptStore = store.deriveSubtree(attemptRelative);
882
+ const bodyPath = path.join(attemptStore.dir, "body.jsonl");
883
+ const finalPath = path.join(attemptStore.dir, "transcript.jsonl");
884
+ const bodyHandle = await fs.open(bodyPath, "wx", 0o600);
885
+ const bodyWriter = new BufferedFileWriter(bodyHandle);
886
+ const provenanceId = entryId();
887
+ let parentId: string | null = provenanceId;
888
+ const pendingToolNames = new Map<string, string>();
889
+ let conversion: CodexConversion;
890
+ try {
891
+ conversion = await convertCodexSession(conversionSource, async (event: CodexMappedEvent) => {
892
+ let entry: Record<string, unknown>;
893
+ if (event.kind === "user") {
894
+ entry = makeEntry(entryId(), parentId, event.timestamp, {
895
+ type: "message",
896
+ message: {
897
+ role: "user",
898
+ content: [{ type: "text", text: event.text }],
899
+ attribution: "user",
900
+ timestamp: timestampMs(event.timestamp),
901
+ },
902
+ });
903
+ } else if (event.kind === "assistant") {
904
+ entry = makeEntry(entryId(), parentId, event.timestamp, {
905
+ type: "message",
906
+ message: assistantEnvelope([{ type: "text", text: event.text }], event.timestamp, "stop"),
907
+ });
908
+ } else if (event.kind === "tool_call") {
909
+ pendingToolNames.set(event.callId, event.name);
910
+ entry = makeEntry(entryId(), parentId, event.timestamp, {
911
+ type: "message",
912
+ message: assistantEnvelope(
913
+ [{ type: "toolCall", id: event.callId, name: event.name, arguments: event.arguments }],
914
+ event.timestamp,
915
+ "toolUse",
916
+ ),
917
+ });
918
+ } else {
919
+ entry = makeEntry(entryId(), parentId, event.timestamp, {
920
+ type: "message",
921
+ message: {
922
+ role: "toolResult",
923
+ toolCallId: event.callId,
924
+ toolName: pendingToolNames.get(event.callId) ?? "codex_tool",
925
+ content: [{ type: "text", text: event.output }],
926
+ isError: false,
927
+ timestamp: timestampMs(event.timestamp),
928
+ },
929
+ });
930
+ pendingToolNames.delete(event.callId);
931
+ }
932
+ const serialized = serializedJsonLine(entry);
933
+ const projectedBytes = bodyWriter.bytes + serialized.byteLength + TRANSCRIPT_PREFIX_RESERVE_BYTES;
934
+ if (projectedBytes > TARGET_TRANSCRIPT_MAX_BYTES)
935
+ throw new CodexImportError(
936
+ "content_too_large",
937
+ "source_event",
938
+ "Converted GJC transcript exceeds the import limit.",
939
+ TARGET_TRANSCRIPT_MAX_BYTES,
940
+ projectedBytes,
941
+ );
942
+ parentId = String(entry.id);
943
+ await bodyWriter.write(serialized);
944
+ });
945
+ await bodyWriter.flush();
946
+ await bodyHandle.sync();
947
+ } finally {
948
+ await bodyHandle.close();
949
+ }
950
+ const targetSessionId = stableTargetSessionId(importKey(conversion));
951
+ const quarantine = quarantineBytes(conversion);
952
+ const provenance: ImportProvenance = {
953
+ schemaVersion: 1,
954
+ providerId: CODEX_PROVIDER_ID,
955
+ sourceSessionId: conversion.source.id,
956
+ sourceSha256: conversion.sourceSha256,
957
+ converterVersion: CODEX_CONVERTER_VERSION,
958
+ sanitizerVersion: CODEX_SANITIZER_VERSION,
959
+ mappingVersion: CODEX_MAPPING_VERSION,
960
+ targetSessionId,
961
+ sourceBytes: conversion.sourceBytes,
962
+ transcriptBytes: 0,
963
+ counts: conversion.counts,
964
+ quarantine: {
965
+ present: quarantine !== null,
966
+ truncated: conversion.quarantineTruncated,
967
+ ...(quarantine ? { sha256: createHash("sha256").update(quarantine).digest("hex") } : {}),
968
+ },
969
+ source: {
970
+ workspaceSha256: createHash("sha256").update(path.resolve(conversion.source.cwd)).digest("hex"),
971
+ ...(conversion.source.cliVersion
972
+ ? { cliVersion: sanitizeImportedString(conversion.source.cliVersion).value }
973
+ : {}),
974
+ ...(conversion.source.modelProvider
975
+ ? { modelProvider: sanitizeImportedString(conversion.source.modelProvider).value }
976
+ : {}),
977
+ },
978
+ };
979
+ const timestamp = conversion.source.timestamp;
980
+ const header = {
981
+ type: "session",
982
+ version: 5,
983
+ id: targetSessionId,
984
+ title:
985
+ conversion.source.title ?? sanitizeImportedString(`Imported Codex ${conversion.source.id.slice(0, 12)}`).value,
986
+ titleSource: "user",
987
+ timestamp,
988
+ cwd: conversion.source.cwd,
989
+ };
990
+ const modelId = entryId();
991
+ const model = makeEntry(modelId, null, timestamp, {
992
+ type: "model_change",
993
+ model: "openai-codex/codex-imported-history",
994
+ });
995
+ let prefix = Buffer.alloc(0);
996
+ for (let attempt = 0; attempt < 8; attempt++) {
997
+ const provenanceEntry = makeEntry(provenanceId, modelId, timestamp, {
998
+ type: "custom",
999
+ customType: "session-import-provenance",
1000
+ data: provenance,
1001
+ });
1002
+ prefix = Buffer.from(
1003
+ `${JSON.stringify(header)}\n${JSON.stringify(model)}\n${JSON.stringify(provenanceEntry)}\n`,
1004
+ "utf8",
1005
+ );
1006
+ const nextBytes = prefix.byteLength + bodyWriter.bytes;
1007
+ if (nextBytes === provenance.transcriptBytes) break;
1008
+ provenance.transcriptBytes = nextBytes;
1009
+ }
1010
+ if (provenance.transcriptBytes > TARGET_TRANSCRIPT_MAX_BYTES)
1011
+ throw new CodexImportError(
1012
+ "content_too_large",
1013
+ "source_event",
1014
+ "Converted GJC transcript exceeds the import limit.",
1015
+ TARGET_TRANSCRIPT_MAX_BYTES,
1016
+ provenance.transcriptBytes,
1017
+ );
1018
+ const finalHandle = await fs.open(finalPath, "wx", 0o600);
1019
+ const finalWriter = new BufferedFileWriter(finalHandle);
1020
+ const transcriptHash = createHash("sha256");
1021
+ try {
1022
+ transcriptHash.update(prefix);
1023
+ await finalWriter.write(prefix);
1024
+ await streamFileIntoWriter(bodyPath, finalWriter, transcriptHash);
1025
+ await finalWriter.flush();
1026
+ await finalHandle.sync();
1027
+ } finally {
1028
+ await finalHandle.close();
1029
+ }
1030
+ if (finalWriter.bytes !== provenance.transcriptBytes) throw new Error("validation_failed");
1031
+ await validateExactV5(finalPath, targetSessionId, finalWriter.bytes);
1032
+ const inspection = await SessionManager.inspectSessionTailReadOnly(finalPath);
1033
+ if (inspection.kind === "error" && inspection.reason === "context_too_large")
1034
+ throw new CodexImportError(
1035
+ "content_too_large",
1036
+ "source_event",
1037
+ "Converted GJC transcript exceeds the resumable context limit.",
1038
+ TARGET_TRANSCRIPT_MAX_BYTES,
1039
+ finalWriter.bytes,
1040
+ );
1041
+ return {
1042
+ conversion,
1043
+ transcript: {
1044
+ relativePath: `${attemptRelative}/transcript.jsonl`,
1045
+ absolutePath: finalPath,
1046
+ bytes: finalWriter.bytes,
1047
+ sha256: transcriptHash.digest("hex"),
1048
+ provenance,
1049
+ },
1050
+ };
1051
+ }
1052
+
1053
+ async function publishSource(source: CodexConversion["source"]): Promise<SessionImportSuccess> {
1054
+ const agentDir = getAgentDir();
1055
+ const sessionsRoot = getSessionsDir(agentDir);
1056
+ await assertCodexWorkspaceIdentity(source);
1057
+ const resolved = resolveManagedScopeForWrite({ cwd: source.cwd, agentDir, sessionsRoot });
1058
+ if (resolved.kind === "error") {
1059
+ const cause = resolved.cause?.classification;
1060
+ if (resolved.code === "capacity_exceeded" || cause === "artifact_capacity_exceeded")
1061
+ throw new CodexImportError(
1062
+ "content_too_large",
1063
+ "quarantine",
1064
+ "Managed session artifacts exceed the storage limit.",
1065
+ );
1066
+ throw new Error(resolved.code);
1067
+ }
1068
+ await assertCodexWorkspaceIdentity(source);
1069
+ const prepared = prepareManagedSessionScopeForWriteSync(resolved.scope);
1070
+ if (prepared.kind === "error") {
1071
+ const cause = prepared.cause?.classification;
1072
+ if (prepared.code === "capacity_exceeded" || cause === "artifact_capacity_exceeded")
1073
+ throw new CodexImportError(
1074
+ "content_too_large",
1075
+ "quarantine",
1076
+ "Managed session artifacts exceed the storage limit.",
1077
+ );
1078
+ throw new Error(prepared.code);
1079
+ }
1080
+ const root = managedRootForScope(resolved.scope);
1081
+ const store = new ManagedSessionDescendantStore(root, resolved.scope.directoryPath);
1082
+ const attemptId = randomUUID();
1083
+ const attemptRelative = `${IMPORT_INTERNAL_DIRECTORY}/${IMPORT_STAGING_DIRECTORY}/${attemptId}`;
1084
+ let artifactsPublished = false;
1085
+ let artifactDirectory = "";
1086
+ let cleanupAttempt = true;
1087
+ let recoveryLock: ManagedStorageLock | undefined;
1088
+ try {
1089
+ recoveryLock = await acquireManagedLock(
1090
+ path.join(resolved.scope.directoryPath, IMPORT_INTERNAL_DIRECTORY, IMPORT_LOCKS_DIRECTORY),
1091
+ "import-session-global",
1092
+ root,
1093
+ );
1094
+ await reconcileImportStaging(store, resolved.scope.directoryPath);
1095
+ const staged = await stageConversion(store, attemptRelative, source);
1096
+ const key = importKey(staged.conversion);
1097
+ const targetSessionId = staged.transcript.provenance.targetSessionId;
1098
+ const lockName = `import-${createHash("sha256").update(key).digest("hex")}`;
1099
+ const lock = await acquireManagedLock(
1100
+ path.join(resolved.scope.directoryPath, IMPORT_INTERNAL_DIRECTORY, IMPORT_LOCKS_DIRECTORY),
1101
+ lockName,
1102
+ root,
1103
+ );
1104
+ try {
1105
+ const existing = await findExisting(resolved.scope, key);
1106
+ if (existing) return existing;
1107
+ const timestamp = staged.conversion.source.timestamp.replace(/[:.]/g, "-");
1108
+ const filename = `${timestamp}_${targetSessionId}.jsonl`;
1109
+ artifactDirectory = filename.slice(0, -6);
1110
+ const manifest: ImportManifest = {
1111
+ schemaVersion: 1,
1112
+ provenance: staged.transcript.provenance,
1113
+ transcriptSha256: staged.transcript.sha256,
1114
+ };
1115
+ await reconcileStaleArtifactDirectory(
1116
+ store,
1117
+ resolved.scope.directoryPath,
1118
+ artifactDirectory,
1119
+ path.join(resolved.scope.directoryPath, filename),
1120
+ key,
1121
+ targetSessionId,
1122
+ );
1123
+ const recoveryReceipt: ImportRecoveryReceipt = {
1124
+ schemaVersion: 1,
1125
+ artifactDirectory,
1126
+ importKey: key,
1127
+ targetSessionId,
1128
+ };
1129
+ await store.publishNoReplace(
1130
+ `${attemptRelative}/${IMPORT_RECOVERY_RECEIPT}`,
1131
+ Buffer.from(`${JSON.stringify(recoveryReceipt)}\n`, "utf8"),
1132
+ );
1133
+ const artifactStagingRelative = `${attemptRelative}/artifacts`;
1134
+ const artifactStore = store.deriveSubtree(artifactStagingRelative);
1135
+ try {
1136
+ await artifactStore.publishNoReplace(
1137
+ "codex-import-manifest.json",
1138
+ Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8"),
1139
+ );
1140
+ const quarantine = quarantineBytes(staged.conversion);
1141
+ if (quarantine) await artifactStore.publishNoReplace("codex-quarantine.jsonl", quarantine);
1142
+ artifactStore.fsyncTree();
1143
+ } finally {
1144
+ artifactStore.close();
1145
+ }
1146
+ const artifactSnapshot = store.captureTree(artifactStagingRelative);
1147
+ store.moveTreeNoReplace(artifactStagingRelative, artifactDirectory, artifactSnapshot);
1148
+ artifactsPublished = true;
1149
+ store.publishStagedFileNoReplace(staged.transcript.relativePath, filename, {
1150
+ bytes: staged.transcript.bytes,
1151
+ sha256: staged.transcript.sha256,
1152
+ });
1153
+ return {
1154
+ status: "imported",
1155
+ provider: "codex",
1156
+ sourceSessionId: staged.conversion.source.id,
1157
+ targetSessionId,
1158
+ targetPath: path.join(resolved.scope.directoryPath, filename),
1159
+ sourceBytes: staged.conversion.sourceBytes,
1160
+ transcriptBytes: staged.transcript.bytes,
1161
+ mappedEvents: staged.conversion.counts.mapped,
1162
+ quarantinedEvents: staged.conversion.counts.quarantined,
1163
+ droppedEvents: staged.conversion.counts.dropped,
1164
+ quarantineTruncated: staged.conversion.quarantineTruncated,
1165
+ redactedValues: staged.conversion.counts.redacted,
1166
+ };
1167
+ } catch (error) {
1168
+ const cleanupSafe =
1169
+ (!(error instanceof ManagedPublishError) && !(error instanceof ManagedTreeMoveOutcomeError)) ||
1170
+ error.stagingCleanupSafe;
1171
+ if (!cleanupSafe) cleanupAttempt = false;
1172
+ if (cleanupSafe && artifactsPublished && artifactDirectory) await removeStaging(store, artifactDirectory);
1173
+ throw error;
1174
+ } finally {
1175
+ await lock.release();
1176
+ }
1177
+ } finally {
1178
+ if (cleanupAttempt) await removeStaging(store, attemptRelative);
1179
+ if (recoveryLock) await recoveryLock.release();
1180
+ store.close();
1181
+ }
1182
+ }
1183
+ async function publishSourceWithRecovery(source: CodexSessionSource): Promise<SessionImportSuccess> {
1184
+ return retryCleanupPending(() => publishSource(source));
1185
+ }
1186
+
1187
+ export async function importCodexSessions(
1188
+ cwd: string,
1189
+ requestedIds: readonly string[],
1190
+ ): Promise<SessionImportBatchResult> {
1191
+ let sources: CodexSessionSource[] = [];
1192
+ try {
1193
+ sources = await discoverCodexSessions(cwd, requestedIds, undefined, true);
1194
+ if (requestedIds.length === 0 && sources.length > CODEX_IMPORT_BATCH_LIMIT)
1195
+ throw new CodexImportError(
1196
+ "content_too_large",
1197
+ "discovery",
1198
+ `Codex session import batch exceeds the maximum of ${CODEX_IMPORT_BATCH_LIMIT} sessions.`,
1199
+ );
1200
+ const canonicalWorkspace = sources[0]?.cwd ?? (await fs.realpath(cwd));
1201
+ await retryCleanupPending(() => reconcileWorkspaceImports(canonicalWorkspace));
1202
+ } catch (error) {
1203
+ await closeCodexSessionAuthorities(sources);
1204
+ return { provider: "codex", status: "failed", results: [classifyFailure(error)] };
1205
+ }
1206
+ if (sources.length === 0) {
1207
+ return {
1208
+ provider: "codex",
1209
+ status: "failed",
1210
+ results: [
1211
+ {
1212
+ status: "failed",
1213
+ provider: "codex",
1214
+ code: "source_not_found",
1215
+ phase: "discovery",
1216
+ retryable: false,
1217
+ message: "No Codex sessions belong to the current workspace.",
1218
+ },
1219
+ ],
1220
+ };
1221
+ }
1222
+ const results: SessionImportResult[] = [];
1223
+ for (const source of sources) {
1224
+ try {
1225
+ results.push(await publishSourceWithRecovery(source));
1226
+ } catch (error) {
1227
+ results.push(classifyFailure(error, source.id));
1228
+ }
1229
+ }
1230
+ await closeCodexSessionAuthorities(sources);
1231
+ const imported = results.filter(result => result.status === "imported").length;
1232
+ const existing = results.filter(result => result.status === "existing").length;
1233
+ const failed = results.filter(result => result.status === "failed").length;
1234
+ return {
1235
+ provider: "codex",
1236
+ status: failed > 0 ? (imported + existing > 0 ? "partial" : "failed") : imported > 0 ? "success" : "existing",
1237
+ results,
1238
+ };
1239
+ }
1240
+
1241
+ export function formatSessionImportBatch(result: SessionImportBatchResult): string {
1242
+ const lines = result.results.map(item => {
1243
+ if (item.status === "failed") {
1244
+ const id = item.sourceSessionId ? ` ${item.sourceSessionId}` : "";
1245
+ return `failed${id}: ${item.code} (${item.phase}) — ${item.message}`;
1246
+ }
1247
+ const truncated = item.quarantineTruncated ? ", quarantine truncated" : "";
1248
+ const dropped = item.droppedEvents > 0 ? `, ${item.droppedEvents} dropped` : "";
1249
+ return `${item.status} ${item.sourceSessionId} -> ${item.targetSessionId} (${item.mappedEvents} mapped, ${item.quarantinedEvents} quarantined${dropped}${truncated})`;
1250
+ });
1251
+ return [`Codex session import: ${result.status}`, ...lines].join("\n");
1252
+ }
1253
+
1254
+ /**
1255
+ * Provider-neutral session import service (issue #3709).
1256
+ *
1257
+ * Pipeline: bounded read-only source load → format detection → provider adapter
1258
+ * → normalization + fail-closed redaction → head/tail bounding → persistence
1259
+ * into a NEW GJC session through the public SessionManager API.
1260
+ *
1261
+ * The source file is never written, moved, or enumerated. The live session is
1262
+ * never touched by this module; callers switch sessions explicitly after a
1263
+ * successful materialization.
1264
+ */