aimux-cli 0.1.18 → 0.1.20

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 (304) hide show
  1. package/README.md +13 -4
  2. package/bin/aimux +4 -0
  3. package/bin/aimux-dev +2 -6
  4. package/dist/agent-events.js +0 -1
  5. package/dist/agent-output-parser-audit.d.ts +23 -0
  6. package/dist/agent-output-parser-audit.js +187 -0
  7. package/dist/agent-output-parser-contract.d.ts +9 -0
  8. package/dist/agent-output-parser-contract.js +33 -0
  9. package/dist/agent-output-parser-fixtures.d.ts +15 -0
  10. package/dist/agent-output-parser-fixtures.js +593 -0
  11. package/dist/agent-output-parser-harness.d.ts +21 -0
  12. package/dist/agent-output-parser-harness.js +43 -0
  13. package/dist/agent-output-parser-test-utils.d.ts +1 -0
  14. package/dist/agent-output-parser-test-utils.js +7 -0
  15. package/dist/agent-output-parser.js +215 -36
  16. package/dist/agent-prompt-delivery.js +0 -1
  17. package/dist/agent-tracker.js +0 -1
  18. package/dist/agent-watcher.js +0 -1
  19. package/dist/alert-display.js +0 -1
  20. package/dist/atomic-write.d.ts +15 -0
  21. package/dist/atomic-write.js +69 -5
  22. package/dist/attachment-store.d.ts +7 -0
  23. package/dist/attachment-store.js +64 -6
  24. package/dist/backend-session-discovery.d.ts +17 -0
  25. package/dist/backend-session-discovery.js +57 -0
  26. package/dist/builtin-metadata-watchers.js +0 -1
  27. package/dist/claude-hooks.js +0 -1
  28. package/dist/config.js +9 -5
  29. package/dist/connection-targets.js +20 -2
  30. package/dist/context/compactor.js +0 -1
  31. package/dist/context/context-bridge.js +4 -2
  32. package/dist/context/context-file.js +0 -1
  33. package/dist/context/history.js +0 -1
  34. package/dist/credentials.js +3 -7
  35. package/dist/daemon.d.ts +1 -0
  36. package/dist/daemon.js +16 -1
  37. package/dist/dashboard/command-spec.js +0 -1
  38. package/dist/dashboard/feedback.js +0 -1
  39. package/dist/dashboard/index.d.ts +1 -0
  40. package/dist/dashboard/index.js +1 -1
  41. package/dist/dashboard/operation-failures.js +0 -1
  42. package/dist/dashboard/order.js +0 -1
  43. package/dist/dashboard/pending-actions.js +0 -1
  44. package/dist/dashboard/quick-jump.js +0 -1
  45. package/dist/dashboard/runtime-evidence.js +0 -1
  46. package/dist/dashboard/session-actions.js +0 -1
  47. package/dist/dashboard/session-registry.js +0 -1
  48. package/dist/dashboard/sort.js +0 -1
  49. package/dist/dashboard/state.js +0 -1
  50. package/dist/dashboard/targets.js +14 -3
  51. package/dist/dashboard/ui-state-store.js +4 -4
  52. package/dist/debug-state.js +0 -1
  53. package/dist/debug.js +0 -1
  54. package/dist/default-plugins/gh-pr-context.js +0 -1
  55. package/dist/default-plugins/transcript-length.js +0 -1
  56. package/dist/fast-control.js +0 -1
  57. package/dist/hotkeys.js +0 -1
  58. package/dist/http-client.js +0 -1
  59. package/dist/key-parser.js +0 -1
  60. package/dist/last-used.js +3 -3
  61. package/dist/launcher-env.d.ts +4 -0
  62. package/dist/launcher-env.js +70 -0
  63. package/dist/local-ui-server.js +0 -1
  64. package/dist/login-flow.js +0 -1
  65. package/dist/main.js +16 -2
  66. package/dist/managed-launch-env.js +0 -1
  67. package/dist/metadata-server.d.ts +13 -2
  68. package/dist/metadata-server.js +60 -5
  69. package/dist/metadata-store.js +4 -4
  70. package/dist/mobile-push-bridge.d.ts +8 -0
  71. package/dist/mobile-push-bridge.js +22 -0
  72. package/dist/mobile-push-throttle.d.ts +23 -0
  73. package/dist/mobile-push-throttle.js +53 -0
  74. package/dist/multiplexer/agent-io-methods.js +0 -1
  75. package/dist/multiplexer/archives.js +0 -1
  76. package/dist/multiplexer/dashboard-actions-methods.js +0 -1
  77. package/dist/multiplexer/dashboard-control.js +0 -1
  78. package/dist/multiplexer/dashboard-interaction.js +0 -1
  79. package/dist/multiplexer/dashboard-model.js +3 -3
  80. package/dist/multiplexer/dashboard-ops.d.ts +3 -2
  81. package/dist/multiplexer/dashboard-ops.js +2 -3
  82. package/dist/multiplexer/dashboard-state-methods.js +0 -1
  83. package/dist/multiplexer/dashboard-tail-methods.d.ts +3 -2
  84. package/dist/multiplexer/dashboard-tail-methods.js +2 -3
  85. package/dist/multiplexer/dashboard-view-methods.js +2 -1
  86. package/dist/multiplexer/graveyard-view-model.js +0 -1
  87. package/dist/multiplexer/index.d.ts +1 -1
  88. package/dist/multiplexer/index.js +4 -5
  89. package/dist/multiplexer/navigation.js +0 -1
  90. package/dist/multiplexer/notifications.js +0 -1
  91. package/dist/multiplexer/persistence-methods.js +2 -2
  92. package/dist/multiplexer/runtime-lifecycle-methods.js +6 -3
  93. package/dist/multiplexer/runtime-state.js +13 -2
  94. package/dist/multiplexer/runtime-sync.js +0 -1
  95. package/dist/multiplexer/service-state-snapshot.js +4 -3
  96. package/dist/multiplexer/services.js +5 -5
  97. package/dist/multiplexer/session-capture.js +0 -1
  98. package/dist/multiplexer/session-launch.d.ts +1 -1
  99. package/dist/multiplexer/session-launch.js +18 -7
  100. package/dist/multiplexer/session-runtime-core.js +9 -3
  101. package/dist/multiplexer/subscreens.js +0 -1
  102. package/dist/multiplexer/tool-picker.d.ts +2 -1
  103. package/dist/multiplexer/tool-picker.js +29 -22
  104. package/dist/multiplexer/worktree-graveyard.js +0 -1
  105. package/dist/multiplexer/worktrees.js +0 -1
  106. package/dist/notification-context.js +0 -1
  107. package/dist/notifications.js +0 -1
  108. package/dist/notify.d.ts +1 -1
  109. package/dist/notify.js +8 -6
  110. package/dist/orchestration-actions.js +0 -1
  111. package/dist/orchestration-routing.js +0 -1
  112. package/dist/orchestration.js +0 -1
  113. package/dist/osc-notifications.js +0 -1
  114. package/dist/paths.js +50 -5
  115. package/dist/pending-actions.js +0 -1
  116. package/dist/plugin-runtime.js +0 -1
  117. package/dist/project-events.js +0 -1
  118. package/dist/project-scanner.js +0 -1
  119. package/dist/project-service-manifest.js +0 -1
  120. package/dist/project-takeover.d.ts +1 -0
  121. package/dist/project-takeover.js +117 -0
  122. package/dist/recency.js +0 -1
  123. package/dist/recorder.js +0 -1
  124. package/dist/relay-client.d.ts +10 -0
  125. package/dist/relay-client.js +5 -1
  126. package/dist/remote-access.js +0 -1
  127. package/dist/runtime-core/backend-id-reconcile.d.ts +13 -0
  128. package/dist/runtime-core/backend-id-reconcile.js +23 -0
  129. package/dist/runtime-core/exchange-derived.js +0 -1
  130. package/dist/runtime-core/exchange-import.js +0 -1
  131. package/dist/runtime-core/exchange-store.js +3 -9
  132. package/dist/runtime-core/topology-services.js +0 -1
  133. package/dist/runtime-core/topology-sessions.js +0 -1
  134. package/dist/runtime-core/topology-store.js +3 -9
  135. package/dist/runtime-core/topology-worktrees.js +0 -1
  136. package/dist/runtime-migration.js +0 -1
  137. package/dist/runtime-owner.d.ts +3 -0
  138. package/dist/runtime-owner.js +10 -0
  139. package/dist/session-bootstrap.js +0 -1
  140. package/dist/session-runtime.js +0 -1
  141. package/dist/session-semantics.js +0 -1
  142. package/dist/shell-args.d.ts +13 -0
  143. package/dist/shell-args.js +25 -1
  144. package/dist/shell-hooks.d.ts +1 -0
  145. package/dist/shell-hooks.js +1 -1
  146. package/dist/shell-state.js +0 -1
  147. package/dist/status-detector.js +0 -1
  148. package/dist/statusline-model.js +0 -1
  149. package/dist/task-workflow.js +0 -1
  150. package/dist/tasks.js +0 -1
  151. package/dist/team.js +4 -4
  152. package/dist/terminal-host.js +0 -1
  153. package/dist/threads.js +0 -1
  154. package/dist/tmux/doctor.js +0 -1
  155. package/dist/tmux/inbox-popup.js +0 -1
  156. package/dist/tmux/runtime-manager.js +2 -1
  157. package/dist/tmux/session-transport.js +0 -1
  158. package/dist/tmux/statusline-cache.js +0 -1
  159. package/dist/tmux/statusline.js +0 -1
  160. package/dist/tmux/switcher.js +0 -1
  161. package/dist/tmux/window-open.js +0 -1
  162. package/dist/tool-output-watchers.js +0 -1
  163. package/dist/tui/render/box.js +0 -1
  164. package/dist/tui/render/text.js +0 -1
  165. package/dist/tui/screens/dashboard-renderers.js +6 -7
  166. package/dist/tui/screens/overlay-renderers.js +0 -1
  167. package/dist/tui/screens/subscreen-renderers.js +0 -1
  168. package/dist/vitest.setup.d.ts +1 -0
  169. package/dist/vitest.setup.js +9 -0
  170. package/dist/workflow.js +0 -1
  171. package/dist/worktree.js +0 -1
  172. package/dist-ui/_expo/static/css/web-8782287775683e5a944b821b854d0f60.css +1 -0
  173. package/dist-ui/_expo/static/js/web/{entry-477c745b2adc79367a4380ecf07d9ff6.js → entry-90d00d223eefabe5cc21e4329b274fa5.js} +260 -252
  174. package/dist-ui/index.html +2 -2
  175. package/package.json +5 -2
  176. package/dist/agent-events.js.map +0 -1
  177. package/dist/agent-output-parser.js.map +0 -1
  178. package/dist/agent-prompt-delivery.js.map +0 -1
  179. package/dist/agent-tracker.js.map +0 -1
  180. package/dist/agent-watcher.js.map +0 -1
  181. package/dist/alert-display.js.map +0 -1
  182. package/dist/atomic-write.js.map +0 -1
  183. package/dist/attachment-store.js.map +0 -1
  184. package/dist/builtin-metadata-watchers.js.map +0 -1
  185. package/dist/claude-hooks.js.map +0 -1
  186. package/dist/config.js.map +0 -1
  187. package/dist/connection-targets.js.map +0 -1
  188. package/dist/context/compactor.js.map +0 -1
  189. package/dist/context/context-bridge.js.map +0 -1
  190. package/dist/context/context-file.js.map +0 -1
  191. package/dist/context/history.js.map +0 -1
  192. package/dist/credentials.js.map +0 -1
  193. package/dist/daemon.js.map +0 -1
  194. package/dist/dashboard/command-spec.js.map +0 -1
  195. package/dist/dashboard/feedback.js.map +0 -1
  196. package/dist/dashboard/index.js.map +0 -1
  197. package/dist/dashboard/operation-failures.js.map +0 -1
  198. package/dist/dashboard/order.js.map +0 -1
  199. package/dist/dashboard/pending-actions.js.map +0 -1
  200. package/dist/dashboard/quick-jump.js.map +0 -1
  201. package/dist/dashboard/runtime-evidence.js.map +0 -1
  202. package/dist/dashboard/session-actions.js.map +0 -1
  203. package/dist/dashboard/session-registry.js.map +0 -1
  204. package/dist/dashboard/sort.js.map +0 -1
  205. package/dist/dashboard/state.js.map +0 -1
  206. package/dist/dashboard/targets.js.map +0 -1
  207. package/dist/dashboard/ui-state-store.js.map +0 -1
  208. package/dist/debug-state.js.map +0 -1
  209. package/dist/debug.js.map +0 -1
  210. package/dist/default-plugins/gh-pr-context.js.map +0 -1
  211. package/dist/default-plugins/transcript-length.js.map +0 -1
  212. package/dist/fast-control.js.map +0 -1
  213. package/dist/hotkeys.js.map +0 -1
  214. package/dist/http-client.js.map +0 -1
  215. package/dist/key-parser.js.map +0 -1
  216. package/dist/last-used.js.map +0 -1
  217. package/dist/local-ui-server.js.map +0 -1
  218. package/dist/login-flow.js.map +0 -1
  219. package/dist/main.js.map +0 -1
  220. package/dist/managed-launch-env.js.map +0 -1
  221. package/dist/metadata-server.js.map +0 -1
  222. package/dist/metadata-store.js.map +0 -1
  223. package/dist/multiplexer/agent-io-methods.js.map +0 -1
  224. package/dist/multiplexer/archives.js.map +0 -1
  225. package/dist/multiplexer/dashboard-actions-methods.js.map +0 -1
  226. package/dist/multiplexer/dashboard-control.js.map +0 -1
  227. package/dist/multiplexer/dashboard-interaction.js.map +0 -1
  228. package/dist/multiplexer/dashboard-model.js.map +0 -1
  229. package/dist/multiplexer/dashboard-ops.js.map +0 -1
  230. package/dist/multiplexer/dashboard-state-methods.js.map +0 -1
  231. package/dist/multiplexer/dashboard-tail-methods.js.map +0 -1
  232. package/dist/multiplexer/dashboard-view-methods.js.map +0 -1
  233. package/dist/multiplexer/graveyard-view-model.js.map +0 -1
  234. package/dist/multiplexer/index.js.map +0 -1
  235. package/dist/multiplexer/navigation.js.map +0 -1
  236. package/dist/multiplexer/notifications.js.map +0 -1
  237. package/dist/multiplexer/persistence-methods.js.map +0 -1
  238. package/dist/multiplexer/runtime-lifecycle-methods.js.map +0 -1
  239. package/dist/multiplexer/runtime-state.js.map +0 -1
  240. package/dist/multiplexer/runtime-sync.js.map +0 -1
  241. package/dist/multiplexer/service-state-snapshot.js.map +0 -1
  242. package/dist/multiplexer/services.js.map +0 -1
  243. package/dist/multiplexer/session-capture.js.map +0 -1
  244. package/dist/multiplexer/session-launch.js.map +0 -1
  245. package/dist/multiplexer/session-runtime-core.js.map +0 -1
  246. package/dist/multiplexer/subscreens.js.map +0 -1
  247. package/dist/multiplexer/tool-picker.js.map +0 -1
  248. package/dist/multiplexer/worktree-graveyard.js.map +0 -1
  249. package/dist/multiplexer/worktrees.js.map +0 -1
  250. package/dist/notification-context.js.map +0 -1
  251. package/dist/notifications.js.map +0 -1
  252. package/dist/notify.js.map +0 -1
  253. package/dist/orchestration-actions.js.map +0 -1
  254. package/dist/orchestration-routing.js.map +0 -1
  255. package/dist/orchestration.js.map +0 -1
  256. package/dist/osc-notifications.js.map +0 -1
  257. package/dist/paths.js.map +0 -1
  258. package/dist/pending-actions.js.map +0 -1
  259. package/dist/plugin-runtime.js.map +0 -1
  260. package/dist/project-events.js.map +0 -1
  261. package/dist/project-scanner.js.map +0 -1
  262. package/dist/project-service-manifest.js.map +0 -1
  263. package/dist/recency.js.map +0 -1
  264. package/dist/recorder.js.map +0 -1
  265. package/dist/relay-client.js.map +0 -1
  266. package/dist/remote-access.js.map +0 -1
  267. package/dist/runtime-core/exchange-derived.js.map +0 -1
  268. package/dist/runtime-core/exchange-import.js.map +0 -1
  269. package/dist/runtime-core/exchange-store.js.map +0 -1
  270. package/dist/runtime-core/topology-services.js.map +0 -1
  271. package/dist/runtime-core/topology-sessions.js.map +0 -1
  272. package/dist/runtime-core/topology-store.js.map +0 -1
  273. package/dist/runtime-core/topology-worktrees.js.map +0 -1
  274. package/dist/runtime-migration.js.map +0 -1
  275. package/dist/session-bootstrap.js.map +0 -1
  276. package/dist/session-runtime.js.map +0 -1
  277. package/dist/session-semantics.js.map +0 -1
  278. package/dist/shell-args.js.map +0 -1
  279. package/dist/shell-hooks.js.map +0 -1
  280. package/dist/shell-state.js.map +0 -1
  281. package/dist/status-detector.js.map +0 -1
  282. package/dist/statusline-model.js.map +0 -1
  283. package/dist/task-workflow.js.map +0 -1
  284. package/dist/tasks.js.map +0 -1
  285. package/dist/team.js.map +0 -1
  286. package/dist/terminal-host.js.map +0 -1
  287. package/dist/threads.js.map +0 -1
  288. package/dist/tmux/doctor.js.map +0 -1
  289. package/dist/tmux/inbox-popup.js.map +0 -1
  290. package/dist/tmux/runtime-manager.js.map +0 -1
  291. package/dist/tmux/session-transport.js.map +0 -1
  292. package/dist/tmux/statusline-cache.js.map +0 -1
  293. package/dist/tmux/statusline.js.map +0 -1
  294. package/dist/tmux/switcher.js.map +0 -1
  295. package/dist/tmux/window-open.js.map +0 -1
  296. package/dist/tool-output-watchers.js.map +0 -1
  297. package/dist/tui/render/box.js.map +0 -1
  298. package/dist/tui/render/text.js.map +0 -1
  299. package/dist/tui/screens/dashboard-renderers.js.map +0 -1
  300. package/dist/tui/screens/overlay-renderers.js.map +0 -1
  301. package/dist/tui/screens/subscreen-renderers.js.map +0 -1
  302. package/dist/workflow.js.map +0 -1
  303. package/dist/worktree.js.map +0 -1
  304. package/dist-ui/_expo/static/css/web-30453ede1678c16acb08b97e83e8646d.css +0 -1
