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,199 @@
1
+ import { codexGoalMessageId, codexItemMessageId } from './codexMessageIdentity.js';
2
+ function textMutation(event) {
3
+ const id = codexItemMessageId(event.turnId, event.itemId);
4
+ if (!id)
5
+ return null;
6
+ const completed = event.type === 'completed' || event.completed === true;
7
+ return {
8
+ operation: 'upsert',
9
+ mode: event.type === 'delta' ? 'append' : 'replace',
10
+ message: {
11
+ id,
12
+ role: 'assistant',
13
+ type: 'text',
14
+ turnId: event.turnId,
15
+ itemId: event.itemId,
16
+ text: event.type === 'delta' ? event.delta ?? '' : event.text ?? '',
17
+ completed,
18
+ streaming: !completed,
19
+ },
20
+ };
21
+ }
22
+ export function projectCodexConversationMutation(event) {
23
+ if (event.type === 'conversation')
24
+ return event.mutation;
25
+ if (event.type === 'started' || event.type === 'snapshot'
26
+ || event.type === 'delta' || event.type === 'completed') {
27
+ return textMutation(event);
28
+ }
29
+ if (event.type === 'turnCompleted') {
30
+ return event.turnId ? { operation: 'settleTurn', turnId: event.turnId } : null;
31
+ }
32
+ if (event.type === 'goal') {
33
+ if (['blocked', 'usageLimited', 'budgetLimited', 'complete'].includes(event.goal.status)
34
+ && !event.turnId)
35
+ return null;
36
+ const id = codexGoalMessageId(event.goal, event.event);
37
+ if (!id)
38
+ return null;
39
+ return {
40
+ operation: 'upsert',
41
+ mode: 'replace',
42
+ message: {
43
+ id,
44
+ role: 'assistant',
45
+ type: 'goal',
46
+ turnId: event.turnId,
47
+ event: event.event,
48
+ goal: event.goal,
49
+ completed: true,
50
+ },
51
+ };
52
+ }
53
+ // Clearing the current Goal changes session state, not an already recorded conversation card.
54
+ return null;
55
+ }
56
+ function recordOf(value) {
57
+ return value != null && typeof value === 'object' && !Array.isArray(value)
58
+ ? value
59
+ : null;
60
+ }
61
+ function nonEmpty(value) {
62
+ return typeof value === 'string' && value ? value : null;
63
+ }
64
+ export function parseCodexConversationMutationValue(value) {
65
+ const record = recordOf(value);
66
+ if (!record)
67
+ return null;
68
+ if (record.operation === 'settleTurn') {
69
+ const turnId = nonEmpty(record.turnId);
70
+ return turnId ? { operation: 'settleTurn', turnId } : null;
71
+ }
72
+ if (record.operation !== 'upsert' || !['append', 'replace'].includes(String(record.mode)))
73
+ return null;
74
+ const message = recordOf(record.message);
75
+ if (!message)
76
+ return null;
77
+ const id = nonEmpty(message.id);
78
+ const turnId = nonEmpty(message.turnId);
79
+ if (!id || message.role !== 'assistant' || typeof message.completed !== 'boolean')
80
+ return null;
81
+ if (message.type === 'text') {
82
+ const itemId = nonEmpty(message.itemId);
83
+ if (!turnId || !itemId || id !== codexItemMessageId(turnId, itemId)
84
+ || typeof message.text !== 'string' || typeof message.streaming !== 'boolean')
85
+ return null;
86
+ return {
87
+ operation: 'upsert', mode: record.mode,
88
+ message: {
89
+ id, role: 'assistant', type: 'text', turnId, itemId, text: message.text,
90
+ completed: message.completed, streaming: message.streaming,
91
+ },
92
+ };
93
+ }
94
+ const goal = recordOf(message.goal);
95
+ const event = nonEmpty(message.event);
96
+ const itemId = nonEmpty(message.itemId);
97
+ const statuses = ['active', 'paused', 'blocked', 'usageLimited', 'budgetLimited', 'complete'];
98
+ const events = ['set', 'restarted', ...statuses];
99
+ const objective = nonEmpty(goal?.objective);
100
+ const numericKeys = ['createdAt', 'updatedAt', 'tokensUsed', 'timeUsedSeconds'];
101
+ const invalidNumber = numericKeys.some((key) => goal && Object.hasOwn(goal, key)
102
+ && (typeof goal[key] !== 'number' || !Number.isFinite(goal[key])));
103
+ const invalidBudget = goal && Object.hasOwn(goal, 'tokenBudget') && goal.tokenBudget !== null
104
+ && (typeof goal.tokenBudget !== 'number' || !Number.isFinite(goal.tokenBudget));
105
+ if (message.type !== 'goal' || message.completed !== true || !goal || !objective
106
+ || !statuses.includes(String(goal.status)) || !event || !events.includes(event)
107
+ || invalidNumber || invalidBudget
108
+ || (message.turnId !== null && !turnId)
109
+ || (id !== codexGoalMessageId(goal, event)
110
+ && (!turnId || !itemId || id !== codexItemMessageId(turnId, itemId))))
111
+ return null;
112
+ return {
113
+ operation: 'upsert', mode: record.mode,
114
+ message: {
115
+ id, role: 'assistant', type: 'goal', turnId: turnId || null, event,
116
+ goal: goal, completed: true,
117
+ },
118
+ };
119
+ }
120
+ // The raw App Server fields remain on the event for compatibility, but consumers only receive this
121
+ // canonical mutation. Reconstructing the mutation from the validated event prevents a forged duplicate
122
+ // payload from smuggling a second identity or lifecycle into the Web projection.
123
+ export function parseCodexConversationMutation(value, event) {
124
+ const expected = projectCodexConversationMutation(event);
125
+ if (!expected)
126
+ return value === null ? null : undefined;
127
+ const supplied = parseCodexConversationMutationValue(value);
128
+ if (!supplied)
129
+ return undefined;
130
+ if (expected.operation === 'settleTurn') {
131
+ if (supplied.operation !== 'settleTurn')
132
+ return undefined;
133
+ return supplied.turnId === expected.turnId ? expected : undefined;
134
+ }
135
+ if (supplied.operation !== 'upsert')
136
+ return undefined;
137
+ const message = supplied.message;
138
+ if (supplied.mode !== expected.mode)
139
+ return undefined;
140
+ const projected = expected.message;
141
+ if (message.id !== projected.id || message.role !== projected.role
142
+ || message.type !== projected.type || message.turnId !== projected.turnId
143
+ || message.completed !== projected.completed)
144
+ return undefined;
145
+ if (projected.type === 'text') {
146
+ if (message.type !== 'text')
147
+ return undefined;
148
+ if (message.itemId !== projected.itemId || message.text !== projected.text
149
+ || message.streaming !== projected.streaming)
150
+ return undefined;
151
+ }
152
+ else {
153
+ if (message.type !== 'goal' || message.event !== projected.event)
154
+ return undefined;
155
+ }
156
+ return expected;
157
+ }
158
+ export function projectCodexRolloutMutation(value) {
159
+ const message = recordOf(value);
160
+ const id = nonEmpty(message?.id);
161
+ if (!message || !id || message.role !== 'assistant')
162
+ return null;
163
+ if (message.type === 'text') {
164
+ const turnId = nonEmpty(message.turnId);
165
+ const itemId = nonEmpty(message.itemId);
166
+ if (!turnId || !itemId || typeof message.text !== 'string'
167
+ || id !== codexItemMessageId(turnId, itemId))
168
+ return null;
169
+ return {
170
+ operation: 'upsert', mode: 'replace',
171
+ message: {
172
+ id, role: 'assistant', type: 'text', turnId, itemId, text: message.text,
173
+ completed: true, streaming: false,
174
+ },
175
+ };
176
+ }
177
+ if (message.type !== 'goal')
178
+ return null;
179
+ const parsed = parseCodexConversationMutationValue({
180
+ operation: 'upsert', mode: 'replace',
181
+ message: { ...message, completed: true },
182
+ });
183
+ return parsed?.operation === 'upsert' ? parsed : null;
184
+ }
185
+ export function reconcileCodexRolloutMessages(previous, liveMessageIds, messages) {
186
+ const fingerprints = new Map();
187
+ const mutations = [];
188
+ for (const value of messages) {
189
+ const mutation = projectCodexRolloutMutation(value);
190
+ if (!mutation || mutation.operation !== 'upsert')
191
+ continue;
192
+ const fingerprint = JSON.stringify(mutation.message);
193
+ fingerprints.set(mutation.message.id, fingerprint);
194
+ if (liveMessageIds.has(mutation.message.id)
195
+ && previous.get(mutation.message.id) !== fingerprint)
196
+ mutations.push(mutation);
197
+ }
198
+ return { fingerprints, mutations };
199
+ }
@@ -0,0 +1,118 @@
1
+ const HUNK_HEADER = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
2
+ function changedLines(lines) {
3
+ return lines.filter((line) => /^[+-]/.test(line)
4
+ && !line.startsWith('+++') && !line.startsWith('---'));
5
+ }
6
+ export function parseUnifiedDiff(value, { created = false } = {}) {
7
+ const lines = String(value || '').split('\n');
8
+ let added = 0;
9
+ let removed = 0;
10
+ const hunks = [];
11
+ let current = null;
12
+ const flush = () => {
13
+ if (current?.lines.length)
14
+ hunks.push(current);
15
+ current = null;
16
+ };
17
+ for (const line of lines) {
18
+ const header = HUNK_HEADER.exec(line);
19
+ if (header) {
20
+ flush();
21
+ current = { oldStart: Number(header[1]), newStart: Number(header[2]), lines: [] };
22
+ continue;
23
+ }
24
+ if (line.startsWith('+') && !line.startsWith('+++'))
25
+ added++;
26
+ else if (line.startsWith('-') && !line.startsWith('---'))
27
+ removed++;
28
+ if (current && /^[ +\-]/.test(line) && !line.startsWith('+++') && !line.startsWith('---')) {
29
+ current.lines.push(line);
30
+ }
31
+ }
32
+ flush();
33
+ return {
34
+ added,
35
+ removed,
36
+ hunks: hunks.length ? hunks : null,
37
+ ...(created ? { created: true } : {}),
38
+ };
39
+ }
40
+ function applyPatchSection(value, path) {
41
+ const lines = String(value || '').split('\n');
42
+ let active = false;
43
+ const section = [];
44
+ for (const line of lines) {
45
+ const header = /^\*\*\* (?:Add|Update|Delete) File: (.+)$/.exec(line);
46
+ if (header) {
47
+ if (active)
48
+ break;
49
+ active = (header[1] ?? '').trim() === path;
50
+ continue;
51
+ }
52
+ if (active && line === '*** End Patch')
53
+ break;
54
+ if (active)
55
+ section.push(line);
56
+ }
57
+ return section;
58
+ }
59
+ function diffKey(lines) {
60
+ return changedLines(lines).join('\0');
61
+ }
62
+ function sameFilePath(left, right) {
63
+ const a = String(left || '').replaceAll('\\', '/').replace(/^\.\//, '');
64
+ const b = String(right || '').replaceAll('\\', '/').replace(/^\.\//, '');
65
+ return a === b || a.endsWith(`/${b}`) || b.endsWith(`/${a}`);
66
+ }
67
+ function candidateDiffs(thread) {
68
+ const candidates = [];
69
+ for (const turn of thread?.turns || []) {
70
+ for (const item of turn?.items || []) {
71
+ if (item?.type !== 'fileChange')
72
+ continue;
73
+ for (const change of item.changes || []) {
74
+ const diff = parseUnifiedDiff(change?.diff, { created: change?.kind?.type === 'add' });
75
+ if (!diff.hunks)
76
+ continue;
77
+ candidates.push({
78
+ turnId: typeof turn.id === 'string' && turn.id ? turn.id : null,
79
+ path: typeof change?.path === 'string' ? change.path : '',
80
+ key: diffKey(String(change?.diff || '').split('\n')),
81
+ diff,
82
+ });
83
+ }
84
+ }
85
+ }
86
+ return candidates;
87
+ }
88
+ export function enrichCodexFileDiffs(messages, thread) {
89
+ const candidates = candidateDiffs(thread);
90
+ const used = new Set();
91
+ return messages.map((message) => {
92
+ const tool = message?.tool;
93
+ const positioned = tool?.diff?.hunks?.length
94
+ && tool.diff.hunks.every((hunk) => Number.isInteger(hunk.oldStart) && Number.isInteger(hunk.newStart));
95
+ if (tool?.name !== 'apply_patch' || !tool.diff || positioned)
96
+ return message;
97
+ const targetDiff = tool.diff;
98
+ const input = Array.isArray(tool.input) ? null : tool.input;
99
+ const path = typeof input?.file_path === 'string' ? input.file_path : '';
100
+ const key = diffKey(applyPatchSection(input?.patch, path));
101
+ const matches = (candidate, index) => !used.has(index)
102
+ && sameFilePath(candidate.path, path)
103
+ && candidate.diff.added === targetDiff.added
104
+ && candidate.diff.removed === targetDiff.removed
105
+ && (!message.turnId || !candidate.turnId || candidate.turnId === message.turnId);
106
+ let index = candidates.findIndex((candidate, candidateIndex) => matches(candidate, candidateIndex)
107
+ && (!key || !candidate.key || candidate.key === key));
108
+ if (index < 0 && !key)
109
+ index = candidates.findIndex(matches);
110
+ if (index < 0)
111
+ return message;
112
+ const candidate = candidates[index];
113
+ if (!candidate)
114
+ return message;
115
+ used.add(index);
116
+ return { ...message, tool: { ...tool, diff: candidate.diff } };
117
+ });
118
+ }
@@ -0,0 +1,18 @@
1
+ function nonEmpty(value) {
2
+ return typeof value === 'string' && value ? value : null;
3
+ }
4
+ export function codexItemMessageId(turnId, itemId, child = null) {
5
+ const turn = nonEmpty(turnId);
6
+ const item = nonEmpty(itemId);
7
+ if (!turn || !item)
8
+ return null;
9
+ const base = `codex:${turn}:${item}`;
10
+ return child == null ? base : `${base}:child-${encodeURIComponent(String(child))}`;
11
+ }
12
+ export function codexGoalMessageId(goal, event) {
13
+ const lifecycle = nonEmpty(event) || nonEmpty(goal?.status);
14
+ const marker = goal?.createdAt ?? goal?.updatedAt ?? nonEmpty(goal?.objective);
15
+ if (!lifecycle || marker == null)
16
+ return null;
17
+ return `codex-goal:${encodeURIComponent(String(marker))}:${encodeURIComponent(lifecycle)}`;
18
+ }
@@ -0,0 +1,30 @@
1
+ const PLAN_STATUSES = new Map([
2
+ ['pending', 'pending'],
3
+ ['inProgress', 'inProgress'],
4
+ ['in_progress', 'inProgress'],
5
+ ['completed', 'completed'],
6
+ ]);
7
+ // Keep plan projection deliberately small and text-only. App Server uses camelCase statuses while the
8
+ // persisted Code Mode call uses snake_case; the phone receives one stable shape from either source.
9
+ export function normalizeCodexPlan(value) {
10
+ if (!Array.isArray(value))
11
+ return null;
12
+ return value.slice(0, 100).flatMap((item) => {
13
+ const record = item && typeof item === 'object' ? item : null;
14
+ const step = typeof record?.step === 'string' ? record.step.trim().slice(0, 2_000) : '';
15
+ const status = typeof record?.status === 'string' ? PLAN_STATUSES.get(record.status) : undefined;
16
+ return step && status ? [{ step, status }] : [];
17
+ });
18
+ }
19
+ export function codexPlanSnapshot(turnId, value, explanation = null) {
20
+ const steps = normalizeCodexPlan(value);
21
+ if (!turnId || !steps?.length)
22
+ return null;
23
+ return {
24
+ turnId,
25
+ steps,
26
+ ...(typeof explanation === 'string' && explanation.trim()
27
+ ? { explanation: explanation.trim().slice(0, 4_000) }
28
+ : {}),
29
+ };
30
+ }
@@ -0,0 +1,137 @@
1
+ function recordOf(value) {
2
+ return value != null && typeof value === 'object' && !Array.isArray(value)
3
+ ? value
4
+ : null;
5
+ }
6
+ function stringOf(value) {
7
+ return typeof value === 'string' && value ? value : null;
8
+ }
9
+ function timeOf(value) {
10
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null;
11
+ }
12
+ export function parseCodexQueueItem(value) {
13
+ const record = recordOf(value);
14
+ if (!record)
15
+ return null;
16
+ const id = stringOf(record.id);
17
+ const text = stringOf(record.text);
18
+ const createdAt = timeOf(record.createdAt);
19
+ if (!id || !text || createdAt == null)
20
+ return null;
21
+ const requestId = record.requestId == null ? undefined : stringOf(record.requestId) ?? undefined;
22
+ if (record.requestId != null && !requestId)
23
+ return null;
24
+ if (record.editing != null && record.editing !== true)
25
+ return null;
26
+ return {
27
+ ...record, id, text, createdAt,
28
+ ...(requestId ? { requestId } : {}),
29
+ ...(record.editing === true ? { editing: true } : {}),
30
+ };
31
+ }
32
+ export function parseCodexSendResult(value) {
33
+ const record = recordOf(value);
34
+ if (!record)
35
+ return null;
36
+ if (record.queued === true) {
37
+ const item = parseCodexQueueItem(record.item);
38
+ return item ? { ...record, queued: true, item } : null;
39
+ }
40
+ if (record.queued != null && record.queued !== false)
41
+ return null;
42
+ const turn = record.turn == null ? record.turn : recordOf(record.turn);
43
+ if (record.turn != null && !turn)
44
+ return null;
45
+ if (turn?.id != null && typeof turn.id !== 'string')
46
+ return null;
47
+ return { ...record, ...(record.queued === false ? { queued: false } : {}), ...(turn ? { turn } : {}) };
48
+ }
49
+ export function parseCodexSubmissionReceipt(value) {
50
+ const record = recordOf(value);
51
+ if (!record)
52
+ return null;
53
+ const pane = stringOf(record.pane);
54
+ const threadId = stringOf(record.threadId);
55
+ const requestId = stringOf(record.requestId);
56
+ const text = stringOf(record.text);
57
+ const statuses = ['pending', 'queued', 'accepted'];
58
+ const status = statuses.find((candidate) => candidate === record.status);
59
+ const createdAt = timeOf(record.createdAt);
60
+ const updatedAt = timeOf(record.updatedAt);
61
+ if (!pane || !threadId || !requestId || !text || !status || createdAt == null || updatedAt == null)
62
+ return null;
63
+ const queueItemId = record.queueItemId == null ? undefined : stringOf(record.queueItemId) ?? undefined;
64
+ const turnId = record.turnId == null ? undefined : stringOf(record.turnId) ?? undefined;
65
+ if ((record.queueItemId != null && !queueItemId) || (record.turnId != null && !turnId))
66
+ return null;
67
+ if (status === 'queued' && !queueItemId)
68
+ return null;
69
+ return {
70
+ pane, threadId, requestId, text, status, createdAt, updatedAt,
71
+ ...(queueItemId ? { queueItemId } : {}),
72
+ ...(turnId ? { turnId } : {}),
73
+ };
74
+ }
75
+ export function parseCodexSubmissionReceiptView(value) {
76
+ const record = recordOf(value);
77
+ if (!record)
78
+ return null;
79
+ const requestId = stringOf(record.requestId);
80
+ const statuses = ['pending', 'queued', 'accepted'];
81
+ const status = statuses.find((candidate) => candidate === record.status);
82
+ const queueItemId = record.queueItemId == null ? undefined : stringOf(record.queueItemId) ?? undefined;
83
+ const turnId = record.turnId == null ? undefined : stringOf(record.turnId) ?? undefined;
84
+ if (!requestId || !status
85
+ || (record.queueItemId != null && !queueItemId)
86
+ || (record.turnId != null && !turnId)
87
+ || (status === 'queued' && !queueItemId))
88
+ return null;
89
+ return {
90
+ requestId, status,
91
+ ...(queueItemId ? { queueItemId } : {}),
92
+ ...(turnId ? { turnId } : {}),
93
+ };
94
+ }
95
+ export function parseCodexOutboxSnapshot(value) {
96
+ const record = recordOf(value);
97
+ if (!record || record.version !== 1 || !Array.isArray(record.queues) || !Array.isArray(record.receipts)) {
98
+ return null;
99
+ }
100
+ const queues = [];
101
+ const queueKeys = new Set();
102
+ for (const candidate of record.queues) {
103
+ const queue = recordOf(candidate);
104
+ if (!queue || !Array.isArray(queue.items))
105
+ return null;
106
+ const pane = stringOf(queue.pane);
107
+ const threadId = stringOf(queue.threadId);
108
+ const items = queue.items.map(parseCodexQueueItem);
109
+ if (!pane || !threadId || items.some((item) => !item))
110
+ return null;
111
+ const queueKey = `${pane}\0${threadId}`;
112
+ if (queueKeys.has(queueKey))
113
+ return null;
114
+ queueKeys.add(queueKey);
115
+ const itemIds = new Set();
116
+ const itemRequestIds = new Set();
117
+ for (const item of items) {
118
+ if (itemIds.has(item.id) || (item.requestId && itemRequestIds.has(item.requestId)))
119
+ return null;
120
+ itemIds.add(item.id);
121
+ if (item.requestId)
122
+ itemRequestIds.add(item.requestId);
123
+ }
124
+ queues.push({ pane, threadId, items: items });
125
+ }
126
+ const receipts = record.receipts.map(parseCodexSubmissionReceipt);
127
+ if (receipts.some((receipt) => !receipt))
128
+ return null;
129
+ const receiptKeys = new Set();
130
+ for (const receipt of receipts) {
131
+ const receiptKey = `${receipt.pane}\0${receipt.threadId}\0${receipt.requestId}`;
132
+ if (receiptKeys.has(receiptKey))
133
+ return null;
134
+ receiptKeys.add(receiptKey);
135
+ }
136
+ return { version: 1, queues, receipts: receipts };
137
+ }