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,4 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { discoverBackendSessionId } from "../backend-session-discovery.js";
2
3
  import { loadConfig } from "../config.js";
3
4
  import { loadMetadataState, updateSessionMetadata } from "../metadata-store.js";
4
5
  import { isToolInternalWorktree, listWorktrees as listAllWorktrees } from "../worktree.js";
@@ -419,7 +420,18 @@ export function resumeOfflineSession(host, session) {
419
420
  return;
420
421
  const derived = loadMetadataState().sessions[session.id]?.derived;
421
422
  const relaunchFresh = derived?.activity === "error" || derived?.attention === "error";
422
- const backendSessionId = session.backendSessionId;
423
+ let backendSessionId = session.backendSessionId;
424
+ if (!backendSessionId && !relaunchFresh) {
425
+ // The durable backend id can be lost if a crash killed the tmux pane before
426
+ // it was captured. Recover it from the tool's on-disk session store so the
427
+ // agent stays resumable instead of being stranded.
428
+ const discovered = discoverBackendSessionId(session.toolConfigKey, session.worktreePath);
429
+ if (discovered) {
430
+ backendSessionId = discovered;
431
+ session.backendSessionId = discovered;
432
+ host.debug?.(`reconciled backend session id for ${session.id} from disk: ${discovered}`, "session");
433
+ }
434
+ }
423
435
  const useBackendResume = !relaunchFresh && host.sessionBootstrap.canResumeWithBackendSessionId(toolCfg, backendSessionId);
424
436
  let actionArgs;
425
437
  if (useBackendResume) {
@@ -499,4 +511,3 @@ export function stopProjectServiceRefresh(host) {
499
511
  export function listDesktopWorktrees(_host) {
500
512
  return listAllWorktrees().filter((wt) => !wt.isBare && !isToolInternalWorktree(wt));
501
513
  }
502
- //# sourceMappingURL=runtime-state.js.map
@@ -39,4 +39,3 @@ export class MultiplexerRuntimeSync {
39
39
  }
40
40
  }
41
41
  }
42
- //# sourceMappingURL=runtime-sync.js.map
@@ -1,5 +1,6 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { getStatePath } from "../paths.js";
3
+ import { quarantineCorruptFile, writeJsonAtomic } from "../atomic-write.js";
3
4
  import { buildServiceStateFromMetadata } from "./services.js";
4
5
  import { listWorktreeGraveyardPaths } from "./worktree-graveyard.js";
5
6
  function isAvailableSnapshotWorktree(worktreePath, graveyardPaths = listWorktreeGraveyardPaths()) {
@@ -61,14 +62,14 @@ export function persistProjectRuntimeSnapshotsBeforeTmuxStop(projectRoot, tmux)
61
62
  existing = JSON.parse(readFileSync(statePath, "utf-8"));
62
63
  }
63
64
  catch {
65
+ quarantineCorruptFile(statePath);
64
66
  existing = null;
65
67
  }
66
68
  }
67
69
  const nextState = mergeRuntimeSnapshots(existing, { services }, projectRoot);
68
- writeFileSync(statePath, JSON.stringify(nextState, null, 2) + "\n");
70
+ writeJsonAtomic(statePath, nextState);
69
71
  return { sessions: [], services };
70
72
  }
71
73
  export function persistProjectServiceSnapshotsBeforeRuntimeStop(projectRoot, tmux) {
72
74
  return persistProjectRuntimeSnapshotsBeforeTmuxStop(projectRoot, tmux).services;
73
75
  }
74
- //# sourceMappingURL=service-state-snapshot.js.map
@@ -1,7 +1,8 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
3
  import { findMainRepo } from "../worktree.js";
4
4
  import { getStatePath } from "../paths.js";
5
+ import { writeJsonAtomic } from "../atomic-write.js";
5
6
  import { wrapCommandWithShellIntegration, wrapInteractiveShellWithIntegration } from "../shell-hooks.js";
6
7
  import { markLastUsed } from "../last-used.js";
7
8
  import { removeTopologyService, upsertTopologyService, } from "../runtime-core/topology-services.js";