@@ -1,5 +1,56 @@
1
+ const activityDurationPattern = String.raw `\d+(?:ms|s|m|h)(?:\s+\d+(?:ms|s|m|h))*`;
2
+ const activityForDurationRegex = new RegExp(String.raw `\bfor\s+${activityDurationPattern}(?:$|(?=\s*[·•.)]))`, "i");
3
+ const activityRestForDurationRegex = new RegExp(String.raw `^for\s+${activityDurationPattern}(?:$|(?=\s*[·•.)]))`, "i");
4
+ const activityParentheticalDurationRegex = new RegExp(String.raw `\([^)]*\b${activityDurationPattern}\b[^)]*\)`, "i");
5
+ const activityEllipsisRegex = /\.{3}|…/;
6
+ const activityLeadRegex = /^[\p{Lu}][\p{L}-]{2,}\b/u;
7
+ const looksLikeActivityProgressText = (text) => {
8
+ const trimmed = text.trim();
9
+ const lead = trimmed.match(activityLeadRegex)?.[0] ?? "";
10
+ if (!lead || !/(ed|ing)$/i.test(lead))
11
+ return false;
12
+ const rest = trimmed.slice(lead.length).trimStart();
13
+ if (!(activityRestForDurationRegex.test(rest) ||
14
+ activityParentheticalDurationRegex.test(rest) ||
15
+ activityEllipsisRegex.test(rest))) {
16
+ return false;
17
+ }
18
+ return (activityForDurationRegex.test(trimmed) ||
19
+ activityParentheticalDurationRegex.test(trimmed) ||
20
+ activityEllipsisRegex.test(trimmed));
21
+ };
22
+ const looksLikeRanCommandText = (text) => {
23
+ const trimmed = text.trim();
24
+ return (/^Ran\s+(?:aimux|bash|bun|cat|cd|curl|docker|find|gh|git|grep|ls|mkdir|mv|node|npm|pnpm|python3?|rg|rm|sed|sh|tsc|tsx|vitest|yarn)\b/i.test(trimmed) && !/[.!?]$/.test(trimmed));
25
+ };
26
+ const looksLikeToolActionText = (text) => {
27
+ const trimmed = text.trim();
28
+ if (!trimmed)
29
+ return false;
30
+ return (/^Bash\([^)]*$/i.test(trimmed) ||
31
+ /^(?:Bash|BashOutput|Edit|Explore|Glob|Grep|KillBash|LS|MultiEdit|NotebookEdit|Read|Task|TodoWrite|Update|WebFetch|WebSearch|Write)\s*(?:\([^)\n]*\)|\d+[^\n]*(?:ctrl\+o|to expand)|[^\n]*(?:Running in the background|exit code))\s*$/i.test(trimmed) ||
32
+ /^Background command\s+".+"\s+completed\s+\(exit code\s+\d+\)/i.test(trimmed) ||
33
+ looksLikeRanCommandText(trimmed) ||
34
+ /^Searched\s*for\s*\d+\s*patterns?/i.test(trimmed) ||
35
+ /^Read\s*\d+\s*files?/i.test(trimmed));
36
+ };
37
+ const inferAgentOutputTool = (raw) => {
38
+ const text = String(raw || "");
39
+ const hasCodexChrome = /(?:^|\n)\s*(?:│\s*)?>_\s*OpenAI Codex\b/im.test(text) ||
40
+ /(?:^|\n)\s*gpt-[\w.-]+\b.*(?:~\/|\/|permissions|context\))/im.test(text);
41
+ const hasClaudeChrome = /(?:^|\n)\s*(?:│\s*)?Claude Code\b/im.test(text) ||
42
+ /(?:^|\n)\s*claude\b.*(?:~\/|\/|permissions|context\))/im.test(text);
43
+ if (hasCodexChrome && !hasClaudeChrome) {
44
+ return "codex";
45
+ }
46
+ if (hasClaudeChrome && !hasCodexChrome) {
47
+ return "claude";
48
+ }
49
+ return null;
50
+ };
1
51
  export function parseAgentOutput(raw, options = {}) {
2
- const tool = (options.tool || "unknown").trim() || "unknown";
52
+ const requestedTool = (options.tool || "").trim();
53
+ const tool = requestedTool && requestedTool !== "unknown" ? requestedTool : (inferAgentOutputTool(raw) ?? "unknown");
3
54
  const lines = String(raw || "")
4
55
  .replace(/\r/g, "")
5
56
  .split("\n");
@@ -7,6 +58,7 @@ export function parseAgentOutput(raw, options = {}) {
7
58
  let current = null;
8
59
  let sawPrompt = false;
9
60
  let expectingResponse = false;
61
+ let lastLineWasDivider = false;
10
62
  const flush = () => {
11
63
  if (!current)
12
64
  return;
@@ -46,16 +98,40 @@ export function parseAgentOutput(raw, options = {}) {
46
98
  /^[A-Za-z0-9._-]+@[^ ]+\s+(~\/|\/)/.test(trimmed) ||
47
99
  (/^([›>]|▶)\s/.test(trimmed) && /(permissions|cycle|cwd|context)/i.test(trimmed)) ||
48
100
  /^⏵⏵\s/.test(trimmed) ||
49
- /gpt-|claude|context\)|bypass permissions|shift\+tab|to cycle/i.test(trimmed));
101
+ /^gpt-[\w.-]+\b.*(?:~\/|\/|context\)|permissions)/i.test(trimmed) ||
102
+ /^claude\b.*(?:~\/|\/|context\)|permissions)/i.test(trimmed) ||
103
+ /bypass permissions|shift\+tab|to cycle/i.test(trimmed));
104
+ };
105
+ const isCodexUiLine = (line) => {
106
+ const trimmed = line.trim();
107
+ return /^│/.test(trimmed) || /^╰/.test(trimmed) || /^╭/.test(trimmed);
50
108
  };
