handmux 0.23.0 → 0.25.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 (327) hide show
  1. package/README.md +31 -8
  2. package/README.zh-CN.md +31 -8
  3. package/bin/handmux.js +14 -688
  4. package/bin/node-version.js +20 -0
  5. package/dist/bin/handmux.js +1015 -0
  6. package/dist/connectors/bridgeClient.js +385 -0
  7. package/dist/connectors/claude/index.js +476 -0
  8. package/dist/connectors/pi/bridgeClient.js +8 -0
  9. package/dist/connectors/pi/index.js +851 -0
  10. package/dist/hooks/handmux-notify.sh +64 -0
  11. package/{hooks → dist/hooks}/handmux-statusline.cjs +28 -6
  12. package/dist/hooks/handmux-write.cjs +264 -0
  13. package/dist/package.json +127 -0
  14. package/dist/public/assets/index-CTrqTqtT.css +32 -0
  15. package/dist/public/assets/index-DmjeMSQA.js +346 -0
  16. package/dist/public/assets/pcm-worklet-CxcDy7PB.js +1 -0
  17. package/{public → dist/public}/index.html +5 -4
  18. package/dist/src/agent-runtime/adapter.js +204 -0
  19. package/dist/src/agent-runtime/bridge.js +1006 -0
  20. package/dist/src/agent-runtime/bridgeStore.js +25 -0
  21. package/dist/src/agent-runtime/bridgeTransport.js +619 -0
  22. package/dist/src/agent-runtime/bridgeTypes.js +13 -0
  23. package/dist/src/agent-runtime/builtinRuntime.js +193 -0
  24. package/dist/src/agent-runtime/conversation.js +2179 -0
  25. package/dist/src/agent-runtime/conversationActivation.js +75 -0
  26. package/dist/src/agent-runtime/conversationActivity.js +69 -0
  27. package/dist/src/agent-runtime/conversationControls.js +206 -0
  28. package/dist/src/agent-runtime/conversationLiveHub.js +276 -0
  29. package/dist/src/agent-runtime/conversationStore.js +352 -0
  30. package/dist/src/agent-runtime/conversationTypes.js +1 -0
  31. package/dist/src/agent-runtime/conversationValidation.js +339 -0
  32. package/dist/src/agent-runtime/inbox.js +973 -0
  33. package/dist/src/agent-runtime/inboxPushProjection.js +69 -0
  34. package/dist/src/agent-runtime/inboxStore.js +25 -0
  35. package/dist/src/agent-runtime/inboxTypes.js +1 -0
  36. package/dist/src/agent-runtime/interaction.js +829 -0
  37. package/dist/src/agent-runtime/interactionLiveHub.js +225 -0
  38. package/dist/src/agent-runtime/interactionStore.js +18 -0
  39. package/dist/src/agent-runtime/interactionTypes.js +1 -0
  40. package/dist/src/agent-runtime/legacyInboxProjection.js +70 -0
  41. package/dist/src/agent-runtime/migrateLegacyCodexOutbox.js +336 -0
  42. package/dist/src/agent-runtime/resourceTypes.js +1 -0
  43. package/dist/src/agent-runtime/resources.js +245 -0
  44. package/dist/src/agent-runtime/run.js +328 -0
  45. package/dist/src/agent-runtime/runtime.js +793 -0
  46. package/dist/src/agent-runtime/sessionControl.js +164 -0
  47. package/dist/src/agent-runtime/subscriptionUsage.js +190 -0
  48. package/dist/src/agent-runtime/subscriptionUsageLegacy.js +18 -0
  49. package/dist/src/agent-runtime/tmuxRuntime.js +156 -0
  50. package/dist/src/agents/claude.js +203 -0
  51. package/dist/src/agents/claudeConversation.js +362 -0
  52. package/dist/src/agents/claudeInteraction.js +134 -0
  53. package/dist/src/agents/claudeSubscriptionUsage.js +61 -0
  54. package/dist/src/agents/codex.js +262 -0
  55. package/dist/src/agents/codexConversation.js +1103 -0
  56. package/dist/src/agents/codexConversationActivation.js +121 -0
  57. package/dist/src/agents/codexConversationControls.js +256 -0
  58. package/dist/src/agents/codexInteraction.js +306 -0
  59. package/dist/src/agents/codexSessionControl.js +118 -0
  60. package/dist/src/agents/codexSubscriptionUsage.js +313 -0
  61. package/dist/src/agents/codexTerminalControl.js +23 -0
  62. package/dist/src/agents/conversationProjectionSafety.js +416 -0
  63. package/dist/src/agents/index.js +39 -0
  64. package/dist/src/agents/nativeInbox.js +264 -0
  65. package/dist/src/agents/nativeInboxSources.js +115 -0
  66. package/dist/src/agents/pi.js +28 -0
  67. package/dist/src/agents/piConversation.js +523 -0
  68. package/dist/src/agents/piConversationHistory.js +502 -0
  69. package/dist/src/agents/piInboxBridge.js +368 -0
  70. package/dist/src/agents/piSessionControl.js +32 -0
  71. package/dist/src/agents/processIdentity.js +94 -0
  72. package/dist/src/agents/scanUtils.js +262 -0
  73. package/dist/src/apiAccountProviders.js +194 -0
  74. package/dist/src/apiAccounts.js +592 -0
  75. package/dist/src/apiErrors.js +86 -0
  76. package/{src → dist/src}/appName.js +12 -13
  77. package/dist/src/asr/iflyConfig.js +7 -0
  78. package/dist/src/asr/iflySign.js +14 -0
  79. package/dist/src/auth.js +27 -0
  80. package/dist/src/browser/bootstrap.js +72 -0
  81. package/dist/src/browser/cookieProfiles.js +413 -0
  82. package/dist/src/browser/coordinator.js +51 -0
  83. package/dist/src/browser/hammerheadCompat.js +104 -0
  84. package/dist/src/browser/hammerheadRedirectCompat.js +37 -0
  85. package/dist/src/browser/manager.js +560 -0
  86. package/dist/src/browser/originLabel.js +15 -0
  87. package/dist/src/browser/profilePersistence.js +235 -0
  88. package/dist/src/browser/publicProxy.js +176 -0
  89. package/dist/src/browser/routes.js +159 -0
  90. package/dist/src/browser/sessionStore.js +72 -0
  91. package/dist/src/browser/siteVersion.js +121 -0
  92. package/dist/src/browser/targetPolicy.js +163 -0
  93. package/{src → dist/src}/browser/worker.js +14 -18
  94. package/dist/src/browser/workerClient.js +286 -0
  95. package/dist/src/browser/workerServer.js +91 -0
  96. package/dist/src/captureBackground.js +95 -0
  97. package/dist/src/claudeEvents.js +525 -0
  98. package/dist/src/cli/agentIntegration.js +131 -0
  99. package/dist/src/cli/agentLauncher.js +26 -0
  100. package/dist/src/cli/claudeHooks.js +290 -0
  101. package/{src → dist/src}/cli/cloudflareUrl.js +2 -3
  102. package/dist/src/cli/cloudflared.js +106 -0
  103. package/dist/src/cli/codexManaged.js +80 -0
  104. package/{src → dist/src}/cli/cpolarUrl.js +8 -9
  105. package/dist/src/cli/drivers.js +98 -0
  106. package/dist/src/cli/hookScaffold.js +39 -0
  107. package/dist/src/cli/i18n/en.js +392 -0
  108. package/{src → dist/src}/cli/i18n/index.js +27 -25
  109. package/dist/src/cli/i18n/zh.js +391 -0
  110. package/dist/src/cli/legacyCodexHooks.js +52 -0
  111. package/{src → dist/src}/cli/natappUrl.js +2 -3
  112. package/dist/src/cli/options.js +245 -0
  113. package/dist/src/cli/piExtension.js +155 -0
  114. package/dist/src/cli/probe.js +16 -0
  115. package/{src → dist/src}/cli/prompt.js +4 -6
  116. package/dist/src/cli/pushCmd.js +102 -0
  117. package/{src → dist/src}/cli/qr.js +19 -19
  118. package/dist/src/cli/service.js +154 -0
  119. package/dist/src/cli/setupModel.js +194 -0
  120. package/dist/src/cli/setupWizard.js +647 -0
  121. package/dist/src/cli/shortcutEditor.js +351 -0
  122. package/dist/src/cli/sshTunnel.js +18 -0
  123. package/dist/src/cli/state.js +117 -0
  124. package/dist/src/cli/statusLine.js +115 -0
  125. package/dist/src/cli/supervisor.js +278 -0
  126. package/dist/src/cli/supervisorHealth.js +21 -0
  127. package/dist/src/cli/supervisorLaunch.js +148 -0
  128. package/dist/src/cli/supervisorProcesses.js +62 -0
  129. package/dist/src/cli/supervisorState.js +62 -0
  130. package/dist/src/cli/tmuxVersion.js +53 -0
  131. package/{src → dist/src}/cli/tunlite.js +7 -10
  132. package/dist/src/cli/tunnelClients.js +72 -0
  133. package/{src → dist/src}/cli/updateCheck.js +98 -69
  134. package/{src → dist/src}/cli/urlHost.js +8 -4
  135. package/dist/src/cli/workspaceCmd.js +347 -0
  136. package/dist/src/codexAppServer.js +3436 -0
  137. package/dist/src/codexConversationProjection.js +199 -0
  138. package/dist/src/codexDiff.js +118 -0
  139. package/dist/src/codexMessageIdentity.js +18 -0
  140. package/dist/src/codexPlan.js +30 -0
  141. package/dist/src/codexQueueProtocol.js +137 -0
  142. package/dist/src/codexStreamProtocol.js +207 -0
  143. package/dist/src/codexToolProtocol.js +68 -0
  144. package/dist/src/codexTranscriptParse.js +835 -0
  145. package/dist/src/codexUsageSnapshot.js +228 -0
  146. package/dist/src/config.js +8 -0
  147. package/{src → dist/src}/docPath.js +22 -24
  148. package/dist/src/docs.js +337 -0
  149. package/dist/src/git.js +284 -0
  150. package/dist/src/healthProtocol.js +97 -0
  151. package/dist/src/httpApi.js +96 -0
  152. package/dist/src/jsonStore.js +15 -0
  153. package/{src → dist/src}/keyNames.js +3 -3
  154. package/dist/src/notifications.js +111 -0
  155. package/dist/src/orphans.js +167 -0
  156. package/dist/src/paneInput.js +42 -0
  157. package/dist/src/pendingPrompt.js +194 -0
  158. package/dist/src/previewServer.js +202 -0
  159. package/dist/src/previews.js +179 -0
  160. package/dist/src/privateStateStore.js +124 -0
  161. package/dist/src/projectTask/backup.js +224 -0
  162. package/dist/src/projectTask/lock.js +108 -0
  163. package/dist/src/projectTask/migrations.js +90 -0
  164. package/dist/src/projectTask/routes.js +155 -0
  165. package/dist/src/projectTask/runtime.js +182 -0
  166. package/dist/src/projectTask/schema.js +56 -0
  167. package/dist/src/projectTask/store.js +457 -0
  168. package/dist/src/push.js +224 -0
  169. package/dist/src/routes/agents.js +631 -0
  170. package/dist/src/routes/apiAccounts.js +82 -0
  171. package/dist/src/routes/files.js +230 -0
  172. package/dist/src/routes/git.js +89 -0
  173. package/dist/src/routes/health.js +16 -0
  174. package/dist/src/routes/notifications.js +16 -0
  175. package/dist/src/routes/previews.js +43 -0
  176. package/dist/src/routes/push.js +145 -0
  177. package/dist/src/routes/sessions.js +244 -0
  178. package/dist/src/routes/system.js +220 -0
  179. package/dist/src/routes/terminal.js +244 -0
  180. package/dist/src/routes/workspace.js +267 -0
  181. package/dist/src/server.js +315 -0
  182. package/dist/src/shortcutConfig.js +83 -0
  183. package/{src → dist/src}/staticCache.js +5 -3
  184. package/{src → dist/src}/staticCompression.js +5 -6
  185. package/dist/src/terminalStream.js +441 -0
  186. package/{src → dist/src}/tmux/commands.js +185 -172
  187. package/dist/src/tmux/format.js +47 -0
  188. package/dist/src/transcriptParse.js +261 -0
  189. package/dist/src/transcriptReader.js +209 -0
  190. package/{src → dist/src}/trimCapture.js +11 -11
  191. package/dist/src/uploadTypes.js +31 -0
  192. package/dist/src/urlPolicy.js +18 -0
  193. package/dist/src/usagePaths.js +15 -0
  194. package/dist/src/workspace/agentRunner.js +301 -0
  195. package/dist/src/workspace/atomicJson.js +31 -0
  196. package/dist/src/workspace/capture.js +139 -0
  197. package/dist/src/workspace/checkpointer.js +228 -0
  198. package/dist/src/workspace/environment.js +71 -0
  199. package/dist/src/workspace/lock.js +137 -0
  200. package/dist/src/workspace/mapping.js +128 -0
  201. package/dist/src/workspace/operations.js +271 -0
  202. package/dist/src/workspace/paths.js +15 -0
  203. package/dist/src/workspace/planner.js +292 -0
  204. package/dist/src/workspace/restore.js +363 -0
  205. package/dist/src/workspace/runtime.js +318 -0
  206. package/dist/src/workspace/schema.js +156 -0
  207. package/dist/src/workspace/store.js +464 -0
  208. package/dist/src/workspace/tmuxAdapter.js +704 -0
  209. package/dist/src/workspaceProtocol.js +158 -0
  210. package/package.json +30 -17
  211. package/hooks/handmux-notify.sh +0 -22
  212. package/hooks/handmux-write.cjs +0 -78
  213. package/public/assets/index-C3N7OkyP.css +0 -32
  214. package/public/assets/index-D1DWcC5u.js +0 -337
  215. package/src/.gitkeep +0 -0
  216. package/src/agents/claude.js +0 -159
  217. package/src/agents/codex.js +0 -168
  218. package/src/agents/index.js +0 -21
  219. package/src/agents/processIdentity.js +0 -72
  220. package/src/agents/scanUtils.js +0 -225
  221. package/src/asr/iflyConfig.js +0 -10
  222. package/src/asr/iflySign.js +0 -16
  223. package/src/auth.js +0 -30
  224. package/src/browser/bootstrap.js +0 -68
  225. package/src/browser/cookieProfiles.js +0 -403
  226. package/src/browser/coordinator.js +0 -47
  227. package/src/browser/hammerheadCompat.js +0 -86
  228. package/src/browser/hammerheadRedirectCompat.js +0 -38
  229. package/src/browser/manager.js +0 -556
  230. package/src/browser/originLabel.js +0 -17
  231. package/src/browser/profilePersistence.js +0 -241
  232. package/src/browser/publicProxy.js +0 -175
  233. package/src/browser/routes.js +0 -154
  234. package/src/browser/sessionStore.js +0 -78
  235. package/src/browser/siteVersion.js +0 -124
  236. package/src/browser/targetPolicy.js +0 -137
  237. package/src/browser/workerClient.js +0 -253
  238. package/src/browser/workerServer.js +0 -98
  239. package/src/captureBackground.js +0 -93
  240. package/src/claudeEvents.js +0 -399
  241. package/src/cli/claudeHooks.js +0 -206
  242. package/src/cli/cloudflared.js +0 -99
  243. package/src/cli/codexManaged.js +0 -77
  244. package/src/cli/drivers.js +0 -97
  245. package/src/cli/hookScaffold.js +0 -33
  246. package/src/cli/i18n/en.js +0 -385
  247. package/src/cli/i18n/zh.js +0 -384
  248. package/src/cli/legacyCodexHooks.js +0 -49
  249. package/src/cli/options.js +0 -211
  250. package/src/cli/probe.js +0 -16
  251. package/src/cli/pushCmd.js +0 -60
  252. package/src/cli/service.js +0 -126
  253. package/src/cli/setupModel.js +0 -154
  254. package/src/cli/setupWizard.js +0 -506
  255. package/src/cli/shortcutEditor.js +0 -288
  256. package/src/cli/sshTunnel.js +0 -12
  257. package/src/cli/state.js +0 -90
  258. package/src/cli/statusLine.js +0 -90
  259. package/src/cli/supervisor.js +0 -183
  260. package/src/cli/supervisorProcesses.js +0 -57
  261. package/src/cli/tmuxVersion.js +0 -49
  262. package/src/cli/tunnelClients.js +0 -62
  263. package/src/cli/workspaceCmd.js +0 -300
  264. package/src/codexAppServer.js +0 -1620
  265. package/src/codexDiff.js +0 -108
  266. package/src/codexTranscriptParse.js +0 -413
  267. package/src/codexUsageSnapshot.cjs +0 -129
  268. package/src/config.js +0 -9
  269. package/src/docs.js +0 -228
  270. package/src/git.js +0 -208
  271. package/src/httpApi.js +0 -72
  272. package/src/jsonStore.js +0 -24
  273. package/src/notifications.js +0 -68
  274. package/src/orphans.js +0 -152
  275. package/src/pendingPrompt.js +0 -163
  276. package/src/previewServer.js +0 -168
  277. package/src/previews.js +0 -116
  278. package/src/push.js +0 -183
  279. package/src/routes/codex.js +0 -473
  280. package/src/routes/files.js +0 -172
  281. package/src/routes/git.js +0 -57
  282. package/src/routes/notifications.js +0 -19
  283. package/src/routes/previews.js +0 -35
  284. package/src/routes/push.js +0 -119
  285. package/src/routes/sessions.js +0 -168
  286. package/src/routes/system.js +0 -132
  287. package/src/routes/terminal.js +0 -210
  288. package/src/routes/transcript.js +0 -171
  289. package/src/routes/workspace.js +0 -267
  290. package/src/server.js +0 -186
  291. package/src/shortcutConfig.js +0 -75
  292. package/src/terminalStream.js +0 -391
  293. package/src/tmux/format.js +0 -45
  294. package/src/transcriptParse.js +0 -180
  295. package/src/transcriptReader.js +0 -101
  296. package/src/uploadTypes.js +0 -30
  297. package/src/urlPolicy.js +0 -14
  298. package/src/usage.js +0 -260
  299. package/src/workspace/agentRunner.js +0 -188
  300. package/src/workspace/atomicJson.js +0 -32
  301. package/src/workspace/capture.js +0 -139
  302. package/src/workspace/checkpointer.js +0 -187
  303. package/src/workspace/environment.js +0 -75
  304. package/src/workspace/lock.js +0 -128
  305. package/src/workspace/mapping.js +0 -124
  306. package/src/workspace/operations.js +0 -252
  307. package/src/workspace/paths.js +0 -16
  308. package/src/workspace/planner.js +0 -247
  309. package/src/workspace/restore.js +0 -279
  310. package/src/workspace/runtime.js +0 -314
  311. package/src/workspace/schema.js +0 -136
  312. package/src/workspace/store.js +0 -382
  313. package/src/workspace/tmuxAdapter.js +0 -554
  314. /package/{public → dist/public}/fonts/JetBrainsMonoNerdFontMono-Regular.woff2 +0 -0
  315. /package/{public → dist/public}/fonts/TWUnifont.woff2 +0 -0
  316. /package/{public → dist/public}/icons/apple-touch-icon.png +0 -0
  317. /package/{public → dist/public}/icons/badge-96.png +0 -0
  318. /package/{public → dist/public}/icons/icon-192-maskable.png +0 -0
  319. /package/{public → dist/public}/icons/icon-192.png +0 -0
  320. /package/{public → dist/public}/icons/icon-512-maskable.png +0 -0
  321. /package/{public → dist/public}/icons/icon-512.png +0 -0
  322. /package/{public → dist/public}/icons/logo.svg +0 -0
  323. /package/{public → dist/public}/manifest.webmanifest +0 -0
  324. /package/{public → dist/public}/offline.html +0 -0
  325. /package/{public → dist/public}/sw.js +0 -0
  326. /package/{src → dist/src}/browser/protocol.js +0 -0
  327. /package/{src → dist/src}/cli/cfNamed.js +0 -0
