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,264 @@
1
+ import { createHash } from 'node:crypto';
2
+ function attachmentId(sourceId, candidate) {
3
+ return `${sourceId}:${createHash('sha256').update(JSON.stringify({
4
+ paneId: candidate.paneId, process: candidate.process,
5
+ })).digest('hex')}`;
6
+ }
7
+ function operation(sourceId, row) {
8
+ const base = {
9
+ source: { sourceId, cursor: row.cursor },
10
+ ...(row.eventId === undefined ? {} : { eventId: row.eventId }),
11
+ ...(row.correlationId === undefined ? {} : { correlationId: row.correlationId }),
12
+ ...(row.sourceOccurredAt === undefined ? {} : { sourceOccurredAt: row.sourceOccurredAt }),
13
+ };
14
+ return row.state === null
15
+ ? { kind: 'clear', ...base }
16
+ : {
17
+ kind: 'set', state: row.state, ...base,
18
+ message: row.message ?? null,
19
+ reason: row.reason ?? null,
20
+ };
21
+ }
22
+ function baseline(sourceId, tracked) {
23
+ const row = tracked.row;
24
+ if (row.state === null)
25
+ return null;
26
+ return {
27
+ run: tracked.lease.ref,
28
+ source: { sourceId, cursor: row.cursor },
29
+ state: row.state,
30
+ ...(row.message === undefined ? {} : { message: row.message }),
31
+ ...(row.reason === undefined ? {} : { reason: row.reason }),
32
+ ...(row.correlationId === undefined ? {} : { correlationId: row.correlationId }),
33
+ ...(row.eventId === undefined ? {} : { eventId: row.eventId }),
34
+ ...(row.sourceOccurredAt === undefined ? {} : { sourceOccurredAt: row.sourceOccurredAt }),
35
+ };
36
+ }
37
+ function validRow(row, panes) {
38
+ return panes.has(row.paneId) && row.cursor.length > 0 && row.cursor.length <= 4096
39
+ && (row.sessionId === undefined || (row.sessionId.length > 0 && row.sessionId.length <= 1024))
40
+ && (row.correlationId === undefined
41
+ || (row.correlationId.length > 0 && row.correlationId.length <= 256))
42
+ && (row.eventId === undefined || (row.eventId.length > 0 && row.eventId.length <= 256))
43
+ && ((row.state !== 'waiting' && row.state !== 'done' && row.state !== 'error')
44
+ || row.eventId !== undefined);
45
+ }
46
+ // Temporary built-in migration coordinator. It consumes provider-normalized pane rows, while Runtime owns
47
+ // verified attachments and Inbox Core owns ordering, acceptedAt, persistence, and unread semantics.
48
+ export class NativeInboxCoordinator {
49
+ #agentId;
50
+ #sourceId;
51
+ #context;
52
+ #source;
53
+ #pollMs;
54
+ #tracked = new Map();
55
+ #timer;
56
+ #tail = Promise.resolve();
57
+ #started = false;
58
+ #closed = false;
59
+ #baselineReady = false;
60
+ #availability;
61
+ #availabilityMessage;
62
+ constructor({ agentId, sourceId, context, source, pollMs = 500, }) {
63
+ if (!agentId || !sourceId || !context || !source || typeof source.read !== 'function'
64
+ || !Number.isSafeInteger(pollMs) || pollMs < 100) {
65
+ throw new TypeError('Native Inbox coordinator requires a source and bounded poll interval');
66
+ }
67
+ this.#agentId = agentId;
68
+ this.#sourceId = sourceId;
69
+ this.#context = context;
70
+ this.#source = source;
71
+ this.#pollMs = pollMs;
72
+ }
73
+ start() {
74
+ if (this.#closed)
75
+ throw new Error('Native Inbox coordinator is closed');
76
+ if (!this.#started) {
77
+ this.#started = true;
78
+ this.#poll();
79
+ }
80
+ return () => this.close();
81
+ }
82
+ async close() {
83
+ if (this.#closed)
84
+ return;
85
+ this.#closed = true;
86
+ if (this.#timer)
87
+ clearTimeout(this.#timer);
88
+ this.#timer = undefined;
89
+ await this.#tail.catch(() => { });
90
+ const tracked = [...this.#tracked.values()];
91
+ this.#tracked.clear();
92
+ await Promise.all(tracked.map((entry) => (this.#context.runControl.revoke(entry.lease, 'adapter_stopped').catch(() => { }))));
93
+ }
94
+ reconcile() {
95
+ const pending = this.#tail.then(() => this.#reconcile());
96
+ this.#tail = pending.catch(() => { });
97
+ return pending;
98
+ }
99
+ #poll() {
100
+ if (this.#closed)
101
+ return;
102
+ void this.reconcile().catch((error) => {
103
+ this.#context.health.report({
104
+ capability: 'inbox', availability: 'degraded',
105
+ message: error instanceof Error ? error.message : String(error),
106
+ });
107
+ }).finally(() => {
108
+ if (this.#closed)
109
+ return;
110
+ this.#timer = setTimeout(() => this.#poll(), this.#pollMs);
111
+ this.#timer.unref?.();
112
+ });
113
+ }
114
+ async #reconcile() {
115
+ if (this.#closed || this.#context.signal.aborted)
116
+ return;
117
+ let panes;
118
+ let snapshot;
119
+ try {
120
+ panes = await this.#context.panes.list();
121
+ snapshot = await this.#source.read(panes);
122
+ }
123
+ catch (error) {
124
+ const message = error instanceof Error ? error.message : String(error);
125
+ if (this.#availability !== 'unavailable' || this.#availabilityMessage !== message) {
126
+ await this.#context.inbox.restore({ availability: 'unavailable', message });
127
+ this.#availability = 'unavailable';
128
+ this.#availabilityMessage = message;
129
+ }
130
+ this.#baselineReady = false;
131
+ throw error;
132
+ }
133
+ const paneMap = new Map(panes.map((pane) => [pane.paneId, pane]));
134
+ if (!['ready', 'degraded', 'unavailable'].includes(snapshot.availability)
135
+ || !Array.isArray(snapshot.rows) || snapshot.rows.some((row) => !validRow(row, paneMap))
136
+ || new Set(snapshot.rows.map((row) => row.paneId)).size !== snapshot.rows.length) {
137
+ throw new Error(`${this.#agentId} Inbox source returned an invalid snapshot`);
138
+ }
139
+ if (snapshot.availability === 'unavailable') {
140
+ if (this.#availability !== 'unavailable' || this.#availabilityMessage !== snapshot.message) {
141
+ await this.#context.inbox.restore({
142
+ availability: 'unavailable',
143
+ ...(snapshot.message === undefined ? {} : { message: snapshot.message }),
144
+ });
145
+ this.#availability = 'unavailable';
146
+ this.#availabilityMessage = snapshot.message;
147
+ }
148
+ this.#baselineReady = false;
149
+ return;
150
+ }
151
+ let topologyChanged = !this.#baselineReady;
152
+ const rows = new Map(snapshot.rows.map((row) => [row.paneId, structuredClone(row)]));
153
+ // Root Runtime independently revokes a run when its foreground-process proof changes. A coordinator
154
+ // entry must not outlive that lease: retaining it makes this loop skip re-attach forever, then feeds a
155
+ // revoked run into Inbox.restore() and blocks every healthy pane in the same provider snapshot.
156
+ for (const [paneId, tracked] of [...this.#tracked]) {
157
+ if (!tracked.lease.signal.aborted
158
+ && this.#context.runs.resolve(tracked.lease.ref) === tracked.lease)
159
+ continue;
160
+ this.#tracked.delete(paneId);
161
+ topologyChanged = true;
162
+ }
163
+ for (const [paneId, tracked] of [...this.#tracked]) {
164
+ if (rows.has(paneId))
165
+ continue;
166
+ this.#tracked.delete(paneId);
167
+ await this.#context.runControl.revoke(tracked.lease, 'adapter_stopped').catch(() => { });
168
+ topologyChanged = true;
169
+ }
170
+ let attachmentError;
171
+ for (const row of rows.values()) {
172
+ try {
173
+ const pane = paneMap.get(row.paneId);
174
+ const process = await this.#context.process.inspectForeground(pane);
175
+ if (!process)
176
+ throw new Error(`${this.#agentId} foreground process is unavailable`);
177
+ const candidateBase = {
178
+ paneId: row.paneId,
179
+ ...(row.sessionId === undefined ? {} : { sessionId: row.sessionId }),
180
+ process: {
181
+ pid: process.pid,
182
+ ...(process.startedAt === undefined ? {} : { startedAt: process.startedAt }),
183
+ ...(process.tty === undefined ? (pane.tty === undefined ? {} : { tty: pane.tty }) : { tty: process.tty }),
184
+ },
185
+ };
186
+ const nextAttachmentId = attachmentId(this.#sourceId, candidateBase);
187
+ const candidate = { ...candidateBase, attachmentId: nextAttachmentId };
188
+ let tracked = this.#tracked.get(row.paneId);
189
+ if (!tracked) {
190
+ const lease = await this.#context.runControl.attach(candidate);
191
+ tracked = {
192
+ lease, projector: this.#context.inbox.forRun(lease),
193
+ attachmentId: nextAttachmentId, row,
194
+ };
195
+ this.#tracked.set(row.paneId, tracked);
196
+ topologyChanged = true;
197
+ }
198
+ else if (tracked.attachmentId !== nextAttachmentId) {
199
+ const lease = await this.#context.runControl.attach(candidate);
200
+ tracked = {
201
+ lease, projector: this.#context.inbox.forRun(lease),
202
+ attachmentId: nextAttachmentId, row,
203
+ };
204
+ this.#tracked.set(row.paneId, tracked);
205
+ topologyChanged = true;
206
+ }
207
+ else if (tracked.lease.ref.sessionId !== undefined && row.sessionId !== undefined
208
+ && tracked.lease.ref.sessionId !== row.sessionId) {
209
+ const lease = await this.#context.runControl.replace(tracked.lease, candidate, 'session_replaced');
210
+ tracked = {
211
+ lease, projector: this.#context.inbox.forRun(lease),
212
+ attachmentId: nextAttachmentId, row,
213
+ };
214
+ this.#tracked.set(row.paneId, tracked);
215
+ topologyChanged = true;
216
+ }
217
+ else if (tracked.lease.ref.sessionId === undefined && row.sessionId !== undefined) {
218
+ await this.#context.runControl.associateSession(tracked.lease, row.sessionId);
219
+ topologyChanged = true;
220
+ }
221
+ }
222
+ catch (error) {
223
+ // One transient/invalid pane must not suppress every other pane from the same provider. Keep any
224
+ // previously verified row, exclude only the failed newcomer from this cycle, and retry next poll.
225
+ rows.delete(row.paneId);
226
+ attachmentError ??= error instanceof Error ? error.message : String(error);
227
+ }
228
+ }
229
+ const effectiveAvailability = attachmentError ? 'degraded' : snapshot.availability;
230
+ const effectiveMessage = attachmentError ?? snapshot.message;
231
+ const availabilityChanged = this.#availability !== effectiveAvailability
232
+ || this.#availabilityMessage !== effectiveMessage;
233
+ const restoreNeeded = topologyChanged || availabilityChanged;
234
+ if (restoreNeeded) {
235
+ const baselines = [...this.#tracked.values()].flatMap((tracked) => {
236
+ const value = baseline(this.#sourceId, tracked);
237
+ return value ? [value] : [];
238
+ });
239
+ await this.#context.inbox.restore({
240
+ availability: effectiveAvailability,
241
+ snapshot: baselines,
242
+ ...(effectiveMessage === undefined ? {} : { message: effectiveMessage }),
243
+ });
244
+ this.#baselineReady = true;
245
+ }
246
+ for (const row of rows.values()) {
247
+ const tracked = this.#tracked.get(row.paneId);
248
+ if (tracked.row.cursor === row.cursor)
249
+ continue;
250
+ const result = await tracked.projector.submit(operation(this.#sourceId, row));
251
+ if (!result.accepted && result.reason !== 'duplicate_source' && result.reason !== 'duplicate_event') {
252
+ throw new Error(`${this.#agentId} Inbox projection failed: ${result.reason ?? 'unknown'}`);
253
+ }
254
+ tracked.row = row;
255
+ }
256
+ this.#availability = effectiveAvailability;
257
+ this.#availabilityMessage = effectiveMessage;
258
+ this.#context.health.report({
259
+ capability: 'inbox', availability: effectiveAvailability,
260
+ ...(effectiveMessage === undefined ? {} : { message: effectiveMessage }),
261
+ lastSuccessAt: Date.now(),
262
+ });
263
+ }
264
+ }
@@ -0,0 +1,115 @@
1
+ import { createHash } from 'node:crypto';
2
+ const MAX_INBOX_MESSAGE_LENGTH = 4_096;
3
+ function inboxMessage(value) {
4
+ if (!value)
5
+ return undefined;
6
+ if (value.length <= MAX_INBOX_MESSAGE_LENGTH)
7
+ return value;
8
+ let end = MAX_INBOX_MESSAGE_LENGTH;
9
+ const last = value.charCodeAt(end - 1);
10
+ const next = value.charCodeAt(end);
11
+ if (last >= 0xD800 && last <= 0xDBFF && next >= 0xDC00 && next <= 0xDFFF)
12
+ end -= 1;
13
+ return value.slice(0, end);
14
+ }
15
+ function canonical(kind) {
16
+ if (kind === 'working' || kind === 'compacting')
17
+ return 'working';
18
+ if (kind === 'permission')
19
+ return 'waiting';
20
+ if (kind === 'done')
21
+ return 'done';
22
+ if (kind === 'error')
23
+ return 'error';
24
+ if (kind === 'idle')
25
+ return 'ignore';
26
+ return null;
27
+ }
28
+ function stableId(agentId, paneId, sessionId, state) {
29
+ return `${agentId}:${createHash('sha256').update(JSON.stringify({
30
+ paneId, sessionId, kind: state.kind, msg: state.msg ?? '', ts: state.ts ?? 0,
31
+ })).digest('hex')}`;
32
+ }
33
+ function row(agentId, paneId, sessionId, value) {
34
+ const state = canonical(value.kind);
35
+ if (state === 'ignore')
36
+ return null;
37
+ const message = inboxMessage(value.msg);
38
+ const normalized = { ...value, ...(message === undefined ? { msg: '' } : { msg: message }) };
39
+ const identity = stableId(agentId, paneId, sessionId, normalized);
40
+ const terminal = state === 'waiting' || state === 'done' || state === 'error';
41
+ return {
42
+ paneId,
43
+ ...(sessionId === undefined ? {} : { sessionId }),
44
+ cursor: identity,
45
+ state,
46
+ ...(message === undefined ? {} : { message }),
47
+ ...(value.correlationId && value.correlationId.length <= 256
48
+ ? { correlationId: value.correlationId } : {}),
49
+ ...(terminal ? { eventId: identity } : {}),
50
+ ...(typeof value.ts === 'number' && Number.isFinite(value.ts)
51
+ ? { sourceOccurredAt: value.ts } : {}),
52
+ };
53
+ }
54
+ export function createCodexNativeInboxSource(app) {
55
+ const lastRows = new Map();
56
+ return {
57
+ async read(panes) {
58
+ const sourcePanes = panes.map((pane) => ({
59
+ id: pane.paneId,
60
+ cmd: pane.currentCommand,
61
+ tty: pane.tty ?? '',
62
+ session: pane.sessionName,
63
+ window: pane.windowId,
64
+ windowName: pane.windowName,
65
+ }));
66
+ const states = await app.inboxStates(sourcePanes);
67
+ let degraded = false;
68
+ const rows = Object.entries(states).flatMap(([paneId, value]) => {
69
+ if (value.unavailable)
70
+ degraded = true;
71
+ const sessionId = typeof value.threadId === 'string' && value.threadId
72
+ ? value.threadId : undefined;
73
+ const cached = lastRows.get(paneId);
74
+ const projected = value.unavailable && value.kind == null && cached
75
+ ? cached
76
+ : value.kind === 'idle'
77
+ ? (cached?.sessionId === sessionId ? cached : undefined)
78
+ ?? row('codex', paneId, sessionId, { ...value, kind: null })
79
+ : row('codex', paneId, sessionId, value);
80
+ if (projected)
81
+ lastRows.set(paneId, projected);
82
+ return projected ? [projected] : [];
83
+ });
84
+ const present = new Set(Object.keys(states));
85
+ for (const paneId of lastRows.keys())
86
+ if (!present.has(paneId))
87
+ lastRows.delete(paneId);
88
+ return {
89
+ availability: degraded ? 'degraded' : 'ready',
90
+ rows,
91
+ ...(degraded ? { message: 'Codex App Server is reconnecting' } : {}),
92
+ };
93
+ },
94
+ };
95
+ }
96
+ export function createClaudeNativeInboxSource(events) {
97
+ return {
98
+ async read() {
99
+ const states = await events.getStates?.() ?? {};
100
+ const rows = Object.entries(states).flatMap(([paneId, value]) => {
101
+ if (value.agent !== 'claude')
102
+ return [];
103
+ const sessionId = events.paneSession(paneId)?.sessionId || undefined;
104
+ const projected = row('claude', paneId, sessionId, {
105
+ ...value,
106
+ // `idle` is a reminder, not a separate Runtime lifecycle. Preserve the verified Claude run
107
+ // as a neutral row instead of revoking and recreating it on the next hook edge.
108
+ kind: value.kind === 'idle' ? null : value.kind ?? null,
109
+ });
110
+ return projected ? [projected] : [];
111
+ });
112
+ return { availability: 'ready', rows };
113
+ },
114
+ };
115
+ }
@@ -0,0 +1,28 @@
1
+ function isNativePiCommandLine(value) {
2
+ const trimmed = value.trim();
3
+ if (/^pi(?:\s|$)/i.test(trimmed))
4
+ return true;
5
+ return /(?:^|[\s"'])[^\s"']*\/node_modules\/@mariozechner\/pi-coding-agent\/dist\/cli\.js(?:[\s"']|$)/i
6
+ .test(trimmed);
7
+ }
8
+ export const piAgentAdapter = {
9
+ adapterApiVersion: 1,
10
+ id: 'pi',
11
+ label: 'Pi',
12
+ process: {
13
+ commands: ['pi'],
14
+ ambiguousCommands: ['node'],
15
+ verify: async (pane, context) => {
16
+ const foreground = await context.inspectForeground(pane);
17
+ if (!foreground?.commandLine)
18
+ throw new Error('Pi foreground command line is unavailable');
19
+ return isNativePiCommandLine(foreground.commandLine);
20
+ },
21
+ },
22
+ capabilities: {
23
+ inbox: { apiVersion: 1 },
24
+ conversation: { apiVersion: 1, experimental: true },
25
+ sessionControl: { apiVersion: 1 },
26
+ },
27
+ presentation: { iconId: 'pi' },
28
+ };