@@ -119,7 +120,7 @@ export function createService(host, commandLine, worktreePath, opts) {
119
120
  const command = wrapped.command;
120
121
  const args = wrapped.args;
121
122
  const label = serviceLabelForCommand(trimmed);
122
- const tmuxSession = host.tmuxRuntimeManager.ensureProjectSession(process.cwd());
123
+ const tmuxSession = host.tmuxRuntimeManager.ensureProjectSession(projectRoot);
123
124
  const shouldRenderPending = host.startedInDashboard && host.mode === "dashboard";
124
125
  if (shouldRenderPending) {
125
126
  host.setPendingDashboardServiceAction(serviceId, "creating", {
@@ -237,7 +238,7 @@ export function removeOfflineService(host, serviceId) {
237
238
  try {
238
239
  const state = JSON.parse(readFileSync(statePath, "utf-8"));
239
240
  state.services = (state.services ?? []).filter((service) => service.id !== serviceId);
240
- writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
241
+ writeJsonAtomic(statePath, state);
241
242
  }
242
243
  catch { }
243
244
  }
@@ -299,7 +300,7 @@ export function resumeOfflineService(host, service) {
299
300
  const command = wrapped.command;
300
301
  const args = wrapped.args;
301
302
  const label = service.label ?? serviceLabelForCommand(launchCommandLine);
302
- const tmuxSession = host.tmuxRuntimeManager.ensureProjectSession(process.cwd());
303
+ const tmuxSession = host.tmuxRuntimeManager.ensureProjectSession(projectRoot);
303
304
  const retainedTarget = service.tmuxTarget && host.tmuxRuntimeManager.hasWindow?.(service.tmuxTarget) ? service.tmuxTarget : undefined;
304
305
  const target = retainedTarget ??
305
306
  host.tmuxRuntimeManager.createWindow(tmuxSession.sessionName, label, resumeCwd, command, args, {
@@ -356,4 +357,3 @@ export function resumeOfflineServiceById(host, serviceId) {
356
357
  const restored = buildServiceStateFromMetadata(serviceId, existing.metadata);
357
358
  return resumeOfflineService(host, restored);
358
359
  }
359
- //# sourceMappingURL=services.js.map
@@ -21,4 +21,3 @@ export function extractCodexBackendSessionIdFromArgs(args) {
21
21
  }
22
22
  return undefined;
23
23
  }
24
- //# sourceMappingURL=session-capture.js.map
@@ -10,7 +10,7 @@ export declare function runDashboard(host: SessionLaunchHost): Promise<number>;
10
10
  export declare function runProjectService(host: SessionLaunchHost): Promise<number>;
11
11
  export declare function resumeSessions(host: SessionLaunchHost, toolFilter?: string): Promise<number>;
12
12
  export declare function restoreSessions(host: SessionLaunchHost, toolFilter?: string): Promise<number>;
13
- export declare function createSession(host: SessionLaunchHost, command: string, args: string[], preambleFlag?: string[], toolConfigKey?: string, extraPreamble?: string, sessionIdFlag?: string[], worktreePath?: string, backendSessionIdOverride?: string, sessionIdOverride?: string, detachedInTmux?: boolean, suppressStartupPreamble?: boolean, team?: SessionTeamMetadata): any;
13
+ export declare function createSession(host: SessionLaunchHost, command: string, args: string[], preambleFlag?: string[], toolConfigKey?: string, extraPreamble?: string, sessionIdFlag?: string[], worktreePath?: string, backendSessionIdOverride?: string, sessionIdOverride?: string, detachedInTmux?: boolean, suppressStartupPreamble?: boolean, team?: SessionTeamMetadata, launchEnv?: Record<string, string>): any;
14
14
  export declare function migrateAgent(host: SessionLaunchHost, sessionId: string, targetWorktreePath: string): Promise<void>;
15
15
  export declare function getSessionWorktreePath(host: SessionLaunchHost, sessionId: string): string | undefined;
16
16
  export declare function getSessionsByWorktree(host: SessionLaunchHost): Map<string | undefined, any[]>;
@@ -306,7 +306,7 @@ export async function restoreSessions(host, toolFilter) {
306
306
  host.openTmuxDashboardTarget();
307
307
  return 0;
308
308
  }
309
- export function createSession(host, command, args, preambleFlag, toolConfigKey, extraPreamble, sessionIdFlag, worktreePath, backendSessionIdOverride, sessionIdOverride, detachedInTmux = false, suppressStartupPreamble = false, team) {
309
+ export function createSession(host, command, args, preambleFlag, toolConfigKey, extraPreamble, sessionIdFlag, worktreePath, backendSessionIdOverride, sessionIdOverride, detachedInTmux = false, suppressStartupPreamble = false, team, launchEnv) {
310
310
  const cols = process.stdout.columns ?? 80;
311
311
  const sessionId = sessionIdOverride ?? `${command}-${Math.random().toString(36).slice(2, 8)}`;
312
312
  if (host.sessions.some((session) => session.id === sessionId)) {
@@ -314,6 +314,8 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
314
314
  }
315
315
  const config = loadConfig();
316
316
  const toolCfg = toolConfigKey ? config.tools[toolConfigKey] : undefined;
317
+ // A launch override may swap the binary; aimux flags/preamble only apply to the tool's own command.
318
+ const isConfiguredToolCommand = Boolean(toolCfg && toolCfg.command === command);
317
319
  const isClaudeResumeStyleLaunch = Boolean(toolCfg && toolConfigKey === "claude" && toolCfg.command === command) &&
318
320
  shouldSkipClaudeSessionIdInjection(args);
319
321
  const explicitClaudeBackendSessionId = toolCfg && toolConfigKey === "claude" && toolCfg.command === command
@@ -323,7 +325,7 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
323
325
  ? extractCodexBackendSessionIdFromArgs(args)
324
326
  : undefined;
325
327
  const effectiveSuppressStartupPreamble = suppressStartupPreamble;
326
- const effectiveSessionIdFlag = isClaudeResumeStyleLaunch ? undefined : sessionIdFlag;
328
+ const effectiveSessionIdFlag = isConfiguredToolCommand && !isClaudeResumeStyleLaunch ? sessionIdFlag : undefined;
327
329
  const backendSessionId = backendSessionIdOverride ??
328
330
  explicitClaudeBackendSessionId ??
329
331
  explicitCodexBackendSessionId ??
@@ -339,7 +341,7 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
339
341
  includeAimuxPreamble: automaticPreambleEnabled,
340
342
  team,
341
343
  });
342
- const shouldInjectLaunchPreamble = Boolean(!effectiveSuppressStartupPreamble && preambleFlag && preamble.trim());
344
+ const shouldInjectLaunchPreamble = Boolean(isConfiguredToolCommand && !effectiveSuppressStartupPreamble && preambleFlag && preamble.trim());
343
345
  const shouldInjectCodexDeveloperInstructions = Boolean(!effectiveSuppressStartupPreamble &&
344
346
  toolCfg?.command === command &&
345
347
  command === "codex" &&
@@ -375,6 +377,7 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
375
377
  command: launchCommand,
376
378
  args: finalArgs,
377
379
  extraEnv: {
380
+ ...(launchEnv ?? {}),
378
381
  AIMUX_SESSION_ID: sessionId,
379
382
  AIMUX_TOOL: toolConfigKey ?? command,
380
383
  },
@@ -382,13 +385,23 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
382
385
  launchCommand = wrapped.command;
383
386
  finalArgs = wrapped.args;
384
387
  }
385
- else if (toolCfg && toolCfg.command === command) {
388
+ else if (isConfiguredToolCommand) {
386
389
  const wrapped = wrapCommandWithShellIntegration({
387
390
  projectRoot,
388
391
  sessionId,
389
392
  tool: toolConfigKey ?? command,
390
393
  command: launchCommand,
391
394
  args: finalArgs,
395
+ extraEnv: launchEnv,
396
+ });
397
+ launchCommand = wrapped.command;
398
+ finalArgs = wrapped.args;
399
+ }
400
+ else if (launchEnv && Object.keys(launchEnv).length > 0) {
401
+ const wrapped = wrapCommandWithManagedLaunchEnv({
402
+ command: launchCommand,
403
+ args: finalArgs,
404
+ extraEnv: launchEnv,
392
405
  });
393
406
  launchCommand = wrapped.command;
394
407
  finalArgs = wrapped.args;
@@ -399,7 +412,7 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
399
412
  debug(`creating session: ${command} (configKey=${toolConfigKey ?? "cli"}, backendId=${backendSessionId ?? "none"}, cwd=${worktreePath ?? process.cwd()}, args=${finalArgs.length})`, "session");
400
413
  debug(`spawn args: ${JSON.stringify(summarizeLaunchArgs(finalArgs))}`, "session");
401
414
  const sessionStartTime = Date.now();
402
- const tmuxSession = host.tmuxRuntimeManager.ensureProjectSession(process.cwd());
415
+ const tmuxSession = host.tmuxRuntimeManager.ensureProjectSession(projectRoot);
403
416
  const target = host.tmuxRuntimeManager.createWindow(tmuxSession.sessionName, host.getSessionLabel(sessionId) ?? command, worktreePath ?? process.cwd(), launchCommand, finalArgs, { detached: detachedInTmux });
404
417
  const tmuxTransport = new TmuxSessionTransport(sessionId, command, target, host.tmuxRuntimeManager, cols, process.stdout.rows ?? 24);
405
418
  host.sessionTmuxTargets.set(sessionId, target);
@@ -409,7 +422,6 @@ export function createSession(host, command, args, preambleFlag, toolConfigKey,
409
422
  if (session instanceof TmuxSessionTransport) {
410
423
  host.syncTmuxWindowMetadata(sessionId);
411
424
  }
412
- void projectRoot;
413
425
  host.activeIndex = host.sessions.length - 1;
414
426
  if (host.startedInDashboard && host.mode === "dashboard") {
415
427
  host.invalidateDesktopStateSnapshot();
@@ -590,4 +602,3 @@ export function handleAction(host, action) {
590
602
  break;
591
603
  }
592
604
  }
593
- //# sourceMappingURL=session-launch.js.map
@@ -8,6 +8,7 @@ import { SessionRuntime } from "../session-runtime.js";
8
8
  import { TmuxSessionTransport } from "../tmux/session-transport.js";
9
9
  import { loadMetadataState } from "../metadata-store.js";
10
10
  import { parseAgentOutput } from "../agent-output-parser.js";
11
+ import { normalizeSubmittedPrompt, waitForTmuxPromptSubmit } from "../agent-prompt-delivery.js";
11
12
  import { captureGitContext } from "../context/context-bridge.js";
12
13
  export function getSessionLabel(host, sessionId) {
13
14
  return (host.sessionLabels.get(sessionId) ?? host.offlineSessions.find((session) => session.id === sessionId)?.label);
@@ -166,8 +167,14 @@ export async function sendAgentInput(host, sessionId, text) {
166
167
  if (!target)
167
168
  throw new Error(`Session "${sessionId}" does not have a live tmux target`);
168
169
  session.transport.retarget(target);
169
- session.transport.write(text);
170
- session.transport.write("\r");
170
+ const prompt = normalizeSubmittedPrompt(host.sessionToolKeys.get(sessionId), text, true);
171
+ session.transport.write(prompt);
172
+ await waitForTmuxPromptSubmit({
173
+ tmuxRuntimeManager: host.tmuxRuntimeManager,
174
+ target,
175
+ draft: prompt,
176
+ isTargetCurrent: () => resolveLiveSessionTmuxTarget(host, sessionId, target)?.windowId === target.windowId,
177
+ });
171
178
  }
172
179
  else {
173
180
  session.write(text);
@@ -384,4 +391,3 @@ export function updateContextWatcherSessions(host) {
384
391
  };
385
392
  }));
386
393
  }
387
- //# sourceMappingURL=session-runtime-core.js.map
@@ -707,4 +707,3 @@ export async function activateNextAttentionEntry(host) {
707
707
  const next = ordered[currentIdx >= 0 ? (currentIdx + 1) % ordered.length : 0];
708
708
  await host.activateDashboardEntryByNumber(next.index);
709
709
  }
710
- //# sourceMappingURL=subscreens.js.map
@@ -1,10 +1,11 @@
1
1
  import { type ToolConfig } from "../config.js";
2
+ import { type LaunchOverride } from "../shell-args.js";
2
3
  type ToolPickerHost = any;
3
4
  export declare function buildToolPickerOverlayOutput(host: ToolPickerHost): string;
4
5
  export declare function buildToolOptionsOverlayOutput(host: ToolPickerHost): string;
5
6
  export declare function renderToolPicker(host: ToolPickerHost): void;
6
7
  export declare function runSelectedTool(host: ToolPickerHost, toolKey: string, tool: ToolConfig, opts?: {
7
- extraArgs?: string[];
8
+ override?: LaunchOverride;
8
9
  }): void;
9
10
  export declare function showToolPicker(host: ToolPickerHost, sourceSessionId?: string): void;
10
11
  export declare function handleToolPickerKey(host: ToolPickerHost, data: Buffer): void;
@@ -1,6 +1,6 @@
1
1
  import { loadConfig } from "../config.js";
2
2
  import { parseKeys } from "../key-parser.js";
3
- import { parseShellArgs } from "../shell-args.js";
3
+ import { parseLaunchCommandLine } from "../shell-args.js";
4
4
  import { forkDashboardAgentWithFeedback, spawnDashboardAgentWithFeedback } from "./dashboard-ops.js";
5
5
  function enabledTools() {
6
6
  const config = loadConfig();
@@ -67,28 +67,37 @@ export function buildToolOptionsOverlayOutput(host) {
67
67
  }
68
68
  const [toolKey, tool] = selected;
69
69
  const buffer = host.toolOptionsBuffer ?? "";
70
- let parsedExtraArgs = [];
70
+ let override;
71
71
  let parseError = host.toolOptionsError;
72
72
  if (!parseError) {
73
73
  try {
74
- parsedExtraArgs = parseShellArgs(buffer);
74
+ override = parseLaunchCommandLine(buffer);
75
75
  }
76
76
  catch (error) {
77
77
  parseError = error instanceof Error ? error.message : String(error);
78
78
  }
79
79
  }
80
- const defaultArgs = tool.args.length ? tool.args.map(quoteShellArg).join(" ") : "(none)";
81
- const preview = commandPreview(tool.command, [...tool.args, ...parsedExtraArgs]);
82
80
  const lines = [
83
81
  host.pickerMode === "fork" && host.forkSourceSessionId
84
- ? `Fork ${toolKey}: launch options`
85
- : `${toolKey}: launch options`,
82
+ ? `Fork ${toolKey}: launch command`
83
+ : `${toolKey}: launch command`,
86
84
  "",
87
- ` Default args: ${defaultArgs}`,
88
- ` Extra args: ${buffer}_`,
89
- "",
90
- ` Command: ${preview}`,
85
+ ` ${buffer}_`,
91
86
  ];
87
+ if (override) {
88
+ lines.push("");
89
+ if (override.env) {
90
+ const envStr = Object.entries(override.env)
91
+ .map(([key, value]) => `${key}=${quoteShellArg(value)}`)
92
+ .join(" ");
93
+ lines.push(` Env: ${envStr}`);
94
+ }
95
+ lines.push(` Launch: ${commandPreview(override.command, override.args)}`);
96
+ lines.push("");
97
+ lines.push(override.command === tool.command
98
+ ? " aimux hooks + session tracking applied"
99
+ : " custom binary — launched without aimux hooks");
100
+ }
92
101
  if (parseError) {
93
102
  lines.push("");
94
103
  lines.push(` Error: ${parseError}`);
@@ -106,8 +115,7 @@ export function renderToolPicker(host) {
106
115
  }
107
116
  export function runSelectedTool(host, toolKey, tool, opts = {}) {
108
117
  const wtPath = host.mode === "dashboard" ? host.dashboardState.focusedWorktreePath : undefined;
109
- const extraArgs = opts.extraArgs ?? [];
110
- const launchArgs = [...tool.args, ...extraArgs];
118
+ const override = opts.override;
111
119
  if (host.pickerMode === "fork") {
112
120
  const sourceSessionId = host.forkSourceSessionId;
113
121
  host.pickerMode = "create";
@@ -127,7 +135,7 @@ export function runSelectedTool(host, toolKey, tool, opts = {}) {
127
135
  targetSessionId,
128
136
  tool: toolKey,
129
137
  worktreePath: wtPath,
130
- extraArgs,
138
+ launchOverride: override,
131
139
  });
132
140
  return;
133
141
  }
@@ -137,7 +145,7 @@ export function runSelectedTool(host, toolKey, tool, opts = {}) {
137
145
  targetSessionId,
138
146
  targetWorktreePath: wtPath,
139
147
  open: false,
140
- extraArgs,
148
+ launchOverride: override,
141
149
  });
142
150
  return;
143
151
  }
@@ -153,11 +161,11 @@ export function runSelectedTool(host, toolKey, tool, opts = {}) {
153
161
  sessionId,
154
162
  tool: toolKey,
155
163
  worktreePath: wtPath,
156
- extraArgs,
164
+ launchOverride: override,
157
165
  });
158
166
  return;
159
167
  }
160
- host.createSession(tool.command, launchArgs, tool.preambleFlag, toolKey, undefined, tool.sessionIdFlag, wtPath, undefined, sessionId);
168
+ host.createSession(override?.command ?? tool.command, override?.args ?? tool.args, tool.preambleFlag, toolKey, undefined, tool.sessionIdFlag, wtPath, undefined, sessionId, false, false, undefined, override?.env);
161
169
  }
162
170
  export function showToolPicker(host, sourceSessionId) {
163
171
  host.pickerMode = sourceSessionId ? "fork" : "create";
@@ -204,7 +212,7 @@ export function handleToolPickerKey(host, data) {
204
212
  return;
205
213
  }
206
214
  host.toolOptionsToolKey = picked[0];
207
- host.toolOptionsBuffer = "";
215
+ host.toolOptionsBuffer = commandPreview(picked[1].command, picked[1].args);
208
216
  host.toolOptionsError = null;
209
217
  host.openDashboardOverlay("tool-options");
210
218
  if (typeof host.redrawDashboardWithOverlay === "function") {
@@ -271,9 +279,9 @@ export function handleToolOptionsKey(host, data) {
271
279
  return;
272
280
  }
273
281
  const [toolKey, tool] = selected;
274
- let extraArgs;
282
+ let override;
275
283
  try {
276
- extraArgs = parseShellArgs(host.toolOptionsBuffer ?? "");
284
+ override = parseLaunchCommandLine(host.toolOptionsBuffer ?? "");
277
285
  }
278
286
  catch (error) {
279
287
  host.toolOptionsError = error instanceof Error ? error.message : String(error);
@@ -286,7 +294,7 @@ export function handleToolOptionsKey(host, data) {
286
294
  return;
287
295
  }
288
296
  host.clearDashboardOverlay();
289
- runSelectedTool(host, toolKey, tool, { extraArgs });
297
+ runSelectedTool(host, toolKey, tool, { override });
290
298
  return;
291
299
  }
292
300
  if (event.name === "paste" || event.char) {
@@ -300,4 +308,3 @@ export function handleToolOptionsKey(host, data) {
300
308
  }
301
309
  }
302
310
  }
303
- //# sourceMappingURL=tool-picker.js.map
@@ -17,4 +17,3 @@ export function listWorktreeGraveyardEntries() {
17
17
  export function listWorktreeGraveyardPaths() {
18
18
  return listTopologyWorktreeGraveyardPaths();
19
19
  }
20
- //# sourceMappingURL=worktree-graveyard.js.map
@@ -418,4 +418,3 @@ export function handleWorktreeListKey(host, data) {
418
418
  host.restoreDashboardAfterOverlayDismiss();
419
419
  }
420
420
  }
421
- //# sourceMappingURL=worktrees.js.map
@@ -80,4 +80,3 @@ export function shouldSuppressNotification(event) {
80
80
  }
81
81
  return false;
82
82
  }
83
- //# sourceMappingURL=notification-context.js.map
@@ -225,4 +225,3 @@ export function summarizeUnreadNotificationsBySession() {
225
225
  }
226
226
  return summaries;
227
227
  }
228
- //# sourceMappingURL=notifications.js.map
package/dist/notify.d.ts CHANGED
@@ -7,7 +7,7 @@ export declare function notifyPrompt(sessionId: string): void;
7
7
  export declare function notifyError(sessionId: string, message?: string): void;
8
8
  /** Notify that an agent completed (exited cleanly) */
9
9
  export declare function notifyComplete(sessionId: string): void;
10
- export declare function notifyAlert(event: AlertEvent): void;
10
+ export declare function notifyAlert(event: AlertEvent): boolean;
11
11
  export declare function notifyRemoteClientConnected(input: {
12
12
  title?: unknown;
13
13
  body?: unknown;
package/dist/notify.js CHANGED
@@ -3,6 +3,7 @@ import { execFile } from "node:child_process";
3
3
  import { loadConfig } from "./config.js";
4
4
  import { debug } from "./debug.js";
5
5
  import { shouldSuppressNotification } from "./notification-context.js";
6
+ import { forwardAlertToMobilePush } from "./mobile-push-bridge.js";
6
7
  let cachedConfig = null;
7
8
  function getNotifyConfig() {
8
9
  if (!cachedConfig) {
@@ -68,9 +69,9 @@ export function notifyComplete(sessionId) {
68
69
  export function notifyAlert(event) {
69
70
  const config = getNotifyConfig();
70
71
  if (!config.enabled)
71
- return;
72
+ return false;
72
73
  if (shouldSuppressNotification(event))
73
- return;
74
+ return false;
74
75
  if ((event.kind === "notification" ||
75
76
  event.kind === "needs_input" ||
76
77
  event.kind === "message_waiting" ||
@@ -78,17 +79,18 @@ export function notifyAlert(event) {
78
79
  event.kind === "task_assigned" ||
79
80
  event.kind === "review_waiting") &&
80
81
  !config.onPrompt) {
81
- return;
82
+ return false;
82
83
  }
83
84
  if (event.kind === "task_done" && !config.onComplete)
84
- return;
85
+ return false;
85
86
  if ((event.kind === "task_failed" || event.kind === "blocked") && !config.onError)
86
- return;
87
+ return false;
87
88
  send(event.title || "aimux", event.message || event.sessionId || event.kind);
89
+ forwardAlertToMobilePush(event);
90
+ return true;
88
91
  }
89
92
  export function notifyRemoteClientConnected(input) {
90
93
  const title = typeof input.title === "string" && input.title.trim().length > 0 ? input.title : "aimux remote access";
91
94
  const body = typeof input.body === "string" && input.body.trim().length > 0 ? input.body : "Remote client connected";
92
95
  sendSecurity(title, body);
93
96
  }
94
- //# sourceMappingURL=notify.js.map
@@ -307,4 +307,3 @@ export async function reopenTask(input) {
307
307
  await writeTask(reopened);
308
308
  return { task: reopened };
309
309
  }
310
- //# sourceMappingURL=orchestration-actions.js.map
@@ -71,4 +71,3 @@ export function resolveOrchestrationRecipients(input) {
71
71
  .sort((a, b) => scoreCandidate(b, input) - scoreCandidate(a, input) || a.id.localeCompare(b.id))
72
72
  .map((candidate) => candidate.id);
73
73
  }
74
- //# sourceMappingURL=orchestration-routing.js.map
@@ -107,4 +107,3 @@ export function sendDirectMessage(input) {
107
107
  threadCreated: !existing,
108
108
  };
109
109
  }
110
- //# sourceMappingURL=orchestration.js.map
@@ -177,4 +177,3 @@ export class OscNotificationParser {
177
177
  return { cleaned, notifications };
178
178
  }
179
179
  }
180
- //# sourceMappingURL=osc-notifications.js.map
package/dist/paths.js CHANGED
@@ -12,9 +12,10 @@
12
12
  */
13
13
  import { createHash } from "node:crypto";
14
14
  import { execSync } from "node:child_process";
15
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
15
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, realpathSync } from "node:fs";
16
16
  import { join, basename, resolve, dirname, sep } from "node:path";
17
- import { homedir } from "node:os";
17
+ import { homedir, tmpdir } from "node:os";
18
+ import { quarantineCorruptFile, writeJsonAtomic } from "./atomic-write.js";
18
19
  // ── Cached state (populated by initPaths) ──────────────────────────
19
20
  let _repoRoot = null;
20
21
  let _projectId = null;
@@ -264,25 +265,69 @@ export function getGlobalTeamPath() {
264
265
  export function getProjectsRegistryPath() {
265
266
  return join(getGlobalAimuxDir(), "projects.json");
266
267
  }
268
+ const MAX_PROJECT_REGISTRY_ENTRIES = 500;
269
+ const OS_TMP_DIR = tmpdir();
270
+ const TEMP_DIR_CANDIDATES = [OS_TMP_DIR, "/tmp", "/private/tmp", "/var/tmp"];
271
+ const TEMP_DIRS = (() => {
272
+ const dirs = new Set();
273
+ for (const dir of TEMP_DIR_CANDIDATES) {
274
+ const resolved = resolve(dir);
275
+ dirs.add(resolved);
276
+ try {
277
+ dirs.add(realpathSync(resolved));
278
+ }
279
+ catch { }
280
+ }
281
+ return [...dirs];
282
+ })();
283
+ function isPathInsideDir(path, dir) {
284
+ return path === dir || path.startsWith(`${dir}${sep}`);
285
+ }
286
+ function isEphemeralTempProjectRoot(repoRoot) {
287
+ const resolved = resolve(repoRoot);
288
+ return basename(resolved).startsWith("aimux-") && TEMP_DIRS.some((dir) => isPathInsideDir(resolved, dir));
289
+ }
290
+ function assertRegistryWithinCap(projects) {
291
+ if (projects.length <= MAX_PROJECT_REGISTRY_ENTRIES)
292
+ return;
293
+ throw new Error(`aimux project registry has ${projects.length} entries; cap is ${MAX_PROJECT_REGISTRY_ENTRIES}. ` +
294
+ `Refusing to continue because the registry is likely polluted. Remove stale entries from ${getProjectsRegistryPath()}.`);
295
+ }
296
+ function normalizeRegistry(registry) {
297
+ const projectsById = new Map();
298
+ for (const project of registry.projects) {
299
+ if (isEphemeralTempProjectRoot(project.repoRoot))
300
+ continue;
301
+ projectsById.set(project.id, project);
302
+ }
303
+ const projects = [...projectsById.values()];
304
+ assertRegistryWithinCap(projects);
305
+ return { version: 1, projects };
306
+ }
267
307
  function loadRegistry() {
268
308
  const path = getProjectsRegistryPath();
269
309
  if (!existsSync(path))
270
310
  return { version: 1, projects: [] };
311
+ let registry;
271
312
  try {
272
- return JSON.parse(readFileSync(path, "utf-8"));
313
+ registry = JSON.parse(readFileSync(path, "utf-8"));
273
314
  }
274
315
  catch {
316
+ quarantineCorruptFile(path);
275
317
  return { version: 1, projects: [] };
276
318
  }
319
+ return normalizeRegistry(registry);
277
320
  }
278
321
  function saveRegistry(registry) {
279
322
  const dir = getGlobalAimuxDir();
280
323
  if (!existsSync(dir))
281
324
  mkdirSync(dir, { recursive: true });
282
- writeFileSync(getProjectsRegistryPath(), JSON.stringify(registry, null, 2) + "\n");
325
+ writeJsonAtomic(getProjectsRegistryPath(), registry);
283
326
  }
284
327
  function registerProject() {
285
328
  assertInitialized();
329
+ if (isEphemeralTempProjectRoot(_repoRoot))
330
+ return;
286
331
  const registry = loadRegistry();
287
332
  const idx = registry.projects.findIndex((p) => p.id === _projectId);
288
333
  const entry = {
@@ -297,6 +342,7 @@ function registerProject() {
297
342
  else {
298
343
  registry.projects.push(entry);
299
344
  }
345
+ assertRegistryWithinCap(registry.projects);
300
346
  saveRegistry(registry);
301
347
  }
302
348
  export function listProjects() {
@@ -307,4 +353,3 @@ export function removeProject(id) {
307
353
  registry.projects = registry.projects.filter((p) => p.id !== id);
308
354
  saveRegistry(registry);
309
355
  }
310
- //# sourceMappingURL=paths.js.map
@@ -11,4 +11,3 @@ const BLOCKING_PENDING_DASHBOARD_ACTIONS = new Set([
11
11
  export function isBlockingPendingDashboardActionKind(kind) {
12
12
  return Boolean(kind && BLOCKING_PENDING_DASHBOARD_ACTIONS.has(kind));
13
13
  }
14
- //# sourceMappingURL=pending-actions.js.map
@@ -323,4 +323,3 @@ export function deriveAlertFromAgentEvent(sessionId, event) {
323
323
  cooldownMs: kind === "task_done" ? 10_000 : 15_000,
324
324
  };
325
325
  }
326
- //# sourceMappingURL=plugin-runtime.js.map
@@ -53,4 +53,3 @@ export class ProjectEventBus {
53
53
  return true;
54
54
  }
55
55
  }
56
- //# sourceMappingURL=project-events.js.map
@@ -180,4 +180,3 @@ export function listDesktopProjects(tmux = new TmuxRuntimeManager()) {
180
180
  }
181
181
  return [...projects.values()].sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path));
182
182
  }
183
- //# sourceMappingURL=project-scanner.js.map
@@ -51,4 +51,3 @@ export function manifestsMatch(expected, actual) {
51
51
  const actualCapabilities = actual.capabilities || {};
52
52
  return Object.entries(expected.capabilities).every(([key, value]) => actualCapabilities[key] === value);
53
53
  }
54
- //# sourceMappingURL=project-service-manifest.js.map
@@ -0,0 +1 @@
1
+ export declare function takeOverProjectFromOtherOwners(projectRoot: string): Promise<void>;