@@ -0,0 +1,225 @@
1
+ const DEFAULT_MAX_BUFFERED_EVENTS = 256;
2
+ const DEFAULT_IDLE_GRACE_MS = 5_000;
3
+ export class InteractionLiveHubError extends Error {
4
+ code;
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.code = code;
8
+ this.name = 'InteractionLiveHubError';
9
+ }
10
+ }
11
+ class EventQueue {
12
+ checkpoint;
13
+ #maxBufferedEvents;
14
+ #detach;
15
+ #values = [];
16
+ #waiting;
17
+ #closed = false;
18
+ #detached = false;
19
+ constructor(checkpoint, maxBufferedEvents, detach) {
20
+ this.checkpoint = Object.freeze(structuredClone(checkpoint));
21
+ this.#maxBufferedEvents = maxBufferedEvents;
22
+ this.#detach = detach;
23
+ }
24
+ [Symbol.asyncIterator]() { return this; }
25
+ next() {
26
+ const value = this.#values.shift();
27
+ if (value)
28
+ return Promise.resolve({ value: structuredClone(value), done: false });
29
+ if (this.#closed)
30
+ return Promise.resolve({ value: undefined, done: true });
31
+ return new Promise((resolve) => { this.#waiting = resolve; });
32
+ }
33
+ push(event) {
34
+ if (this.#closed)
35
+ return;
36
+ const cloned = structuredClone(event);
37
+ if (this.#waiting) {
38
+ const waiting = this.#waiting;
39
+ this.#waiting = undefined;
40
+ waiting({ value: cloned, done: false });
41
+ return;
42
+ }
43
+ if (this.#values.length >= this.#maxBufferedEvents) {
44
+ this.close();
45
+ return;
46
+ }
47
+ this.#values.push(cloned);
48
+ }
49
+ finishAfterDrain() {
50
+ if (this.#closed)
51
+ return;
52
+ this.#closed = true;
53
+ this.#detachOnce();
54
+ if (!this.#values.length && this.#waiting) {
55
+ const waiting = this.#waiting;
56
+ this.#waiting = undefined;
57
+ waiting({ value: undefined, done: true });
58
+ }
59
+ }
60
+ close() {
61
+ if (this.#closed)
62
+ return;
63
+ this.#closed = true;
64
+ this.#values.splice(0);
65
+ this.#detachOnce();
66
+ if (this.#waiting) {
67
+ const waiting = this.#waiting;
68
+ this.#waiting = undefined;
69
+ waiting({ value: undefined, done: true });
70
+ }
71
+ }
72
+ #detachOnce() {
73
+ if (this.#detached)
74
+ return;
75
+ this.#detached = true;
76
+ this.#detach();
77
+ }
78
+ }
79
+ // App-level fan-out only. InteractionService remains the owner of native observation, persistence,
80
+ // revision ordering, resolution tokens, and first-response-wins semantics.
81
+ export class InteractionLiveHub {
82
+ #interaction;
83
+ #maxBufferedEvents;
84
+ #idleGraceMs;
85
+ #observations = new Map();
86
+ #closed = false;
87
+ constructor({ interaction, maxBufferedEvents = DEFAULT_MAX_BUFFERED_EVENTS, idleGraceMs = DEFAULT_IDLE_GRACE_MS, }) {
88
+ if (!interaction || typeof interaction.open !== 'function'
89
+ || !Number.isSafeInteger(maxBufferedEvents) || maxBufferedEvents <= 0
90
+ || !Number.isSafeInteger(idleGraceMs) || idleGraceMs < 0) {
91
+ throw new TypeError('Interaction live hub requires a service and bounded queues');
92
+ }
93
+ this.#interaction = interaction;
94
+ this.#maxBufferedEvents = maxBufferedEvents;
95
+ this.#idleGraceMs = idleGraceMs;
96
+ }
97
+ async subscribe(run) {
98
+ if (this.#closed)
99
+ throw new InteractionLiveHubError('closed', 'Interaction live hub is closed');
100
+ let shared = this.#observations.get(run.ref.runId);
101
+ if (!shared)
102
+ shared = this.#create(run);
103
+ if (shared.run !== run || shared.terminated) {
104
+ throw new InteractionLiveHubError('stale_run', 'Interaction live run is no longer current');
105
+ }
106
+ if (shared.idleTimer) {
107
+ clearTimeout(shared.idleTimer);
108
+ shared.idleTimer = undefined;
109
+ }
110
+ await shared.ready;
111
+ if (!shared.checkpoint || shared.terminated) {
112
+ throw new InteractionLiveHubError('unavailable', 'Interaction live observation is unavailable');
113
+ }
114
+ let subscription;
115
+ subscription = new EventQueue(shared.checkpoint, this.#maxBufferedEvents, () => {
116
+ this.#detach(shared, subscription);
117
+ });
118
+ shared.subscribers.add(subscription);
119
+ return subscription;
120
+ }
121
+ close() {
122
+ if (this.#closed)
123
+ return;
124
+ this.#closed = true;
125
+ for (const shared of [...this.#observations.values()])
126
+ this.#terminate(shared);
127
+ }
128
+ #create(run) {
129
+ const shared = {
130
+ run,
131
+ ready: Promise.resolve(),
132
+ subscribers: new Set(),
133
+ idleTimer: undefined,
134
+ terminated: false,
135
+ };
136
+ this.#observations.set(run.ref.runId, shared);
137
+ shared.ready = this.#interaction.open(run, (event) => this.#publish(shared, event)).then((handle) => {
138
+ if (shared.terminated) {
139
+ try {
140
+ void Promise.resolve(handle.close()).catch(() => { });
141
+ }
142
+ catch { /* best effort */ }
143
+ throw new Error('Interaction live run was revoked during open');
144
+ }
145
+ shared.handle = handle;
146
+ shared.checkpoint = {
147
+ revision: handle.revision,
148
+ pending: structuredClone(handle.pending),
149
+ };
150
+ void handle.closed.then(() => this.#terminate(shared, true));
151
+ }).catch((error) => {
152
+ this.#terminate(shared);
153
+ throw error;
154
+ });
155
+ return shared;
156
+ }
157
+ #publish(shared, event) {
158
+ if (shared.terminated)
159
+ return;
160
+ const checkpoint = shared.checkpoint;
161
+ if (!checkpoint || !Number.isSafeInteger(event.revision)
162
+ || event.revision !== checkpoint.revision + 1) {
163
+ this.#terminate(shared);
164
+ throw new InteractionLiveHubError('unavailable', 'Interaction revision is invalid');
165
+ }
166
+ const pending = new Map(checkpoint.pending.map((item) => [item.id, item]));
167
+ if (event.type === 'opened') {
168
+ if (event.interaction.runId !== shared.run.ref.runId || pending.has(event.interaction.id)) {
169
+ this.#terminate(shared);
170
+ throw new InteractionLiveHubError('unavailable', 'Interaction opened event is invalid');
171
+ }
172
+ pending.set(event.interaction.id, structuredClone(event.interaction));
173
+ }
174
+ else if (!pending.delete(event.interactionId)) {
175
+ this.#terminate(shared);
176
+ throw new InteractionLiveHubError('unavailable', 'Interaction terminal event is invalid');
177
+ }
178
+ shared.checkpoint = {
179
+ revision: event.revision,
180
+ pending: [...pending.values()],
181
+ };
182
+ for (const subscriber of [...shared.subscribers])
183
+ subscriber.push(event);
184
+ }
185
+ #detach(shared, subscription) {
186
+ shared.subscribers.delete(subscription);
187
+ this.#idle(shared);
188
+ }
189
+ #idle(shared) {
190
+ if (shared.terminated || shared.subscribers.size || shared.idleTimer)
191
+ return;
192
+ if (this.#idleGraceMs === 0) {
193
+ this.#terminate(shared);
194
+ return;
195
+ }
196
+ shared.idleTimer = setTimeout(() => {
197
+ shared.idleTimer = undefined;
198
+ if (!shared.subscribers.size)
199
+ this.#terminate(shared);
200
+ }, this.#idleGraceMs);
201
+ shared.idleTimer.unref?.();
202
+ }
203
+ #terminate(shared, drain = false) {
204
+ if (shared.terminated)
205
+ return;
206
+ shared.terminated = true;
207
+ if (this.#observations.get(shared.run.ref.runId) === shared) {
208
+ this.#observations.delete(shared.run.ref.runId);
209
+ }
210
+ if (shared.idleTimer)
211
+ clearTimeout(shared.idleTimer);
212
+ shared.idleTimer = undefined;
213
+ for (const subscriber of [...shared.subscribers]) {
214
+ if (drain)
215
+ subscriber.finishAfterDrain();
216
+ else
217
+ subscriber.close();
218
+ }
219
+ shared.subscribers.clear();
220
+ try {
221
+ void Promise.resolve(shared.handle?.close()).catch(() => { });
222
+ }
223
+ catch { /* close is best effort */ }
224
+ }
225
+ }
@@ -0,0 +1,18 @@
1
+ import { PrivateStateStore } from '../privateStateStore.js';
2
+ export class MemoryInteractionStateStore {
3
+ #state = null;
4
+ load() {
5
+ return this.#state === null ? null : structuredClone(this.#state);
6
+ }
7
+ save(state) {
8
+ this.#state = structuredClone(state);
9
+ }
10
+ }
11
+ export class FileInteractionStateStore {
12
+ #store;
13
+ constructor(file) {
14
+ this.#store = new PrivateStateStore(file);
15
+ }
16
+ load() { return this.#store.readStrict(); }
17
+ save(state) { this.#store.write(state); }
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,70 @@
1
+ const LEGACY_KIND = {
2
+ working: 'working',
3
+ waiting: 'permission',
4
+ done: 'done',
5
+ error: 'error',
6
+ };
7
+ const LEGACY_MESSAGE = {
8
+ working: '进行中',
9
+ waiting: '需要你',
10
+ done: '已完成',
11
+ error: '出错',
12
+ };
13
+ function sameRun(record, run) {
14
+ return record.run.agentId === run.agentId
15
+ && record.run.paneId === run.paneId
16
+ && record.run.runId === run.runId
17
+ && record.run.sessionId === run.sessionId;
18
+ }
19
+ function sameTerminalEvent(candidate, run, eventId) {
20
+ if (candidate.agentId !== run.agentId || candidate.eventId !== eventId)
21
+ return false;
22
+ if (candidate.sessionId !== undefined && run.sessionId !== undefined) {
23
+ return candidate.sessionId === run.sessionId;
24
+ }
25
+ return candidate.runId === run.runId && candidate.paneId === run.paneId;
26
+ }
27
+ // Temporary compatibility projection for the existing phone roster. Runtime/Inbox remain the only
28
+ // identity and state authorities; tmux contributes presentation location only.
29
+ export function projectLegacyInboxStates({ snapshot, runs, panes, allowedSessions, }) {
30
+ const allowed = allowedSessions === null ? null : new Set(allowedSessions);
31
+ const paneById = new Map(panes.map((pane) => [pane.paneId, pane]));
32
+ const recordByRun = new Map(snapshot.records.map((record) => [record.run.runId, record]));
33
+ const out = {};
34
+ for (const run of runs) {
35
+ const pane = paneById.get(run.paneId);
36
+ if (allowed && (!pane || !allowed.has(pane.sessionName)))
37
+ continue;
38
+ const record = recordByRun.get(run.runId);
39
+ const current = record && sameRun(record, run) ? record : undefined;
40
+ const terminal = current && (current.state === 'done' || current.state === 'error');
41
+ const notifications = terminal && current.eventId
42
+ ? snapshot.terminalNotifications.filter((candidate) => (sameTerminalEvent(candidate, run, current.eventId))) : [];
43
+ // One event may have legacy duplicates from Server restarts. Any read copy makes the durable event
44
+ // read; otherwise keep one canonical notification id so a click marks every duplicate together.
45
+ const notification = notifications.find((candidate) => candidate.readAt !== undefined)
46
+ ?? notifications[0];
47
+ const displayTime = terminal
48
+ ? current?.sourceOccurredAt ?? current?.acceptedAt
49
+ : current?.acceptedAt;
50
+ out[run.paneId] = {
51
+ ...(pane === undefined ? {} : {
52
+ session: pane.sessionName,
53
+ window: pane.windowId,
54
+ windowName: pane.windowName,
55
+ }),
56
+ kind: current ? LEGACY_KIND[current.state] : null,
57
+ // `reason` is a machine-readable state code. Adapters may use it for recovery/diagnostics,
58
+ // but the user-facing roster must never render values such as `agent_end_idle` verbatim.
59
+ msg: current?.message ?? (current ? LEGACY_MESSAGE[current.state] : ''),
60
+ ...(displayTime === undefined ? {} : { ts: displayTime }),
61
+ agent: run.agentId,
62
+ ...(terminal ? {
63
+ terminalUnread: notifications.length > 0
64
+ && notifications.every((candidate) => candidate.readAt === undefined),
65
+ } : {}),
66
+ ...(notification === undefined ? {} : { terminalNotificationId: notification.id }),
67
+ };
68
+ }
69
+ return out;
70
+ }
@@ -0,0 +1,336 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { parseCodexOutboxSnapshot } from '../codexQueueProtocol.js';
5
+ import { PrivateStateStore } from '../privateStateStore.js';
6
+ import { parseConversationState } from './conversationStore.js';
7
+ const DELIVERY_RECEIPT_TTL_MS = 24 * 60 * 60 * 1_000;
8
+ function payloadHash(text) {
9
+ return crypto.createHash('sha256').update(text).digest('hex');
10
+ }
11
+ function migrationId(threadId, itemId, text) {
12
+ return `codex-legacy:${crypto.createHash('sha256').update(`${threadId}\0${itemId}\0${text}`)
13
+ .digest('hex').slice(0, 40)}`;
14
+ }
15
+ function fsyncDirectory(directory) {
16
+ let descriptor;
17
+ try {
18
+ descriptor = fs.openSync(directory, 'r');
19
+ fs.fsyncSync(descriptor);
20
+ }
21
+ finally {
22
+ if (descriptor !== undefined)
23
+ fs.closeSync(descriptor);
24
+ }
25
+ }
26
+ function verifyBackup(file, fingerprint) {
27
+ if (!fs.existsSync(file)) {
28
+ throw new Error('Conversation migration marker exists but the Codex outbox backup is missing');
29
+ }
30
+ const actual = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
31
+ if (actual !== fingerprint) {
32
+ throw new Error('Codex outbox migration backup does not match its marker');
33
+ }
34
+ }
35
+ function verifyMarkerSubmissions(state, marker) {
36
+ if (!marker.submissionKeys) {
37
+ throw new Error('Conversation migration marker has no scoped submission keys');
38
+ }
39
+ for (const key of marker.submissionKeys) {
40
+ const [agentId, sessionId, clientRequestId] = key.split('\0');
41
+ if (!agentId || !sessionId || !clientRequestId || !(state.submissions.some((submission) => (submission.agentId === agentId && submission.sessionId === sessionId
42
+ && submission.clientRequestId === clientRequestId)) || state.deliveryReceipts?.some((receipt) => receipt.agentId === agentId
43
+ && receipt.sessionId === sessionId && receipt.clientRequestId === clientRequestId))) {
44
+ throw new Error('Conversation migration marker is missing a referenced delivery');
45
+ }
46
+ }
47
+ }
48
+ function requiredKeysFromBackup(file) {
49
+ if (!fs.existsSync(file))
50
+ return null;
51
+ const legacy = parseCodexOutboxSnapshot(JSON.parse(fs.readFileSync(file, 'utf8')));
52
+ if (!legacy)
53
+ throw new Error('Legacy Codex outbox backup is corrupt');
54
+ const keys = legacy.queues.flatMap((queue) => queue.items.map((item) => (`codex\0${queue.threadId}\0${item.requestId ?? migrationId(queue.threadId, item.id, item.text)}`)));
55
+ for (const receipt of legacy.receipts) {
56
+ if (receipt.status === 'pending' || receipt.status === 'accepted') {
57
+ keys.push(`codex\0${receipt.threadId}\0${receipt.requestId}`);
58
+ }
59
+ }
60
+ return [...new Set(keys)];
61
+ }
62
+ function verifyRequiredDeliveries(state, keys) {
63
+ for (const key of keys) {
64
+ const [agentId, sessionId, clientRequestId] = key.split('\0');
65
+ if (!(state.submissions.some((submission) => submission.agentId === agentId
66
+ && submission.sessionId === sessionId && submission.clientRequestId === clientRequestId)
67
+ || state.deliveryReceipts?.some((receipt) => receipt.agentId === agentId
68
+ && receipt.sessionId === sessionId && receipt.clientRequestId === clientRequestId))) {
69
+ throw new Error('Conversation migration is missing a queued or uncertain delivery');
70
+ }
71
+ }
72
+ }
73
+ function finalizeMigration(legacyFile, conversationFile, state, fingerprint) {
74
+ const marker = state.migrations?.legacyCodexOutboxImported;
75
+ if (marker)
76
+ verifyMarkerSubmissions(state, marker);
77
+ const backup = `${legacyFile}.imported.${fingerprint}.json`;
78
+ const required = requiredKeysFromBackup(backup);
79
+ if (required)
80
+ verifyRequiredDeliveries(state, required);
81
+ for (const file of [legacyFile, backup]) {
82
+ try {
83
+ fs.unlinkSync(file);
84
+ }
85
+ catch (error) {
86
+ if (!(error instanceof Error) || !('code' in error) || error.code !== 'ENOENT')
87
+ throw error;
88
+ }
89
+ }
90
+ fsyncDirectory(path.dirname(legacyFile));
91
+ if (state.migrations?.legacyCodexOutboxImported) {
92
+ delete state.migrations.legacyCodexOutboxImported;
93
+ if (Object.keys(state.migrations).length === 0)
94
+ delete state.migrations;
95
+ new PrivateStateStore(conversationFile).write(state);
96
+ fsyncDirectory(path.dirname(conversationFile));
97
+ }
98
+ }
99
+ function exactMarkerSet(actual, expected) {
100
+ if (actual.length !== expected.length)
101
+ return false;
102
+ const values = new Set(actual);
103
+ return values.size === actual.length && expected.every((value) => values.has(value));
104
+ }
105
+ function acquireLock(file) {
106
+ const lock = `${file}.migration.lock`;
107
+ for (let attempt = 0; attempt < 2; attempt += 1) {
108
+ try {
109
+ const descriptor = fs.openSync(lock, 'wx', 0o600);
110
+ fs.writeFileSync(descriptor, JSON.stringify({ pid: process.pid, createdAt: Date.now() }));
111
+ fs.fsyncSync(descriptor);
112
+ fs.closeSync(descriptor);
113
+ return () => { try {
114
+ fs.unlinkSync(lock);
115
+ }
116
+ catch { /* already released */ } };
117
+ }
118
+ catch (error) {
119
+ if (!(error instanceof Error) || !('code' in error) || error.code !== 'EEXIST')
120
+ throw error;
121
+ let owner = 0;
122
+ try {
123
+ owner = Number(JSON.parse(fs.readFileSync(lock, 'utf8')).pid);
124
+ }
125
+ catch { /* stale */ }
126
+ try {
127
+ if (owner > 0)
128
+ process.kill(owner, 0);
129
+ throw new Error('Codex outbox migration is already running');
130
+ }
131
+ catch (probe) {
132
+ if (probe instanceof Error && !('code' in probe && probe.code === 'ESRCH'))
133
+ throw probe;
134
+ fs.unlinkSync(lock);
135
+ }
136
+ }
137
+ }
138
+ throw new Error('Could not acquire Codex outbox migration lock');
139
+ }
140
+ export function migrateLegacyCodexOutbox(legacyFile, conversationFile, now = Date.now()) {
141
+ if (!fs.existsSync(legacyFile)) {
142
+ if (!fs.existsSync(conversationFile))
143
+ return { imported: 0 };
144
+ fsyncDirectory(path.dirname(conversationFile));
145
+ const stateStore = new PrivateStateStore(conversationFile);
146
+ const state = parseConversationState(stateStore.readStrict());
147
+ const marker = state.migrations?.legacyCodexOutboxImported;
148
+ if (!marker)
149
+ return { imported: 0 };
150
+ const backup = `${legacyFile}.imported.${marker.fingerprint}.json`;
151
+ if (fs.existsSync(backup))
152
+ verifyBackup(backup, marker.fingerprint);
153
+ finalizeMigration(legacyFile, conversationFile, state, marker.fingerprint);
154
+ return { imported: 0, fingerprint: marker.fingerprint };
155
+ }
156
+ const release = acquireLock(legacyFile);
157
+ try {
158
+ const bytes = fs.readFileSync(legacyFile);
159
+ const fingerprint = crypto.createHash('sha256').update(bytes).digest('hex');
160
+ let raw;
161
+ try {
162
+ raw = JSON.parse(bytes.toString('utf8'));
163
+ }
164
+ catch {
165
+ throw new Error('Legacy Codex outbox is truncated or invalid JSON');
166
+ }
167
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)
168
+ && raw.migratedToConversationCore === true) {
169
+ const state = parseConversationState(new PrivateStateStore(conversationFile).readStrict());
170
+ const marker = state.migrations?.legacyCodexOutboxImported;
171
+ if (!marker || marker.fingerprint !== raw.fingerprint) {
172
+ throw new Error('Codex outbox tombstone does not match Conversation migration marker');
173
+ }
174
+ const backup = `${legacyFile}.imported.${marker.fingerprint}.json`;
175
+ if (fs.existsSync(backup))
176
+ verifyBackup(backup, marker.fingerprint);
177
+ finalizeMigration(legacyFile, conversationFile, state, marker.fingerprint);
178
+ return { imported: 0, fingerprint: marker.fingerprint };
179
+ }
180
+ const legacy = parseCodexOutboxSnapshot(raw);
181
+ if (!legacy)
182
+ throw new Error('Unsupported or corrupt legacy Codex outbox schema');
183
+ const store = new PrivateStateStore(conversationFile);
184
+ const state = parseConversationState(store.readStrict());
185
+ const existingMarker = state.migrations?.legacyCodexOutboxImported;
186
+ const expectedIds = [];
187
+ const expectedKeys = [];
188
+ const byKey = new Map(state.submissions.map((item) => [`${item.agentId}\0${item.sessionId}\0${item.clientRequestId}`, item]));
189
+ const receiptsByKey = new Map((state.deliveryReceipts ?? []).map((item) => [`${item.agentId}\0${item.sessionId}\0${item.clientRequestId}`, item]));
190
+ const queuedByNativeId = new Map();
191
+ let imported = 0;
192
+ for (const queue of legacy.queues) {
193
+ for (const item of queue.items) {
194
+ const id = item.requestId ?? migrationId(queue.threadId, item.id, item.text);
195
+ const key = `codex\0${queue.threadId}\0${id}`;
196
+ expectedIds.push(id);
197
+ expectedKeys.push(key);
198
+ const existing = byKey.get(key);
199
+ if (existing) {
200
+ if (existing.text !== item.text)
201
+ throw new Error('Legacy Codex queue conflicts with Conversation ledger');
202
+ queuedByNativeId.set(`${queue.pane}\0${queue.threadId}\0${item.id}`, existing);
203
+ continue;
204
+ }
205
+ const revision = ++state.ledgerRevision;
206
+ const submission = {
207
+ agentId: 'codex', sessionId: queue.threadId, clientRequestId: id,
208
+ text: item.text, payloadHash: payloadHash(item.text), state: 'queued', revision,
209
+ queueOrderKey: `${String(Math.floor(item.createdAt)).padStart(16, '0')}:${String(revision).padStart(16, '0')}`,
210
+ lastRunId: queue.pane, createdAt: item.createdAt, updatedAt: item.createdAt,
211
+ };
212
+ state.submissions.push(submission);
213
+ byKey.set(key, submission);
214
+ queuedByNativeId.set(`${queue.pane}\0${queue.threadId}\0${item.id}`, submission);
215
+ imported += 1;
216
+ }
217
+ }
218
+ for (const receipt of legacy.receipts) {
219
+ const key = `codex\0${receipt.threadId}\0${receipt.requestId}`;
220
+ const existing = byKey.get(key);
221
+ const existingReceipt = receiptsByKey.get(key);
222
+ if (receipt.status === 'queued') {
223
+ const queued = queuedByNativeId.get(`${receipt.pane}\0${receipt.threadId}\0${receipt.queueItemId}`);
224
+ if (!queued || queued.clientRequestId !== receipt.requestId || queued.text !== receipt.text) {
225
+ throw new Error('Legacy Codex queued receipt has no matching queue item');
226
+ }
227
+ continue;
228
+ }
229
+ if (receipt.status === 'pending' || receipt.status === 'accepted') {
230
+ expectedIds.push(receipt.requestId);
231
+ expectedKeys.push(key);
232
+ }
233
+ if (existingReceipt) {
234
+ if (existingReceipt.payloadHash !== payloadHash(receipt.text)) {
235
+ throw new Error('Legacy Codex receipt conflicts with Conversation delivery receipt');
236
+ }
237
+ continue;
238
+ }
239
+ if (existing) {
240
+ if (existing.text !== receipt.text)
241
+ throw new Error('Legacy Codex receipt conflicts with Conversation ledger');
242
+ if (receipt.status !== 'accepted')
243
+ continue;
244
+ }
245
+ if (receipt.status === 'accepted') {
246
+ const settled = {
247
+ agentId: 'codex', sessionId: receipt.threadId,
248
+ clientRequestId: receipt.requestId,
249
+ payloadHash: existing?.payloadHash ?? payloadHash(receipt.text),
250
+ ...(existing?.baseline === undefined ? {} : { baseline: structuredClone(existing.baseline) }),
251
+ ...(receipt.turnId ?? existing?.nativeId
252
+ ? { nativeId: (receipt.turnId ?? existing?.nativeId) } : {}),
253
+ ...(existing?.steerActionId === undefined ? {} : { steerActionId: existing.steerActionId }),
254
+ ...(existing?.steerBaseRevision === undefined
255
+ ? {} : { steerBaseRevision: existing.steerBaseRevision }),
256
+ ...(existing?.steerAnchor === undefined
257
+ ? {} : { steerAnchor: structuredClone(existing.steerAnchor) }),
258
+ ...(existing?.steerAttempts === undefined
259
+ ? {} : { steerAttempts: structuredClone(existing.steerAttempts) }),
260
+ acceptedAt: receipt.updatedAt,
261
+ expiresAt: receipt.updatedAt + DELIVERY_RECEIPT_TTL_MS,
262
+ };
263
+ if (existing) {
264
+ state.submissions = state.submissions.filter((candidate) => candidate !== existing);
265
+ byKey.delete(key);
266
+ }
267
+ state.deliveryReceipts ??= [];
268
+ state.deliveryReceipts.push(settled);
269
+ receiptsByKey.set(key, settled);
270
+ imported += 1;
271
+ continue;
272
+ }
273
+ const revision = ++state.ledgerRevision;
274
+ const submission = {
275
+ agentId: 'codex', sessionId: receipt.threadId, clientRequestId: receipt.requestId,
276
+ text: receipt.text, payloadHash: payloadHash(receipt.text),
277
+ state: 'unknown', revision,
278
+ dispatchOrigin: 'direct',
279
+ ...(receipt.turnId ? { nativeId: receipt.turnId } : {}),
280
+ lastRunId: receipt.pane, createdAt: receipt.createdAt, updatedAt: receipt.updatedAt,
281
+ };
282
+ state.submissions.push(submission);
283
+ byKey.set(key, submission);
284
+ imported += 1;
285
+ }
286
+ const uniqueIds = [...new Set(expectedIds)].sort();
287
+ const uniqueKeys = [...new Set(expectedKeys)].sort();
288
+ if (existingMarker) {
289
+ if (existingMarker.fingerprint !== fingerprint
290
+ || (existingMarker.submissionKeys
291
+ ? !exactMarkerSet(existingMarker.submissionKeys, uniqueKeys)
292
+ : !exactMarkerSet(existingMarker.submissionIds, uniqueIds))) {
293
+ throw new Error('Legacy Codex outbox migration marker conflicts with its source');
294
+ }
295
+ const needsMarkerUpgrade = !existingMarker.submissionKeys;
296
+ if (needsMarkerUpgrade)
297
+ existingMarker.submissionKeys = uniqueKeys;
298
+ if (imported > 0 || needsMarkerUpgrade)
299
+ store.write(state);
300
+ }
301
+ else {
302
+ state.migrations ??= {};
303
+ state.migrations.legacyCodexOutboxImported = {
304
+ fingerprint, submissionIds: uniqueIds, submissionKeys: uniqueKeys, importedAt: now,
305
+ };
306
+ store.write(state);
307
+ }
308
+ // PrivateStateStore makes the file contents durable before rename. Persist the directory entry too,
309
+ // then re-read the committed ledger before the only recoverable legacy source is moved aside.
310
+ fsyncDirectory(path.dirname(conversationFile));
311
+ const verified = parseConversationState(store.readStrict());
312
+ for (const key of uniqueKeys) {
313
+ const [agentId, sessionId, clientRequestId] = key.split('\0');
314
+ if (!(verified.submissions.some((submission) => submission.agentId === agentId
315
+ && submission.sessionId === sessionId && submission.clientRequestId === clientRequestId)
316
+ || verified.deliveryReceipts?.some((receipt) => receipt.agentId === agentId
317
+ && receipt.sessionId === sessionId && receipt.clientRequestId === clientRequestId))) {
318
+ throw new Error('Conversation migration marker is missing a referenced delivery');
319
+ }
320
+ }
321
+ const backup = `${legacyFile}.imported.${fingerprint}.json`;
322
+ if (!fs.existsSync(backup))
323
+ fs.renameSync(legacyFile, backup);
324
+ else {
325
+ verifyBackup(backup, fingerprint);
326
+ if (fs.existsSync(legacyFile))
327
+ fs.unlinkSync(legacyFile);
328
+ }
329
+ fsyncDirectory(path.dirname(legacyFile));
330
+ finalizeMigration(legacyFile, conversationFile, verified, fingerprint);
331
+ return { imported, fingerprint };
332
+ }
333
+ finally {
334
+ release();
335
+ }
336
+ }
@@ -0,0 +1 @@
1
+ export {};