51
109
  const isStatusLine = (line) => {
52
110
  const trimmed = line.trim();
53
111
  if (!trimmed)
54
112
  return false;
113
+ const dotBulletText = trimmed.replace(/^•\s?/, "");
114
+ const starBulletText = trimmed.replace(/^\*\s+/, "");
115
+ const dashBulletText = trimmed.replace(/^-\s+/, "");
116
+ const spinnerText = trimmed.replace(/^[✻✽✶]\s+/, "");
117
+ const conversationBulletText = trimmed.replace(/^(?:•|⏺)\s?/, "");
55
118
  return (/^■\s?/.test(trimmed) ||
119
+ /^⏺\s*$/.test(trimmed) ||
120
+ /^⏺\s*[\u2500-\u257f\-_=\s]+Bash command\b/i.test(trimmed) ||
121
+ /^⏺\s*Bash\([^)\n]*terminal-notifier/i.test(trimmed) ||
122
+ /^└\s+/.test(trimmed) ||
123
+ looksLikeActivityProgressText(trimmed) ||
56
124
  /^•\s?Working\b/.test(trimmed) ||
125
+ /^•\s?Starting MCP servers\b/.test(trimmed) ||
126
+ /^•\s?How is Claude doing this session\?\s*\(optional\)/i.test(trimmed) ||
127
+ looksLikeRanCommandText(trimmed) ||
128
+ looksLikeToolActionText(trimmed) ||
129
+ (/^(?:•|⏺)\s?/.test(trimmed) && looksLikeToolActionText(conversationBulletText)) ||
130
+ (/^•\s?/.test(trimmed) && looksLikeActivityProgressText(dotBulletText)) ||
57
131
  /^⏵⏵\s/.test(trimmed) ||
58
- /^\*\s+[A-Z][A-Za-z-]+(?:\.\.\.|…)?$/.test(trimmed) ||
132
+ (/^\*\s+/.test(trimmed) && looksLikeActivityProgressText(starBulletText)) ||
133
+ (/^-\s+/.test(trimmed) && looksLikeActivityProgressText(dashBulletText)) ||
134
+ (/^[✻✽✶]\s+/.test(trimmed) && looksLikeActivityProgressText(spinnerText)) ||
59
135
  /^[╰└]\s*Tip:/i.test(trimmed) ||
60
136
  /^Tip:\s/i.test(trimmed) ||
61
137
  /(Plan Mode|default permission mode)/i.test(trimmed) ||
@@ -69,13 +145,43 @@ export function parseAgentOutput(raw, options = {}) {
69
145
  };
70
146
  const stripPromptMarker = (line) => line.trimStart().replace(/^(›|>|❯)\s?/, "");
71
147
  const stripResponseMarker = (line) => line.trimStart().replace(/^(•|⏺)\s?/, "");
72
- const stripStatusMarker = (line) => line.trimStart().replace(/^(■|\*\s+)\s?/, "");
148
+ const stripStatusMarker = (line) => line.trimStart().replace(/^(■|[-*✻✽✶]\s+)\s?/, "");
149
+ const isCodexPickerSelectionPrompt = (promptText) => {
150
+ if (tool !== "codex" || sawPrompt || (current?.type !== "response" && current?.type !== "raw"))
151
+ return false;
152
+ const activeText = current.lines.join("\n");
153
+ if (!/(?:Resume a previous session|Choose working directory to resume this session)/i.test(activeText)) {
154
+ return false;
155
+ }
156
+ return /^(?:now|\d+[smhd]\s+ago|\d+\.\s)/i.test(promptText.trim());
157
+ };
73
158
  for (const line of lines) {
74
159
  const trimmed = line.trimEnd();
75
- if (isDivider(trimmed))
160
+ if (isCodexUiLine(trimmed)) {
161
+ lastLineWasDivider = false;
162
+ pushLine(sawPrompt ? "status" : "meta", trimmed);
76
163
  continue;
164
+ }
165
+ if (isDivider(trimmed)) {
166
+ lastLineWasDivider = true;
167
+ continue;
168
+ }
77
169
  if (isPromptLine(trimmed)) {
78
170
  const promptText = stripPromptMarker(trimmed);
171
+ if (lastLineWasDivider) {
172
+ if (promptText.trim())
173
+ pushLine("status", promptText);
174
+ lastLineWasDivider = false;
175
+ expectingResponse = false;
176
+ continue;
177
+ }
178
+ lastLineWasDivider = false;
179
+ if (isCodexPickerSelectionPrompt(promptText)) {
180
+ if (promptText.trim())
181
+ pushLine("status", promptText);
182
+ expectingResponse = false;
183
+ continue;
184
+ }
79
185
  if (!promptText.trim()) {
80
186
  flush();
81
187
  expectingResponse = false;
@@ -83,10 +189,11 @@ export function parseAgentOutput(raw, options = {}) {
83
189
  }
84
190
  pushLine("prompt", promptText);
85
191
  sawPrompt = true;
86
- expectingResponse = true;
192
+ expectingResponse = false;
87
193
  continue;
88
194
  }
89
- if (/^(•|⏺)\s?/.test(trimmed) && !/^•\s?Working\b/.test(trimmed)) {
195
+ lastLineWasDivider = false;
196
+ if (/^(•|⏺)\s?/.test(trimmed) && !isStatusLine(trimmed)) {
90
197
  pushLine("response", stripResponseMarker(trimmed));
91
198
  sawPrompt = true;
92
199
  expectingResponse = false;
@@ -110,11 +217,23 @@ export function parseAgentOutput(raw, options = {}) {
110
217
  const active = current;
111
218
  if (active && active.type !== "raw") {
112
219
  active.lines.push("");
220
+ if (active.type === "prompt")
221
+ expectingResponse = true;
113
222
  continue;
114
223
  }
115
224
  flush();
116
225
  continue;
117
226
  }
227
+ const promptBlock = current;
228
+ if (promptBlock?.type === "prompt" && !expectingResponse) {
229
+ promptBlock.lines.push(trimmed);
230
+ continue;
231
+ }
232
+ if (promptBlock?.type === "prompt" && expectingResponse && /^\s+\S/.test(trimmed)) {
233
+ promptBlock.lines.push(trimmed);
234
+ expectingResponse = false;
235
+ continue;
236
+ }
118
237
  if (expectingResponse || current?.type === "response") {
119
238
  pushLine("response", trimmed);
120
239
  continue;
@@ -132,7 +251,7 @@ export function parseAgentOutput(raw, options = {}) {
132
251
  }
133
252
  flush();
134
253
  return {
135
- blocks: normalizeTranscriptBlocks(blocks.filter((block) => block.text.trim().length > 0)),
254
+ blocks: normalizeTranscriptBlocks(blocks.filter((block) => block.text.trim().length > 0), tool),
136
255
  parser: {
137
256
  tool,
138
257
  version: 1,
@@ -140,8 +259,40 @@ export function parseAgentOutput(raw, options = {}) {
140
259
  },
141
260
  };
142
261
  }
143
- function normalizeTranscriptBlocks(blocks) {
262
+ function normalizeTranscriptBlocks(blocks, tool) {
144
263
  const next = blocks.map((block) => ({ ...block }));
264
+ const looksLikeFooterStatus = (text) => {
265
+ return String(text || "")
266
+ .split("\n")
267
+ .some((line) => {
268
+ const trimmed = line.trim();
269
+ return ((/^([A-Za-z0-9._-]+@[^ ]+|~\/|\/)/.test(trimmed) && /(context\)|%\s|[$#]\s)/.test(trimmed)) ||
270
+ /^gpt-[\w.-]+\b.*(?:~\/|\/|context\)|permissions)/i.test(trimmed) ||
271
+ /^claude\b.*(?:~\/|\/|context\)|permissions)/i.test(trimmed) ||
272
+ /bypass permissions|shift\+tab|to cycle/i.test(trimmed));
273
+ });
274
+ };
275
+ const looksLikeActiveWorkStatus = (text) => String(text || "")
276
+ .split("\n")
277
+ .some((line) => {
278
+ const trimmed = line.trim();
279
+ return (/\bWorking \(\d+s\b.*\besc to interrupt\b/i.test(trimmed) ||
280
+ /^Starting MCP servers\b/i.test(trimmed) ||
281
+ looksLikeActivityProgressText(trimmed));
282
+ });
283
+ const normalizedPromptText = (text) => String(text || "")
284
+ .trim()
285
+ .replace(/\s+/g, " ");
286
+ const promptCounts = new Map();
287
+ for (const block of next) {
288
+ if (block.type !== "prompt")
289
+ continue;
290
+ const normalized = normalizedPromptText(block.text);
291
+ if (!normalized)
292
+ continue;
293
+ promptCounts.set(normalized, (promptCounts.get(normalized) ?? 0) + 1);
294
+ }
295
+ const isTemplatePrompt = (text) => /\{[A-Za-z][A-Za-z0-9_-]*\}/.test(text);
145
296
  const looksLikeAssistantText = (text) => {
146
297
  const trimmed = String(text || "").trim();
147
298
  if (!trimmed)
@@ -154,6 +305,26 @@ function normalizeTranscriptBlocks(blocks) {
154
305
  return false;
155
306
  return /[A-Za-z]/.test(trimmed);
156
307
  };
308
+ const looksLikeRuntimeNoiseText = (text) => {
309
+ const lines = String(text || "")
310
+ .split("\n")
311
+ .map((line) => line.trim())
312
+ .filter(Boolean);
313
+ const joined = lines.join("\n");
314
+ const runtimeLineCount = lines.filter((line) => {
315
+ return (/^[✢✳✶✻✽·]/.test(line) ||
316
+ /^\(thinking\)$/i.test(line) ||
317
+ /^Bash\([^)]*terminal-notifier/i.test(line));
318
+ }).length;
319
+ return (runtimeLineCount >= 2 ||
320
+ /terminal-notifier.*Running/i.test(joined) ||
321
+ (/terminal-notifier/i.test(joined) && /(?:Bash command|Thiscommandrequiresapproval|Doyouwanttoproceed)/i.test(joined)));
322
+ };
323
+ for (const block of next) {
324
+ if (block.type === "raw" && looksLikeRuntimeNoiseText(block.text)) {
325
+ block.type = "status";
326
+ }
327
+ }
157
328
  for (let i = 0; i < next.length; i += 1) {
158
329
  const current = next[i];
159
330
  if (!current || current.type !== "raw")
@@ -165,8 +336,13 @@ function normalizeTranscriptBlocks(blocks) {
165
336
  (following?.type === "prompt" || following?.type === "response");
166
337
  const leadingAssistantCarryover = !prev && (following?.type === "prompt" || following?.type === "response" || following?.type === "status");
167
338
  const leadingAssistantPrelude = !prev && nextConversationIndex !== -1;
339
+ const leadingAssistantAfterMetaPrelude = prev?.type === "meta" && nextConversationIndex !== -1;
168
340
  const responseContinuation = prev?.type === "response";
169
- if ((betweenConversationTurns || leadingAssistantCarryover || leadingAssistantPrelude || responseContinuation) &&
341
+ if ((betweenConversationTurns ||
342
+ leadingAssistantCarryover ||
343
+ leadingAssistantPrelude ||
344
+ leadingAssistantAfterMetaPrelude ||
345
+ responseContinuation) &&
170
346
  looksLikeAssistantText(current.text)) {
171
347
  current.type = "response";
172
348
  }
@@ -191,6 +367,34 @@ function normalizeTranscriptBlocks(blocks) {
191
367
  block.type = "response";
192
368
  }
193
369
  }
370
+ for (let i = 0; i < next.length; i += 1) {
371
+ const current = next[i];
372
+ if (!current || current.type !== "prompt")
373
+ continue;
374
+ const previous = next[i - 1] || null;
375
+ const following = next[i + 1] || null;
376
+ const normalized = normalizedPromptText(current.text);
377
+ const nextConversationIndex = next.findIndex((block, index) => index > i && (block.type === "prompt" || block.type === "response"));
378
+ const intervening = next.slice(i + 1, nextConversationIndex === -1 ? undefined : nextConversationIndex);
379
+ const hasActiveWorkBeforeNextTurn = intervening.some((block) => block.type === "status" && looksLikeActiveWorkStatus(block.text));
380
+ const repeatedPrompt = (promptCounts.get(normalized) ?? 0) > 1;
381
+ const templatePrompt = isTemplatePrompt(current.text);
382
+ const hasPriorConversationTurn = next
383
+ .slice(0, i)
384
+ .some((block) => block.type === "prompt" || block.type === "response");
385
+ const trailingFooterInput = nextConversationIndex === -1 && hasPriorConversationTurn;
386
+ if (tool === "codex" &&
387
+ following?.type === "status" &&
388
+ looksLikeFooterStatus(following.text) &&
389
+ (!hasActiveWorkBeforeNextTurn || repeatedPrompt || templatePrompt) &&
390
+ (repeatedPrompt ||
391
+ templatePrompt ||
392
+ trailingFooterInput ||
393
+ previous?.type === "response" ||
394
+ (previous?.type === "status" && looksLikeActiveWorkStatus(previous.text)))) {
395
+ current.type = "status";
396
+ }
397
+ }
194
398
  const merged = [];
195
399
  for (const block of next) {
196
400
  const previous = merged[merged.length - 1];
@@ -200,30 +404,5 @@ function normalizeTranscriptBlocks(blocks) {
200
404
  }
201
405
  merged.push(block);
202
406
  }
203
- return stripTrailingVisiblePrompt(merged);
204
- }
205
- function stripTrailingVisiblePrompt(blocks) {
206
- const promptIndex = findLastIndex(blocks, (block) => block.type === "prompt");
207
- if (promptIndex === -1) {
208
- return blocks;
209
- }
210
- const hasResponseAfterPrompt = blocks.slice(promptIndex + 1).some((block) => block.type === "response");
211
- if (hasResponseAfterPrompt) {
212
- return blocks;
213
- }
214
- const trailingBlocks = blocks.slice(promptIndex + 1);
215
- const hasOnlyNonConversationTail = trailingBlocks.every((block) => block.type === "status" || block.type === "meta" || block.type === "raw");
216
- if (!hasOnlyNonConversationTail) {
217
- return blocks;
218
- }
219
- return blocks.filter((_, index) => index !== promptIndex);
220
- }
221
- function findLastIndex(items, predicate) {
222
- for (let index = items.length - 1; index >= 0; index -= 1) {
223
- if (predicate(items[index])) {
224
- return index;
225
- }
226
- }
227
- return -1;
407
+ return merged;
228
408
  }
229
- //# sourceMappingURL=agent-output-parser.js.map
@@ -116,4 +116,3 @@ export async function deliverTmuxPrompt(opts) {
116
116
  return true;
117
117
  return waitForTmuxPromptSubmit({ tmuxRuntimeManager, target, draft: prompt, isTargetCurrent });
118
118
  }
119
- //# sourceMappingURL=agent-prompt-delivery.js.map
@@ -145,4 +145,3 @@ export class AgentTracker {
145
145
  export function getDerivedState(metadata) {
146
146
  return metadata?.derived;
147
147
  }
148
- //# sourceMappingURL=agent-tracker.js.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=agent-watcher.js.map
@@ -83,4 +83,3 @@ export function contextualizeAlertInput(input, context) {
83
83
  worktreePath: input.worktreePath ?? context?.worktreePath,
84
84
  };
85
85
  }
86
- //# sourceMappingURL=alert-display.js.map
@@ -1 +1,16 @@
1
+ /**
2
+ * Crash-safe write: stage to a temp file, fsync its contents, atomically rename
3
+ * into place, then fsync the directory so the rename itself survives power loss.
4
+ * This is the single durable write path for persistent state.
5
+ */
6
+ export declare function atomicWrite(path: string, data: string | Buffer, options?: {
7
+ mode?: number;
8
+ }): void;
1
9
  export declare function writeJsonAtomic(path: string, value: unknown): void;
10
+ export declare function writeTextAtomic(path: string, text: string): void;
11
+ /**
12
+ * Move a corrupt/unparseable state file aside (preserving it for diagnosis)
13
+ * instead of silently letting a reader reset to empty and a later write
14
+ * overwrite the evidence. Best-effort; returns the quarantine path or null.
15
+ */
16
+ export declare function quarantineCorruptFile(path: string): string | null;
@@ -1,9 +1,73 @@
1
- import { mkdirSync, renameSync, writeFileSync } from "node:fs";
1
+ import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
- export function writeJsonAtomic(path, value) {
3
+ function fsyncDir(dir) {
4
+ let fd;
5
+ try {
6
+ fd = openSync(dir, "r");
7
+ fsyncSync(fd);
8
+ }
9
+ catch {
10
+ // Directory fsync is unsupported on some filesystems; the renamed file is
11
+ // still covered by the temp-file fsync above. Best-effort only.
12
+ }
13
+ finally {
14
+ if (fd !== undefined)
15
+ closeSync(fd);
16
+ }
17
+ }
18
+ /**
19
+ * Crash-safe write: stage to a temp file, fsync its contents, atomically rename
20
+ * into place, then fsync the directory so the rename itself survives power loss.
21
+ * This is the single durable write path for persistent state.
22
+ */
23
+ export function atomicWrite(path, data, options) {
4
24
  mkdirSync(dirname(path), { recursive: true });
5
25
  const tmpPath = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
6
- writeFileSync(tmpPath, `${JSON.stringify(value, null, 2)}\n`);
7
- renameSync(tmpPath, path);
26
+ let fd;
27
+ try {
28
+ writeFileSync(tmpPath, data, options?.mode !== undefined ? { mode: options.mode } : undefined);
29
+ fd = openSync(tmpPath, "r");
30
+ fsyncSync(fd);
31
+ closeSync(fd);
32
+ fd = undefined;
33
+ renameSync(tmpPath, path);
34
+ fsyncDir(dirname(path));
35
+ }
36
+ catch (error) {
37
+ if (fd !== undefined) {
38
+ try {
39
+ closeSync(fd);
40
+ }
41
+ catch { }
42
+ }
43
+ try {
44
+ rmSync(tmpPath, { force: true });
45
+ }
46
+ catch { }
47
+ throw error;
48
+ }
49
+ }
50
+ export function writeJsonAtomic(path, value) {
51
+ atomicWrite(path, `${JSON.stringify(value, null, 2)}\n`);
52
+ }
53
+ export function writeTextAtomic(path, text) {
54
+ atomicWrite(path, text);
55
+ }
56
+ /**
57
+ * Move a corrupt/unparseable state file aside (preserving it for diagnosis)
58
+ * instead of silently letting a reader reset to empty and a later write
59
+ * overwrite the evidence. Best-effort; returns the quarantine path or null.
60
+ */
61
+ export function quarantineCorruptFile(path) {
62
+ try {
63
+ if (!existsSync(path))
64
+ return null;
65
+ const dest = `${path}.corrupt-${Date.now()}`;
66
+ renameSync(path, dest);
67
+ console.error(`aimux: quarantined corrupt state file ${path} -> ${dest}`);
68
+ return dest;
69
+ }
70
+ catch {
71
+ return null;
72
+ }
8
73
  }
9
- //# sourceMappingURL=atomic-write.js.map
@@ -20,9 +20,16 @@ export interface PublicAttachmentRecord {
20
20
  source: "path" | "upload";
21
21
  contentUrl: string;
22
22
  }
23
+ export interface CreateUploadedAttachmentInput {
24
+ filename: string;
25
+ mimeType: string;
26
+ dataBase64: string;
27
+ }
28
+ export declare function createUploadedAttachment(input: CreateUploadedAttachmentInput): PublicAttachmentRecord;
23
29
  export declare function getAttachment(id: string): PublicAttachmentRecord | null;
24
30
  export declare function getAttachmentContent(id: string): {
25
31
  attachment: PublicAttachmentRecord;
26
32
  contentPath: string;
27
33
  buffer: Buffer;
28
34
  } | null;
35
+ export declare function getAttachmentRecord(id: string): AttachmentRecord | null;
@@ -1,12 +1,55 @@
1
- import { readFileSync, existsSync } from "node:fs";
2
- import { join } from "node:path";
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
3
+ import { basename, join } from "node:path";
3
4
  import { getAttachmentsDir } from "./paths.js";
5
+ import { atomicWrite, writeJsonAtomic } from "./atomic-write.js";
6
+ const maxUploadBytes = 10 * 1024 * 1024;
7
+ const allowedImageExtensions = new Map([
8
+ ["image/png", ".png"],
9
+ ["image/jpeg", ".jpg"],
10
+ ["image/webp", ".webp"],
11
+ ["image/gif", ".gif"],
12
+ ]);
13
+ export function createUploadedAttachment(input) {
14
+ const mimeType = input.mimeType.trim().toLowerCase();
15
+ const extension = allowedImageExtensions.get(mimeType);
16
+ if (!extension) {
17
+ throw new Error("unsupported attachment mime type");
18
+ }
19
+ const filename = sanitizeFilename(input.filename);
20
+ const dataBase64 = normalizeBase64(input.dataBase64);
21
+ const buffer = Buffer.from(dataBase64, "base64");
22
+ if (buffer.length === 0) {
23
+ throw new Error("attachment content is required");
24
+ }
25
+ if (buffer.length > maxUploadBytes) {
26
+ throw new Error("attachment exceeds 10 MB");
27
+ }
28
+ const attachmentsDir = getAttachmentsDir();
29
+ mkdirSync(attachmentsDir, { recursive: true });
30
+ const id = `att_${randomUUID().replaceAll("-", "")}`;
31
+ const contentPath = join(attachmentsDir, `${id}${extension}`);
32
+ const record = {
33
+ id,
34
+ kind: "image",
35
+ filename,
36
+ mimeType,
37
+ sizeBytes: buffer.length,
38
+ sha256: createHash("sha256").update(buffer).digest("hex"),
39
+ createdAt: new Date().toISOString(),
40
+ source: "upload",
41
+ contentPath,
42
+ };
43
+ atomicWrite(contentPath, buffer);
44
+ writeJsonAtomic(join(attachmentsDir, `${id}.json`), record);
45
+ return toPublicAttachment(record);
46
+ }
4
47
  export function getAttachment(id) {
5
- const record = loadAttachmentRecord(id);
48
+ const record = getAttachmentRecord(id);
6
49
  return record ? toPublicAttachment(record) : null;
7
50
  }
8
51
  export function getAttachmentContent(id) {
9
- const record = loadAttachmentRecord(id);
52
+ const record = getAttachmentRecord(id);
10
53
  if (!record)
11
54
  return null;
12
55
  return {
@@ -15,10 +58,12 @@ export function getAttachmentContent(id) {
15
58
  buffer: readFileSync(record.contentPath),
16
59
  };
17
60
  }
18
- function loadAttachmentRecord(id) {
61
+ export function getAttachmentRecord(id) {
19
62
  const normalizedId = id.trim();
20
63
  if (!normalizedId)
21
64
  return null;
65
+ if (!/^[A-Za-z0-9_-]+$/.test(normalizedId))
66
+ return null;
22
67
  const metadataPath = join(getAttachmentsDir(), `${normalizedId}.json`);
23
68
  if (!existsSync(metadataPath)) {
24
69
  return null;
@@ -29,6 +74,20 @@ function loadAttachmentRecord(id) {
29
74
  }
30
75
  return parsed;
31
76
  }
77
+ function sanitizeFilename(filename) {
78
+ const safeName = basename(filename.trim()).replaceAll(/[\\/]/g, "").trim();
79
+ return safeName || "image";
80
+ }
81
+ function normalizeBase64(dataBase64) {
82
+ const normalized = dataBase64
83
+ .trim()
84
+ .replace(/^data:[^;]+;base64,/, "")
85
+ .replaceAll(/\s/g, "");
86
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(normalized) || normalized.length % 4 !== 0) {
87
+ throw new Error("attachment content must be base64");
88
+ }
89
+ return normalized;
90
+ }
32
91
  function toPublicAttachment(record) {
33
92
  return {
34
93
  id: record.id,
@@ -42,4 +101,3 @@ function toPublicAttachment(record) {
42
101
  contentUrl: `/attachments/${record.id}/content`,
43
102
  };
44
103
  }
45
- //# sourceMappingURL=attachment-store.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Best-effort recovery of a claude backend session id from its on-disk
3
+ * transcript store, for a session whose durable backend id was lost (e.g. a
4
+ * crash that killed the tmux pane before the id was captured). Returns the
5
+ * uuid only when the worktree's transcript directory holds exactly one
6
+ * candidate — an unambiguous match. If the directory is absent, empty, or
7
+ * holds several transcripts (e.g. the main repo where many agents ran over
8
+ * time), it refuses and returns null rather than guess the wrong session.
9
+ * This preserves the "exact id only" safety of the original resume path.
10
+ */
11
+ export declare function discoverClaudeBackendSessionId(cwd: string, projectsDir?: string): string | null;
12
+ /**
13
+ * Recover a backend session id from the tool's own on-disk session store when
14
+ * the durable topology record lost it. Only claude is supported today (codex
15
+ * carries its id in launch args); returns null for anything else.
16
+ */
17
+ export declare function discoverBackendSessionId(toolConfigKey: string | undefined, cwd: string | undefined): string | null;
@@ -0,0 +1,57 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5
+ function claudeProjectsDir() {
6
+ // Mirrors Claude Code's own config location, which honors CLAUDE_CONFIG_DIR.
7
+ const override = process.env.CLAUDE_CONFIG_DIR?.trim();
8
+ return join(override ? override : join(homedir(), ".claude"), "projects");
9
+ }
10
+ // Claude encodes a project directory by replacing "/" and "." in the cwd with
11
+ // "-", e.g. /Users/x/.aimux/wt -> -Users-x--aimux-wt.
12
+ function encodeClaudeProjectPath(cwd) {
13
+ return cwd.replace(/[/.]/g, "-");
14
+ }
15
+ /**
16
+ * Best-effort recovery of a claude backend session id from its on-disk
17
+ * transcript store, for a session whose durable backend id was lost (e.g. a
18
+ * crash that killed the tmux pane before the id was captured). Returns the
19
+ * uuid only when the worktree's transcript directory holds exactly one
20
+ * candidate — an unambiguous match. If the directory is absent, empty, or
21
+ * holds several transcripts (e.g. the main repo where many agents ran over
22
+ * time), it refuses and returns null rather than guess the wrong session.
23
+ * This preserves the "exact id only" safety of the original resume path.
24
+ */
25
+ export function discoverClaudeBackendSessionId(cwd, projectsDir = claudeProjectsDir()) {
26
+ const dir = join(projectsDir, encodeClaudeProjectPath(cwd));
27
+ if (!existsSync(dir))
28
+ return null;
29
+ let entries;
30
+ try {
31
+ entries = readdirSync(dir);
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ const ids = [];
37
+ for (const entry of entries) {
38
+ if (!entry.endsWith(".jsonl"))
39
+ continue;
40
+ const id = entry.slice(0, -".jsonl".length);
41
+ if (UUID_RE.test(id))
42
+ ids.push(id);
43
+ }
44
+ return ids.length === 1 ? ids[0] : null;
45
+ }
46
+ /**
47
+ * Recover a backend session id from the tool's own on-disk session store when
48
+ * the durable topology record lost it. Only claude is supported today (codex
49
+ * carries its id in launch args); returns null for anything else.
50
+ */
51
+ export function discoverBackendSessionId(toolConfigKey, cwd) {
52
+ if (!cwd || !toolConfigKey)
53
+ return null;
54
+ if (toolConfigKey === "claude")
55
+ return discoverClaudeBackendSessionId(cwd);
56
+ return null;
57
+ }
@@ -175,4 +175,3 @@ export function createBuiltinMetadataWatchers(api) {
175
175
  debug("registered builtin metadata watchers", "plugin");
176
176
  return [planWatcher, statusWatcher, taskWatcher, historyWatcher];
177
177
  }
178
- //# sourceMappingURL=builtin-metadata-watchers.js.map
@@ -128,4 +128,3 @@ export function summarizeClaudeStop(payload) {
128
128
  const body = pickString(payload.message, object["summary"], object["message"]) ?? "Claude completed its turn.";
129
129
  return { subtitle, body };
130
130
  }
131
- //# sourceMappingURL=claude-hooks.js.map