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
package/src/codexDiff.js DELETED
@@ -1,108 +0,0 @@
1
- const HUNK_HEADER = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
2
-
3
- function changedLines(lines) {
4
- return lines.filter((line) => /^[+-]/.test(line)
5
- && !line.startsWith('+++') && !line.startsWith('---'));
6
- }
7
-
8
- export function parseUnifiedDiff(value, { created = false } = {}) {
9
- const lines = String(value || '').split('\n');
10
- let added = 0;
11
- let removed = 0;
12
- const hunks = [];
13
- let current = null;
14
- const flush = () => {
15
- if (current?.lines.length) hunks.push(current);
16
- current = null;
17
- };
18
-
19
- for (const line of lines) {
20
- const header = HUNK_HEADER.exec(line);
21
- if (header) {
22
- flush();
23
- current = { oldStart: Number(header[1]), newStart: Number(header[2]), lines: [] };
24
- continue;
25
- }
26
- if (line.startsWith('+') && !line.startsWith('+++')) added++;
27
- else if (line.startsWith('-') && !line.startsWith('---')) 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
-
41
- function applyPatchSection(value, path) {
42
- const lines = String(value || '').split('\n');
43
- let active = false;
44
- const section = [];
45
- for (const line of lines) {
46
- const header = /^\*\*\* (?:Add|Update|Delete) File: (.+)$/.exec(line);
47
- if (header) {
48
- if (active) break;
49
- active = header[1].trim() === path;
50
- continue;
51
- }
52
- if (active && line === '*** End Patch') break;
53
- if (active) section.push(line);
54
- }
55
- return section;
56
- }
57
-
58
- function diffKey(lines) {
59
- return changedLines(lines).join('\0');
60
- }
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
-
68
- function candidateDiffs(thread) {
69
- const candidates = [];
70
- for (const turn of thread?.turns || []) {
71
- for (const item of turn?.items || []) {
72
- if (item?.type !== 'fileChange') continue;
73
- for (const change of item.changes || []) {
74
- const diff = parseUnifiedDiff(change?.diff, { created: change?.kind?.type === 'add' });
75
- if (!diff.hunks) continue;
76
- candidates.push({
77
- turnId: turn.id || null,
78
- path: change?.path || '',
79
- key: diffKey(String(change?.diff || '').split('\n')),
80
- diff,
81
- });
82
- }
83
- }
84
- }
85
- return candidates;
86
- }
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
- if (tool?.name !== 'apply_patch' || !tool.diff || tool.diff.hunks?.length) return message;
94
- const path = tool.input?.file_path || '';
95
- const key = diffKey(applyPatchSection(tool.input?.patch, path));
96
- const matches = (candidate, index) => !used.has(index)
97
- && sameFilePath(candidate.path, path)
98
- && candidate.diff.added === tool.diff.added
99
- && candidate.diff.removed === tool.diff.removed
100
- && (!message.turnId || !candidate.turnId || candidate.turnId === message.turnId);
101
- let index = candidates.findIndex((candidate, candidateIndex) => matches(candidate, candidateIndex)
102
- && (!key || !candidate.key || candidate.key === key));
103
- if (index < 0 && !key) index = candidates.findIndex(matches);
104
- if (index < 0) return message;
105
- used.add(index);
106
- return { ...message, tool: { ...tool, diff: candidates[index].diff } };
107
- });
108
- }
@@ -1,413 +0,0 @@
1
- // Codex's rollout JSONL is the durable, ordered conversation log. App Server notifications drive live
2
- // controls/status, but its thread snapshots currently omit some completed tools, so transcript rendering
3
- // must come from this log alone. event_msg mirrors response_item content and is deliberately ignored.
4
- const SLASH_CMD_RE = /^\/([a-z][\w-]*)(?:\s+([^\n]*))?$/i;
5
- const OUTPUT_CAP = 100_000;
6
- const SCRIPT_CAP = 4_000;
7
-
8
- const cap = (value, limit) => {
9
- const text = String(value ?? '');
10
- return text.length > limit ? `${text.slice(0, limit)}…` : text;
11
- };
12
-
13
- // Codex persists its injected context as role=user response items. Match only its reserved envelope roots:
14
- // user prompts may legitimately begin with ordinary HTML/XML and must remain part of the conversation.
15
- export function isCodexSyntheticUserText(value) {
16
- const text = String(value ?? '');
17
- return /^\s*<(?:environment_context|permissions(?:\s+instructions)?|collaboration_mode|apps_instructions|plugins_instructions|skills_instructions|recommended_plugins|user_action|user_shell_command|image|turn_aborted)(?:\s|>|\/)/.test(text)
18
- || /^\s*# AGENTS\.md instructions for [^\r\n]+\r?\n\s*<INSTRUCTIONS>(?:\r?\n|$)/.test(text);
19
- }
20
-
21
- function messageText(content) {
22
- if (typeof content === 'string') return content;
23
- if (!Array.isArray(content)) return '';
24
- return content
25
- .map((item) => (item && ['input_text', 'output_text', 'text'].includes(item.type)) ? (item.text || '') : '')
26
- .join('');
27
- }
28
-
29
- function parseInput(value) {
30
- if (value && typeof value === 'object') return value;
31
- if (typeof value !== 'string' || !value.trim()) return {};
32
- try {
33
- const parsed = JSON.parse(value);
34
- return parsed && typeof parsed === 'object' ? parsed : { value: parsed };
35
- } catch { return { value }; }
36
- }
37
-
38
- function outputText(value) {
39
- if (typeof value === 'string') {
40
- const trimmed = value.trim();
41
- if ((trimmed.startsWith('[') || trimmed.startsWith('{')) && trimmed.length < OUTPUT_CAP * 2) {
42
- try { return outputText(JSON.parse(trimmed)); } catch { /* keep original */ }
43
- }
44
- return cap(value, OUTPUT_CAP);
45
- }
46
- if (Array.isArray(value)) {
47
- return cap(value.map((item) => {
48
- if (item && typeof item === 'object' && typeof item.text === 'string') return item.text;
49
- return outputText(item);
50
- }).filter(Boolean).join('\n'), OUTPUT_CAP);
51
- }
52
- if (value && typeof value === 'object') {
53
- if (Object.keys(value).length === 0) return '';
54
- if (typeof value.output === 'string') return cap(value.output, OUTPUT_CAP);
55
- try { return cap(JSON.stringify(value, null, 2), OUTPUT_CAP); } catch { return ''; }
56
- }
57
- return value == null ? '' : cap(value, OUTPUT_CAP);
58
- }
59
-
60
- function classicOutput(value) {
61
- const raw = outputText(value);
62
- const exit = raw.match(/Process exited with code\s+(-?\d+)/i);
63
- const marker = raw.match(/(?:^|\n)Output:\n?/i);
64
- const result = marker ? raw.slice(marker.index + marker[0].length) : raw;
65
- const declined = /^aborted by user(?:\s+after\s+[\d.]+s)?\s*$/i.test(result.trim());
66
- const isError = !!(exit && Number(exit[1]) !== 0);
67
- return {
68
- result,
69
- isError,
70
- outcome: declined ? 'declined' : isError ? 'failed' : 'success',
71
- };
72
- }
73
-
74
- function resultOutcome(value, result) {
75
- if (/^aborted by user(?:\s+after\s+[\d.]+s)?\s*$/i.test(String(result).trim())) return 'declined';
76
- if (value && typeof value === 'object') {
77
- if (value.success === false) return 'failed';
78
- if (typeof value.exit_code === 'number') return value.exit_code === 0 ? 'success' : 'failed';
79
- }
80
- return 'success';
81
- }
82
-
83
- // Bounded extraction only: find known tools.* calls without evaluating generated JavaScript.
84
- function callArgument(script, open) {
85
- let depth = 1;
86
- let quote = null;
87
- let escaped = false;
88
- for (let i = open + 1; i < script.length; i++) {
89
- const ch = script[i];
90
- if (quote) {
91
- if (escaped) escaped = false;
92
- else if (ch === '\\') escaped = true;
93
- else if (ch === quote) quote = null;
94
- continue;
95
- }
96
- if (ch === '"' || ch === "'" || ch === '`') { quote = ch; continue; }
97
- if (ch === '(' || ch === '{' || ch === '[') depth++;
98
- else if (ch === ')' || ch === '}' || ch === ']') {
99
- depth--;
100
- if (depth === 0) return { text: script.slice(open + 1, i).trim(), end: i + 1 };
101
- }
102
- }
103
- return null;
104
- }
105
-
106
- function decodeJsString(raw) {
107
- if (!raw || !['"', "'", '`'].includes(raw[0]) || raw.at(-1) !== raw[0]) return null;
108
- if (raw[0] === '"') {
109
- try { return JSON.parse(raw); } catch { return null; }
110
- }
111
- const body = raw.slice(1, -1);
112
- if (raw[0] === '`' && body.includes('${')) return null;
113
- return body.replace(/\\([\\'`nrt])/g, (_match, char) => ({ n: '\n', r: '\r', t: '\t' }[char] ?? char));
114
- }
115
-
116
- function stringTokenAt(script, start) {
117
- const quote = script[start];
118
- if (!['"', "'", '`'].includes(quote)) return null;
119
- let escaped = false;
120
- for (let i = start + 1; i < script.length; i++) {
121
- const ch = script[i];
122
- if (escaped) escaped = false;
123
- else if (ch === '\\') escaped = true;
124
- else if (ch === quote) return script.slice(start, i + 1);
125
- }
126
- return null;
127
- }
128
-
129
- function resolveStringArgument(script, raw, before) {
130
- const direct = decodeJsString(raw);
131
- if (direct != null || !/^[A-Za-z_$][\w$]*$/.test(raw)) return direct;
132
- const escaped = raw.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
133
- const declaration = new RegExp(`(?:const|let|var)\\s+${escaped}\\s*=\\s*`, 'g');
134
- let match;
135
- let token = null;
136
- while ((match = declaration.exec(script)) && match.index < before) {
137
- token = stringTokenAt(script, declaration.lastIndex);
138
- }
139
- return token ? decodeJsString(token) : null;
140
- }
141
-
142
- function splitTopLevel(value, separator) {
143
- const parts = [];
144
- let start = 0;
145
- let depth = 0;
146
- let quote = null;
147
- let escaped = false;
148
- for (let i = 0; i < value.length; i++) {
149
- const ch = value[i];
150
- if (quote) {
151
- if (escaped) escaped = false;
152
- else if (ch === '\\') escaped = true;
153
- else if (ch === quote) quote = null;
154
- continue;
155
- }
156
- if (ch === '"' || ch === "'" || ch === '`') { quote = ch; continue; }
157
- if (ch === '(' || ch === '{' || ch === '[') depth++;
158
- else if (ch === ')' || ch === '}' || ch === ']') depth--;
159
- else if (ch === separator && depth === 0) {
160
- parts.push(value.slice(start, i));
161
- start = i + 1;
162
- }
163
- }
164
- parts.push(value.slice(start));
165
- return parts;
166
- }
167
-
168
- function parseJsLiteral(script, raw, before) {
169
- const value = raw.trim();
170
- const decoded = decodeJsString(value);
171
- if (decoded != null) return decoded;
172
- try { return JSON.parse(value); } catch { /* JavaScript object syntax is not strict JSON. */ }
173
- const resolved = resolveStringArgument(script, value, before);
174
- return resolved == null ? value : resolved;
175
- }
176
-
177
- // Orchestrated tool calls are stored as JavaScript source, and their arguments commonly use object-literal
178
- // keys (`{ cmd: "pwd" }`) rather than strict JSON (`{"cmd":"pwd"}`). Parse only top-level literal fields;
179
- // never evaluate generated code. Unknown expressions stay as source text so the detail view loses nothing.
180
- function parseJsObjectInput(script, raw, before) {
181
- const value = raw.trim();
182
- if (!value.startsWith('{') || !value.endsWith('}')) return null;
183
- const input = {};
184
- for (const field of splitTopLevel(value.slice(1, -1), ',')) {
185
- const colon = splitTopLevel(field, ':');
186
- if (colon.length < 2) continue;
187
- const rawKey = colon.shift().trim();
188
- const key = decodeJsString(rawKey) ?? (/^[A-Za-z_$][\w$]*$/.test(rawKey) ? rawKey : null);
189
- if (!key) continue;
190
- input[key] = parseJsLiteral(script, colon.join(':').trim(), before);
191
- }
192
- return Object.keys(input).length ? input : null;
193
- }
194
-
195
- function parseToolInput(script, raw, before) {
196
- const parsed = parseInput(raw);
197
- if (parsed.value !== raw) return parsed;
198
- return parseJsObjectInput(script, raw, before) || { script: raw };
199
- }
200
-
201
- function applyPatchCalls(patch) {
202
- const header = /^\*\*\* (Add|Update|Delete) File: (.+)$/gm;
203
- const sections = [];
204
- let match;
205
- while ((match = header.exec(patch))) {
206
- sections.push({ kind: match[1], path: match[2].trim(), start: match.index });
207
- }
208
- if (!sections.length) return [{ name: 'apply_patch', input: { patch } }];
209
- return sections.map((section, index) => {
210
- const end = sections[index + 1]?.start ?? patch.indexOf('\n*** End Patch', section.start);
211
- const body = patch.slice(section.start, end < 0 ? patch.length : end);
212
- let added = 0;
213
- let removed = 0;
214
- for (const line of body.split('\n').slice(1)) {
215
- if (line.startsWith('@@')) continue;
216
- if (!/^[+\- ]/.test(line)) continue;
217
- if (line.startsWith('+')) added++;
218
- else if (line.startsWith('-')) removed++;
219
- }
220
- return {
221
- name: 'apply_patch',
222
- input: { file_path: section.path, patch },
223
- diff: {
224
- // apply_patch's persisted input normally uses bare `@@` markers with no file positions. Keep the
225
- // stat, but never invent line zero; the transcript route enriches this from App Server's unified diff.
226
- added, removed, hunks: null,
227
- ...(section.kind === 'Add' ? { created: true } : {}),
228
- },
229
- };
230
- });
231
- }
232
-
233
- function extractCustomCalls(script) {
234
- const calls = [];
235
- const re = /tools\.([A-Za-z][\w]*)\s*\(/g;
236
- let match;
237
- while ((match = re.exec(script))) {
238
- const arg = callArgument(script, re.lastIndex - 1);
239
- if (!arg) return null;
240
- if (match[1] === 'apply_patch') {
241
- const decoded = resolveStringArgument(script, arg.text, match.index);
242
- calls.push(...(decoded == null
243
- ? [{ name: 'apply_patch', input: { script: arg.text } }]
244
- : applyPatchCalls(decoded)));
245
- } else {
246
- calls.push({ name: match[1], input: parseToolInput(script, arg.text, match.index) });
247
- }
248
- re.lastIndex = arg.end;
249
- }
250
- return calls.length ? calls : null;
251
- }
252
-
253
- function structuredResults(value) {
254
- let parsed = value;
255
- if (typeof parsed === 'string') {
256
- try { parsed = JSON.parse(parsed); } catch { return null; }
257
- }
258
- if (Array.isArray(parsed)) {
259
- for (let i = parsed.length - 1; i >= 0; i--) {
260
- const text = parsed[i] && typeof parsed[i] === 'object' ? parsed[i].text : null;
261
- if (typeof text !== 'string') continue;
262
- try {
263
- const object = JSON.parse(text);
264
- if (object && typeof object === 'object' && !Array.isArray(object)) return Object.values(object);
265
- } catch { /* try previous block */ }
266
- }
267
- return null;
268
- }
269
- return parsed && typeof parsed === 'object' ? Object.values(parsed) : null;
270
- }
271
-
272
- function applyCustomOutput(messages, value) {
273
- const values = structuredResults(value);
274
- if (values && values.length === messages.length) {
275
- messages.forEach((message, index) => {
276
- const result = values[index];
277
- message.tool.result = outputText(result);
278
- message.tool.outcome = resultOutcome(result, message.tool.result);
279
- message.tool.isError = message.tool.outcome === 'failed';
280
- });
281
- return;
282
- }
283
- const raw = outputText(value).replace(/^Script completed[^\n]*\n(?:Wall time[^\n]*\n)?(?:Output:\n?)?/i, '');
284
- const declined = /^aborted by user(?:\s+after\s+[\d.]+s)?\s*$/i.test(raw.trim());
285
- messages.forEach((message, index) => {
286
- message.tool.result = index === messages.length - 1 ? raw : '';
287
- // A single extracted call has an exact persisted outcome. With several calls and one unstructured
288
- // wrapper result, the rollout cannot identify which nested call produced it, so keep every card neutral.
289
- message.tool.outcome = declined && messages.length === 1 ? 'declined'
290
- : messages.length === 1 ? 'success' : 'completed';
291
- });
292
- }
293
-
294
- export function createCodexTranscriptParser() {
295
- const messages = [];
296
- const pending = new Map();
297
- let lineIndex = 0;
298
-
299
- const toolMessage = (i, ts, name, input, diff, item) => ({
300
- i, role: 'assistant', type: 'tool', ts,
301
- ...(item?.internal_chat_message_metadata_passthrough?.turn_id
302
- ? { turnId: item.internal_chat_message_metadata_passthrough.turn_id }
303
- : {}),
304
- tool: { name, input, result: null, isError: false, ...(diff ? { diff } : {}) },
305
- });
306
-
307
- function push(lines) {
308
- for (const raw of lines) {
309
- const i = lineIndex++;
310
- const source = typeof raw === 'string' ? raw.trim() : '';
311
- if (!source) continue;
312
- let row;
313
- try { row = JSON.parse(source); } catch { continue; }
314
- const ts = typeof row.timestamp === 'string' ? row.timestamp : undefined;
315
-
316
- if (row.type === 'compacted') { messages.push({ i, type: 'compact', ts }); continue; }
317
- if (row.type !== 'response_item' || !row.payload || typeof row.payload !== 'object') continue;
318
- const item = row.payload;
319
-
320
- if (item.type === 'message') {
321
- const text = messageText(item.content);
322
- if (/^\s*<turn_aborted>/.test(text)) { messages.push({ i, type: 'interrupt', ts }); continue; }
323
- if (item.role === 'developer') continue;
324
- if (!['user', 'assistant'].includes(item.role) || !text.trim()) continue;
325
- if (item.role === 'user') {
326
- if (isCodexSyntheticUserText(text)) continue;
327
- const slash = SLASH_CMD_RE.exec(text.trim());
328
- if (slash) {
329
- const marker = { i, type: 'slash', name: `/${slash[1]}`, ts };
330
- if (slash[2]?.trim()) marker.args = slash[2].trim();
331
- messages.push(marker);
332
- continue;
333
- }
334
- }
335
- const turnId = item.internal_chat_message_metadata_passthrough?.turn_id;
336
- messages.push({ i, role: item.role, type: 'text', text, ts, ...(turnId ? { turnId } : {}) });
337
- continue;
338
- }
339
-
340
- if (item.type === 'reasoning') {
341
- const text = (Array.isArray(item.summary) ? item.summary : [])
342
- .map((part) => part?.text || '').filter(Boolean).join('\n');
343
- if (text.trim()) messages.push({ i, role: 'assistant', type: 'thinking', text, ts });
344
- continue;
345
- }
346
-
347
- if (item.type === 'function_call') {
348
- const message = toolMessage(i, ts, item.name || '', parseInput(item.arguments), null, item);
349
- messages.push(message);
350
- if (item.call_id) pending.set(item.call_id, [message]);
351
- continue;
352
- }
353
- if (item.type === 'function_call_output') {
354
- const targets = item.call_id && pending.get(item.call_id);
355
- if (targets?.length === 1) {
356
- const output = classicOutput(item.output);
357
- targets[0].tool.result = output.result;
358
- targets[0].tool.isError = output.isError;
359
- targets[0].tool.outcome = output.outcome;
360
- pending.delete(item.call_id);
361
- }
362
- continue;
363
- }
364
-
365
- if (item.type === 'tool_search_call') {
366
- const message = toolMessage(i, ts, 'tool_search', parseInput(item.arguments), null, item);
367
- if (item.status && item.status !== 'in_progress') {
368
- message.tool.result = '';
369
- message.tool.isError = item.status === 'failed';
370
- message.tool.outcome = item.status === 'failed' ? 'failed' : 'success';
371
- }
372
- messages.push(message);
373
- if (item.call_id) pending.set(item.call_id, [message]);
374
- continue;
375
- }
376
- if (item.type === 'tool_search_output') {
377
- const targets = item.call_id && pending.get(item.call_id);
378
- if (targets?.length === 1) {
379
- targets[0].tool.result = outputText(item.tools);
380
- targets[0].tool.isError = item.status === 'failed';
381
- targets[0].tool.outcome = item.status === 'failed' ? 'failed' : 'success';
382
- pending.delete(item.call_id);
383
- }
384
- continue;
385
- }
386
-
387
- if (item.type === 'custom_tool_call') {
388
- const script = typeof item.input === 'string' ? item.input : '';
389
- const calls = extractCustomCalls(script);
390
- const targets = calls
391
- ? calls.map((call) => toolMessage(i, ts, call.name, call.input, call.diff, item))
392
- : [toolMessage(i, ts, item.name || 'exec', { script: cap(script, SCRIPT_CAP) }, null, item)];
393
- messages.push(...targets);
394
- if (item.call_id) pending.set(item.call_id, targets);
395
- continue;
396
- }
397
- if (item.type === 'custom_tool_call_output') {
398
- const targets = item.call_id && pending.get(item.call_id);
399
- if (targets) {
400
- applyCustomOutput(targets, item.output);
401
- pending.delete(item.call_id);
402
- }
403
- }
404
- }
405
- return messages;
406
- }
407
-
408
- return { push, messages };
409
- }
410
-
411
- export function parseCodexTranscript(lines) {
412
- return createCodexTranscriptParser().push(lines);
413
- }
@@ -1,129 +0,0 @@
1
- // Persistent machine-wide Codex usage snapshot utilities. Kept as CommonJS so the ESM usage service can
2
- // load the same bounded rollout reader without adding a second package boundary.
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
-
6
- const CHUNK_BYTES = 64 * 1024;
7
-
8
- function windowUsage(value) {
9
- if (!value || typeof value.used_percent !== 'number') return null;
10
- return {
11
- usedPercent: value.used_percent,
12
- windowMinutes: typeof value.window_minutes === 'number' ? value.window_minutes : null,
13
- resetsAt: typeof value.resets_at === 'number' ? value.resets_at : null,
14
- };
15
- }
16
-
17
- function normalize(record) {
18
- const payload = record && record.payload;
19
- if (!payload || payload.type !== 'token_count') return null;
20
- const updatedAt = Date.parse(record.timestamp);
21
- if (!Number.isFinite(updatedAt)) return null;
22
- const info = payload.info || {};
23
- const totals = info.total_token_usage || {};
24
- const limits = payload.rate_limits || {};
25
- return {
26
- updatedAt,
27
- rateLimits: {
28
- primary: windowUsage(limits.primary),
29
- secondary: windowUsage(limits.secondary),
30
- },
31
- tokens: {
32
- total: totals.total_tokens ?? null,
33
- input: totals.input_tokens ?? null,
34
- cachedInput: totals.cached_input_tokens ?? null,
35
- output: totals.output_tokens ?? null,
36
- reasoning: totals.reasoning_output_tokens ?? null,
37
- },
38
- contextWindow: typeof info.model_context_window === 'number' ? info.model_context_window : null,
39
- };
40
- }
41
-
42
- function parseLatest(lines) {
43
- for (let i = lines.length - 1; i >= 0; i--) {
44
- const line = lines[i];
45
- if (!line || !line.includes('token_count')) continue;
46
- try {
47
- const usage = normalize(JSON.parse(line));
48
- if (usage) return usage;
49
- } catch { /* damaged/incomplete line: continue toward older complete records */ }
50
- }
51
- return null;
52
- }
53
-
54
- function readLatestUsage(transcriptPath) {
55
- let fd;
56
- try {
57
- fd = fs.openSync(transcriptPath, 'r');
58
- let end = fs.fstatSync(fd).size;
59
- let suffix = '';
60
- while (end > 0) {
61
- const start = Math.max(0, end - CHUNK_BYTES);
62
- const buffer = Buffer.allocUnsafe(end - start);
63
- fs.readSync(fd, buffer, 0, buffer.length, start);
64
- const lines = `${buffer.toString('utf8')}${suffix}`.split('\n');
65
- if (start > 0) suffix = lines.shift() || '';
66
- const usage = parseLatest(lines);
67
- if (usage) return usage;
68
- end = start;
69
- }
70
- return suffix ? parseLatest([suffix]) : null;
71
- } catch {
72
- return null;
73
- } finally {
74
- if (fd !== undefined) {
75
- try { fs.closeSync(fd); } catch { /* best effort */ }
76
- }
77
- }
78
- }
79
-
80
- function readSnapshot(snapshotPath) {
81
- try {
82
- const value = JSON.parse(fs.readFileSync(snapshotPath, 'utf8'));
83
- if (!value || value.version !== 1 || typeof value.checkedAt !== 'number') return null;
84
- if (value.usage !== null && (!value.usage || typeof value.usage.updatedAt !== 'number')) return null;
85
- return value;
86
- } catch {
87
- return null;
88
- }
89
- }
90
-
91
- function nap(ms) {
92
- try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); }
93
- catch { const until = Date.now() + ms; while (Date.now() < until) { /* spin */ } }
94
- }
95
-
96
- function writeSnapshot(snapshotPath, usage, { checkedAt } = {}) {
97
- try { fs.mkdirSync(path.dirname(snapshotPath), { recursive: true }); } catch { return null; }
98
- const lock = `${snapshotPath}.lock`;
99
- let held = false;
100
- for (let i = 0; i < 40 && !held; i++) {
101
- try { fs.closeSync(fs.openSync(lock, 'wx')); held = true; }
102
- catch {
103
- try { if (Date.now() - fs.statSync(lock).mtimeMs > 3000) fs.unlinkSync(lock); } catch { /* retry */ }
104
- nap(5);
105
- }
106
- }
107
- if (!held) return readSnapshot(snapshotPath)?.usage || null;
108
-
109
- try {
110
- const previous = readSnapshot(snapshotPath);
111
- const priorUsage = previous?.usage || null;
112
- const nextUsage = usage && (!priorUsage || usage.updatedAt >= priorUsage.updatedAt) ? usage : priorUsage;
113
- const value = {
114
- version: 1,
115
- checkedAt: typeof checkedAt === 'number' ? checkedAt : (previous?.checkedAt || 0),
116
- usage: nextUsage,
117
- };
118
- const tmp = `${snapshotPath}.${process.pid}.${Date.now()}.tmp`;
119
- fs.writeFileSync(tmp, JSON.stringify(value));
120
- fs.renameSync(tmp, snapshotPath);
121
- return nextUsage;
122
- } catch {
123
- return readSnapshot(snapshotPath)?.usage || null;
124
- } finally {
125
- try { fs.unlinkSync(lock); } catch { /* best effort */ }
126
- }
127
- }
128
-
129
- module.exports = { readLatestUsage, readSnapshot, writeSnapshot };
package/src/config.js DELETED
@@ -1,9 +0,0 @@
1
- import { normalizeShortcuts } from './shortcutConfig.js';
2
-
3
- export function loadConfig(env = process.env) {
4
- return {
5
- host: env.HANDMUX_HOST || '0.0.0.0',
6
- port: Number(env.HANDMUX_PORT) || 4000,
7
- shortcuts: normalizeShortcuts(env.HANDMUX_SHORTCUTS === undefined ? undefined : JSON.parse(env.HANDMUX_SHORTCUTS)),
8
- };
9
- }