aimux-cli 0.1.13 → 0.1.14

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 (212) hide show
  1. package/README.md +54 -0
  2. package/dist/agent-prompt-delivery.d.ts +31 -0
  3. package/dist/agent-prompt-delivery.js +119 -0
  4. package/dist/agent-prompt-delivery.js.map +1 -0
  5. package/dist/agent-tracker.js +4 -2
  6. package/dist/agent-tracker.js.map +1 -1
  7. package/dist/atomic-write.d.ts +1 -0
  8. package/dist/atomic-write.js +9 -0
  9. package/dist/atomic-write.js.map +1 -0
  10. package/dist/builtin-metadata-watchers.js +10 -6
  11. package/dist/builtin-metadata-watchers.js.map +1 -1
  12. package/dist/daemon.js +3 -6
  13. package/dist/daemon.js.map +1 -1
  14. package/dist/dashboard/index.d.ts +26 -15
  15. package/dist/dashboard/index.js +26 -56
  16. package/dist/dashboard/index.js.map +1 -1
  17. package/dist/dashboard/operation-failures.d.ts +30 -0
  18. package/dist/dashboard/operation-failures.js +86 -0
  19. package/dist/dashboard/operation-failures.js.map +1 -0
  20. package/dist/dashboard/pending-actions.d.ts +9 -2
  21. package/dist/dashboard/pending-actions.js +66 -7
  22. package/dist/dashboard/pending-actions.js.map +1 -1
  23. package/dist/dashboard/quick-jump.d.ts +3 -1
  24. package/dist/dashboard/quick-jump.js +27 -8
  25. package/dist/dashboard/quick-jump.js.map +1 -1
  26. package/dist/dashboard/runtime-evidence.d.ts +4 -0
  27. package/dist/dashboard/runtime-evidence.js +22 -0
  28. package/dist/dashboard/runtime-evidence.js.map +1 -0
  29. package/dist/dashboard/session-actions.d.ts +1 -1
  30. package/dist/dashboard/session-actions.js +3 -1
  31. package/dist/dashboard/session-actions.js.map +1 -1
  32. package/dist/dashboard/session-registry.d.ts +1 -0
  33. package/dist/dashboard/session-registry.js +47 -30
  34. package/dist/dashboard/session-registry.js.map +1 -1
  35. package/dist/dashboard/state.d.ts +9 -0
  36. package/dist/dashboard/state.js +33 -1
  37. package/dist/dashboard/state.js.map +1 -1
  38. package/dist/dashboard/targets.js +11 -3
  39. package/dist/dashboard/targets.js.map +1 -1
  40. package/dist/dashboard/ui-state-store.d.ts +3 -3
  41. package/dist/dashboard/ui-state-store.js +23 -14
  42. package/dist/dashboard/ui-state-store.js.map +1 -1
  43. package/dist/default-plugins/gh-pr-context.d.ts +8 -0
  44. package/dist/default-plugins/gh-pr-context.js +256 -0
  45. package/dist/default-plugins/gh-pr-context.js.map +1 -0
  46. package/dist/default-plugins/transcript-length.d.ts +1 -0
  47. package/dist/default-plugins/transcript-length.js +10 -0
  48. package/dist/default-plugins/transcript-length.js.map +1 -1
  49. package/dist/fast-control.d.ts +8 -3
  50. package/dist/fast-control.js +13 -14
  51. package/dist/fast-control.js.map +1 -1
  52. package/dist/main.js +22 -35
  53. package/dist/main.js.map +1 -1
  54. package/dist/managed-launch-env.d.ts +10 -0
  55. package/dist/managed-launch-env.js +51 -0
  56. package/dist/managed-launch-env.js.map +1 -0
  57. package/dist/metadata-server.d.ts +41 -0
  58. package/dist/metadata-server.js +272 -15
  59. package/dist/metadata-server.js.map +1 -1
  60. package/dist/metadata-store.d.ts +1 -0
  61. package/dist/metadata-store.js +3 -5
  62. package/dist/metadata-store.js.map +1 -1
  63. package/dist/multiplexer/agent-io-methods.d.ts +1 -1
  64. package/dist/multiplexer/agent-io-methods.js +36 -29
  65. package/dist/multiplexer/agent-io-methods.js.map +1 -1
  66. package/dist/multiplexer/archives.d.ts +1 -0
  67. package/dist/multiplexer/archives.js +143 -25
  68. package/dist/multiplexer/archives.js.map +1 -1
  69. package/dist/multiplexer/dashboard-actions-methods.d.ts +10 -1
  70. package/dist/multiplexer/dashboard-actions-methods.js +16 -4
  71. package/dist/multiplexer/dashboard-actions-methods.js.map +1 -1
  72. package/dist/multiplexer/dashboard-control.d.ts +13 -1
  73. package/dist/multiplexer/dashboard-control.js +150 -121
  74. package/dist/multiplexer/dashboard-control.js.map +1 -1
  75. package/dist/multiplexer/dashboard-interaction.d.ts +2 -1
  76. package/dist/multiplexer/dashboard-interaction.js +175 -113
  77. package/dist/multiplexer/dashboard-interaction.js.map +1 -1
  78. package/dist/multiplexer/dashboard-model.d.ts +5 -1
  79. package/dist/multiplexer/dashboard-model.js +83 -8
  80. package/dist/multiplexer/dashboard-model.js.map +1 -1
  81. package/dist/multiplexer/dashboard-ops.d.ts +32 -1
  82. package/dist/multiplexer/dashboard-ops.js +517 -15
  83. package/dist/multiplexer/dashboard-ops.js.map +1 -1
  84. package/dist/multiplexer/dashboard-state-methods.d.ts +7 -0
  85. package/dist/multiplexer/dashboard-state-methods.js +79 -4
  86. package/dist/multiplexer/dashboard-state-methods.js.map +1 -1
  87. package/dist/multiplexer/dashboard-tail-methods.d.ts +12 -2
  88. package/dist/multiplexer/dashboard-tail-methods.js +18 -6
  89. package/dist/multiplexer/dashboard-tail-methods.js.map +1 -1
  90. package/dist/multiplexer/dashboard-view-methods.d.ts +3 -1
  91. package/dist/multiplexer/dashboard-view-methods.js +53 -38
  92. package/dist/multiplexer/dashboard-view-methods.js.map +1 -1
  93. package/dist/multiplexer/graveyard-view-model.d.ts +68 -0
  94. package/dist/multiplexer/graveyard-view-model.js +185 -0
  95. package/dist/multiplexer/graveyard-view-model.js.map +1 -0
  96. package/dist/multiplexer/index.d.ts +18 -11
  97. package/dist/multiplexer/index.js +30 -18
  98. package/dist/multiplexer/index.js.map +1 -1
  99. package/dist/multiplexer/navigation.d.ts +1 -1
  100. package/dist/multiplexer/navigation.js +31 -11
  101. package/dist/multiplexer/navigation.js.map +1 -1
  102. package/dist/multiplexer/notifications.d.ts +1 -0
  103. package/dist/multiplexer/notifications.js +27 -19
  104. package/dist/multiplexer/notifications.js.map +1 -1
  105. package/dist/multiplexer/persistence-methods.d.ts +16 -0
  106. package/dist/multiplexer/persistence-methods.js +349 -28
  107. package/dist/multiplexer/persistence-methods.js.map +1 -1
  108. package/dist/multiplexer/runtime-lifecycle-methods.js +81 -12
  109. package/dist/multiplexer/runtime-lifecycle-methods.js.map +1 -1
  110. package/dist/multiplexer/runtime-state.d.ts +6 -2
  111. package/dist/multiplexer/runtime-state.js +144 -23
  112. package/dist/multiplexer/runtime-state.js.map +1 -1
  113. package/dist/multiplexer/service-state-snapshot.d.ts +14 -0
  114. package/dist/multiplexer/service-state-snapshot.js +121 -0
  115. package/dist/multiplexer/service-state-snapshot.js.map +1 -0
  116. package/dist/multiplexer/services.d.ts +19 -1
  117. package/dist/multiplexer/services.js +114 -16
  118. package/dist/multiplexer/services.js.map +1 -1
  119. package/dist/multiplexer/session-actions.d.ts +3 -0
  120. package/dist/multiplexer/session-actions.js +10 -8
  121. package/dist/multiplexer/session-actions.js.map +1 -1
  122. package/dist/multiplexer/session-launch.d.ts +1 -1
  123. package/dist/multiplexer/session-launch.js +63 -22
  124. package/dist/multiplexer/session-launch.js.map +1 -1
  125. package/dist/multiplexer/session-runtime-core.d.ts +2 -1
  126. package/dist/multiplexer/session-runtime-core.js +107 -69
  127. package/dist/multiplexer/session-runtime-core.js.map +1 -1
  128. package/dist/multiplexer/subscreens.d.ts +1 -0
  129. package/dist/multiplexer/subscreens.js +17 -12
  130. package/dist/multiplexer/subscreens.js.map +1 -1
  131. package/dist/multiplexer/tool-picker.d.ts +6 -2
  132. package/dist/multiplexer/tool-picker.js +231 -81
  133. package/dist/multiplexer/tool-picker.js.map +1 -1
  134. package/dist/multiplexer/worktree-graveyard.d.ts +13 -0
  135. package/dist/multiplexer/worktree-graveyard.js +21 -0
  136. package/dist/multiplexer/worktree-graveyard.js.map +1 -0
  137. package/dist/multiplexer/worktrees.d.ts +1 -0
  138. package/dist/multiplexer/worktrees.js +151 -69
  139. package/dist/multiplexer/worktrees.js.map +1 -1
  140. package/dist/notification-context.js +9 -15
  141. package/dist/notification-context.js.map +1 -1
  142. package/dist/notifications.d.ts +22 -0
  143. package/dist/notifications.js +66 -11
  144. package/dist/notifications.js.map +1 -1
  145. package/dist/orchestration-actions.js +3 -3
  146. package/dist/orchestration-actions.js.map +1 -1
  147. package/dist/orchestration-dispatcher.d.ts +4 -3
  148. package/dist/orchestration-dispatcher.js +9 -7
  149. package/dist/orchestration-dispatcher.js.map +1 -1
  150. package/dist/orchestration-routing.d.ts +2 -2
  151. package/dist/orchestration-routing.js +3 -7
  152. package/dist/orchestration-routing.js.map +1 -1
  153. package/dist/paths.d.ts +3 -0
  154. package/dist/paths.js +9 -0
  155. package/dist/paths.js.map +1 -1
  156. package/dist/plugin-runtime.d.ts +5 -0
  157. package/dist/plugin-runtime.js +98 -34
  158. package/dist/plugin-runtime.js.map +1 -1
  159. package/dist/project-events.js +4 -2
  160. package/dist/project-events.js.map +1 -1
  161. package/dist/session-bootstrap.d.ts +0 -3
  162. package/dist/session-bootstrap.js +12 -104
  163. package/dist/session-bootstrap.js.map +1 -1
  164. package/dist/session-semantics.d.ts +44 -7
  165. package/dist/session-semantics.js +164 -69
  166. package/dist/session-semantics.js.map +1 -1
  167. package/dist/shell-args.d.ts +1 -0
  168. package/dist/shell-args.js +57 -0
  169. package/dist/shell-args.js.map +1 -0
  170. package/dist/shell-hooks.d.ts +3 -0
  171. package/dist/shell-hooks.js +19 -2
  172. package/dist/shell-hooks.js.map +1 -1
  173. package/dist/statusline-model.d.ts +5 -1
  174. package/dist/statusline-model.js +18 -29
  175. package/dist/statusline-model.js.map +1 -1
  176. package/dist/task-dispatcher.d.ts +3 -3
  177. package/dist/task-dispatcher.js +6 -5
  178. package/dist/task-dispatcher.js.map +1 -1
  179. package/dist/task-workflow.d.ts +2 -0
  180. package/dist/task-workflow.js +15 -6
  181. package/dist/task-workflow.js.map +1 -1
  182. package/dist/tasks.d.ts +4 -1
  183. package/dist/tasks.js +19 -1
  184. package/dist/tasks.js.map +1 -1
  185. package/dist/tmux/doctor.d.ts +1 -0
  186. package/dist/tmux/doctor.js +4 -1
  187. package/dist/tmux/doctor.js.map +1 -1
  188. package/dist/tmux/runtime-manager.d.ts +6 -0
  189. package/dist/tmux/runtime-manager.js +56 -18
  190. package/dist/tmux/runtime-manager.js.map +1 -1
  191. package/dist/tmux/statusline.js +17 -16
  192. package/dist/tmux/statusline.js.map +1 -1
  193. package/dist/tmux/window-open.d.ts +1 -0
  194. package/dist/tmux/window-open.js +10 -10
  195. package/dist/tmux/window-open.js.map +1 -1
  196. package/dist/tool-output-watchers.d.ts +2 -0
  197. package/dist/tool-output-watchers.js +123 -12
  198. package/dist/tool-output-watchers.js.map +1 -1
  199. package/dist/tui/screens/dashboard-renderers.d.ts +2 -21
  200. package/dist/tui/screens/dashboard-renderers.js +126 -32
  201. package/dist/tui/screens/dashboard-renderers.js.map +1 -1
  202. package/dist/tui/screens/overlay-renderers.d.ts +11 -1
  203. package/dist/tui/screens/overlay-renderers.js +68 -28
  204. package/dist/tui/screens/overlay-renderers.js.map +1 -1
  205. package/dist/tui/screens/subscreen-renderers.js +156 -33
  206. package/dist/tui/screens/subscreen-renderers.js.map +1 -1
  207. package/dist/workflow.js +2 -2
  208. package/dist/workflow.js.map +1 -1
  209. package/dist/worktree.d.ts +3 -0
  210. package/dist/worktree.js +27 -2
  211. package/dist/worktree.js.map +1 -1
  212. package/package.json +5 -1
@@ -1,15 +1,30 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
2
- import { execFileSync, spawn } from "node:child_process";
2
+ import { spawn } from "node:child_process";
3
3
  import { basename, join } from "node:path";
4
4
  import { debug } from "../debug.js";
5
5
  import { DashboardPendingActions } from "../dashboard/pending-actions.js";
6
+ import { addDashboardOperationFailure, clearDashboardOperationFailures, listDashboardOperationFailures, } from "../dashboard/operation-failures.js";
7
+ import { composeDashboardWorktreeGroups } from "./dashboard-model.js";
6
8
  import { loadDaemonInfo } from "../daemon.js";
7
9
  import { getGraveyardPath, getLocalAimuxDir, getProjectStateDir, getStatePath } from "../paths.js";
8
10
  import { loadMetadataState } from "../metadata-store.js";
9
- import { renderCurrentDashboardView as renderCurrentDashboardViewImpl } from "./runtime-state.js";
11
+ import { renderCurrentDashboardView as renderCurrentDashboardViewImpl, stopSessionToOffline } from "./runtime-state.js";
12
+ import { listWorktreeGraveyardEntries as listWorktreeGraveyardEntriesImpl, listWorktreeGraveyardPaths, writeWorktreeGraveyardEntries, } from "./worktree-graveyard.js";
10
13
  import { loadStatusline, renderTmuxStatuslineFromData } from "../tmux/statusline.js";
11
14
  import { ensureTmuxStatuslineDir, invalidateTmuxStatuslineArtifacts } from "../tmux/statusline-cache.js";
12
- import { findMainRepo, getWorktreeBaseDir, getWorktreeCreatePath, listWorktrees as listAllWorktrees, } from "../worktree.js";
15
+ import { markLastUsed } from "../last-used.js";
16
+ import { findMainRepo, getWorktreeBaseDir, getWorktreeAddArgs, getWorktreeCreatePath, isToolInternalWorktree, listWorktrees as listAllWorktrees, } from "../worktree.js";
17
+ function recordDashboardFailure(host, input) {
18
+ const failure = addDashboardOperationFailure(input);
19
+ host.publishAlert?.({
20
+ kind: "task_failed",
21
+ title: input.title,
22
+ message: input.message,
23
+ worktreePath: input.worktreePath,
24
+ dedupeKey: `dashboard-operation-failed:${input.targetKind}:${input.operation}:${input.targetId ?? input.worktreePath ?? input.title}:${input.message}`,
25
+ });
26
+ return failure;
27
+ }
13
28
  export const persistenceMethods = {
14
29
  writeSessionsFile() {
15
30
  const dir = getLocalAimuxDir();
@@ -55,6 +70,8 @@ export const persistenceMethods = {
55
70
  const managedWindows = this.tmuxRuntimeManager.listProjectManagedWindows(process.cwd());
56
71
  const liveTargets = new Map();
57
72
  for (const { target, metadata } of managedWindows) {
73
+ if (metadata.kind !== "agent")
74
+ continue;
58
75
  liveTargets.set(metadata.sessionId, target);
59
76
  }
60
77
  for (const session of this.sessions) {
@@ -190,6 +207,7 @@ export const persistenceMethods = {
190
207
  services: desktopState.services,
191
208
  statusline: this.buildStatuslineSnapshot(),
192
209
  worktrees: desktopState.worktrees,
210
+ operationFailures: desktopState.operationFailures,
193
211
  mainCheckoutInfo: desktopState.mainCheckoutInfo,
194
212
  mainCheckoutPath: desktopState.mainCheckoutPath,
195
213
  };
@@ -197,13 +215,14 @@ export const persistenceMethods = {
197
215
  reapplyDashboardPendingActions() {
198
216
  this.dashboardSessionsCache = this.dashboardPendingActions.applyToSessions(this.dashboardSessionsCache.map(({ pendingAction: _pendingAction, optimistic: _optimistic, ...session }) => session));
199
217
  this.dashboardServicesCache = this.dashboardPendingActions.applyToServices(this.dashboardServicesCache.map(({ pendingAction: _pendingAction, optimistic: _optimistic, ...service }) => service));
200
- this.dashboardWorktreeGroupsCache = this.dashboardPendingActions.applyToWorktrees(this.dashboardWorktreeGroupsCache.map(({ pendingAction: _pendingAction, optimistic: _optimistic, pending: _pending, removing: _removing, ...wt }) => wt));
218
+ this.dashboardWorktreeGroupsCache = composeDashboardWorktreeGroups(this.dashboardPendingActions.applyToWorktrees(this.dashboardWorktreeGroupsCache.map(({ pendingAction: _pendingAction, optimistic: _optimistic, pending: _pending, removing: _removing, operationFailure: _operationFailure, ...wt }) => wt)), this.dashboardSessionsCache, this.dashboardServicesCache);
201
219
  },
202
220
  listDesktopWorktrees() {
203
221
  const pendingCreates = this.pendingWorktreeCreates;
204
222
  const pendingRemovals = this.pendingWorktreeRemovals;
223
+ const hiddenPaths = listWorktreeGraveyardPaths();
205
224
  const worktrees = listAllWorktrees()
206
- .filter((wt) => !wt.isBare)
225
+ .filter((wt) => !wt.isBare && !hiddenPaths.has(wt.path) && !isToolInternalWorktree(wt))
207
226
  .map((wt) => ({
208
227
  ...wt,
209
228
  pending: pendingRemovals?.has(wt.path) ?? false,
@@ -226,9 +245,168 @@ export const persistenceMethods = {
226
245
  });
227
246
  }
228
247
  }
248
+ const worktreePaths = new Set(worktrees.map((worktree) => worktree.path));
249
+ for (const failure of listDashboardOperationFailures()) {
250
+ if (failure.targetKind !== "worktree" || failure.operation !== "create" || !failure.worktreePath)
251
+ continue;
252
+ if (hiddenPaths.has(failure.worktreePath) || worktreePaths.has(failure.worktreePath))
253
+ continue;
254
+ worktrees.push({
255
+ name: failure.worktreeName ?? basename(failure.worktreePath),
256
+ path: failure.worktreePath,
257
+ branch: "(failed)",
258
+ isBare: false,
259
+ createdAt: failure.createdAt,
260
+ operationFailure: failure,
261
+ });
262
+ worktreePaths.add(failure.worktreePath);
263
+ }
229
264
  sortDesktopWorktrees(worktrees);
230
265
  return worktrees;
231
266
  },
267
+ listWorktreeGraveyardEntries() {
268
+ return listWorktreeGraveyardEntriesImpl();
269
+ },
270
+ async graveyardDesktopWorktree(path) {
271
+ this.syncSessionsFromState();
272
+ const mainRepo = findMainRepo();
273
+ if (path === mainRepo) {
274
+ throw new Error("Cannot graveyard the main checkout");
275
+ }
276
+ const matching = listAllWorktrees()
277
+ .filter((worktree) => !worktree.isBare)
278
+ .find((worktree) => worktree.path === path);
279
+ if (!matching) {
280
+ throw new Error(`Worktree "${path}" not found`);
281
+ }
282
+ const worktreeGraveyardEntries = this.listWorktreeGraveyardEntries();
283
+ if (worktreeGraveyardEntries.some((entry) => entry.path === path)) {
284
+ throw new Error(`Worktree "${matching.name}" is already in the graveyard`);
285
+ }
286
+ const attachedServices = collectWorktreeServices(this, path);
287
+ detachWorktreeServices(this, path);
288
+ const liveSessions = this.sessions.filter((session) => this.sessionWorktreePaths.get(session.id) === path && this.isSessionRuntimeLive(session) && !session.exited);
289
+ for (const session of liveSessions) {
290
+ stopSessionToOffline(this, session);
291
+ }
292
+ await waitForWorktreeSessionsToStop(this, path);
293
+ const attachedAgents = collectWorktreeAgents(this, path);
294
+ const nextEntries = [
295
+ ...worktreeGraveyardEntries.filter((entry) => entry.path !== path),
296
+ {
297
+ name: matching.name,
298
+ path: matching.path,
299
+ branch: matching.branch,
300
+ createdAt: matching.createdAt,
301
+ graveyardedAt: new Date().toISOString(),
302
+ agents: attachedAgents,
303
+ services: attachedServices,
304
+ },
305
+ ];
306
+ writeWorktreeGraveyardEntries(nextEntries);
307
+ this.worktreeGraveyardEntries = nextEntries;
308
+ this.offlineSessions = this.offlineSessions.filter((session) => session.worktreePath !== path);
309
+ this.saveState();
310
+ this.invalidateDesktopStateSnapshot();
311
+ this.refreshLocalDashboardModel();
312
+ this.metadataServer?.notifyChange?.();
313
+ if (this.mode === "dashboard") {
314
+ this.renderDashboard();
315
+ }
316
+ return { path, status: "graveyarded" };
317
+ },
318
+ async resurrectGraveyardWorktree(path) {
319
+ const entries = this.listWorktreeGraveyardEntries();
320
+ const entry = entries.find((candidate) => candidate.path === path);
321
+ if (!entry) {
322
+ throw new Error(`Graveyard worktree "${path}" not found`);
323
+ }
324
+ const nextEntries = entries.filter((candidate) => candidate.path !== path);
325
+ writeWorktreeGraveyardEntries(nextEntries);
326
+ this.worktreeGraveyardEntries = nextEntries;
327
+ const flatAgents = takeFlatGraveyardAgentsForWorktree(path);
328
+ const seen = new Set(this.offlineSessions.map((session) => session.id));
329
+ for (const agent of [...entry.agents, ...flatAgents]) {
330
+ if (seen.has(agent.id))
331
+ continue;
332
+ this.offlineSessions.push(agent);
333
+ seen.add(agent.id);
334
+ }
335
+ const serviceSeen = new Set(this.offlineServices.map((service) => service.id));
336
+ for (const service of entry.services ?? []) {
337
+ if (serviceSeen.has(service.id))
338
+ continue;
339
+ this.offlineServices.push(service);
340
+ serviceSeen.add(service.id);
341
+ }
342
+ this.saveState();
343
+ this.invalidateDesktopStateSnapshot();
344
+ this.refreshLocalDashboardModel();
345
+ this.metadataServer?.notifyChange?.();
346
+ if (this.mode === "dashboard") {
347
+ this.renderDashboard();
348
+ }
349
+ return { path, status: "offline" };
350
+ },
351
+ async deleteGraveyardWorktree(path) {
352
+ const entries = this.listWorktreeGraveyardEntries();
353
+ const entry = entries.find((candidate) => candidate.path === path);
354
+ if (!entry) {
355
+ throw new Error(`Graveyard worktree "${path}" not found`);
356
+ }
357
+ const mainRepo = findMainRepo();
358
+ if (path !== mainRepo) {
359
+ if (!existsSync(path)) {
360
+ await removeOrphanedDesktopWorktree(this, mainRepo, path);
361
+ }
362
+ else {
363
+ await new Promise((resolve, reject) => {
364
+ let stderr = "";
365
+ let child;
366
+ try {
367
+ child = spawn("git", ["worktree", "remove", path, "--force"], {
368
+ cwd: mainRepo,
369
+ stdio: ["ignore", "ignore", "pipe"],
370
+ });
371
+ }
372
+ catch (error) {
373
+ reject(error);
374
+ return;
375
+ }
376
+ child.stderr.on("data", (chunk) => {
377
+ stderr += chunk.toString();
378
+ });
379
+ child.on("error", reject);
380
+ child.on("close", (code) => {
381
+ if (code === 0) {
382
+ resolve();
383
+ return;
384
+ }
385
+ const detail = stderr
386
+ .split("\n")
387
+ .map((line) => line.trim())
388
+ .filter(Boolean)
389
+ .at(-1);
390
+ reject(new Error(detail || `git worktree remove exited with code ${code ?? 1}`));
391
+ });
392
+ });
393
+ }
394
+ }
395
+ const nextEntries = entries.filter((candidate) => candidate.path !== path);
396
+ writeWorktreeGraveyardEntries(nextEntries);
397
+ this.worktreeGraveyardEntries = nextEntries;
398
+ this.offlineSessions = this.offlineSessions.filter((session) => session.worktreePath !== path);
399
+ this.offlineServices = this.offlineServices.filter((service) => service.worktreePath !== path);
400
+ removeFlatGraveyardAgentsForWorktree(path);
401
+ this.saveState();
402
+ this.invalidateDesktopStateSnapshot();
403
+ this.refreshLocalDashboardModel();
404
+ this.metadataServer?.notifyChange?.();
405
+ if (this.mode === "dashboard") {
406
+ this.renderDashboard();
407
+ }
408
+ return { path, status: "removed" };
409
+ },
232
410
  createDesktopWorktree(name) {
233
411
  const targetPath = getWorktreeCreatePath(name);
234
412
  const pendingCreates = this.pendingWorktreeCreates;
@@ -236,9 +414,10 @@ export const persistenceMethods = {
236
414
  if (existingCreate) {
237
415
  return { path: targetPath, status: "creating" };
238
416
  }
239
- if (this.listDesktopWorktrees().some((worktree) => worktree.path === targetPath && !worktree.pending)) {
417
+ if (this.listDesktopWorktrees().some((worktree) => worktree.path === targetPath && !worktree.pending && !worktree.operationFailure)) {
240
418
  throw new Error(`Worktree "${name}" already exists`);
241
419
  }
420
+ clearDashboardOperationFailures({ targetKind: "worktree", operation: "create", worktreePath: targetPath });
242
421
  let resolveCreate;
243
422
  let rejectCreate;
244
423
  const createPromise = new Promise((resolve, reject) => {
@@ -246,10 +425,28 @@ export const persistenceMethods = {
246
425
  rejectCreate = reject;
247
426
  });
248
427
  pendingCreates.set(targetPath, createPromise);
428
+ const clearPendingCreate = () => {
429
+ if (pendingCreates.get(targetPath) === createPromise) {
430
+ pendingCreates.delete(targetPath);
431
+ }
432
+ if (this.worktreeCreateJob?.path === targetPath) {
433
+ this.worktreeCreateJob = null;
434
+ }
435
+ };
249
436
  this.dashboardPendingActions.set(DashboardPendingActions.worktreeKey(targetPath), "creating", {
250
437
  timeoutMs: 180_000,
251
438
  onTimeout: () => {
252
- this.footerFlash = `Timed out creating ${name}`;
439
+ clearPendingCreate();
440
+ const message = `Timed out creating worktree "${name}"`;
441
+ recordDashboardFailure(this, {
442
+ targetKind: "worktree",
443
+ operation: "create",
444
+ title: `Failed to create worktree "${name}"`,
445
+ message,
446
+ worktreePath: targetPath,
447
+ worktreeName: name,
448
+ });
449
+ this.footerFlash = message;
253
450
  this.footerFlashTicks = 5;
254
451
  this.invalidateDesktopStateSnapshot();
255
452
  this.refreshLocalDashboardModel();
@@ -271,12 +468,11 @@ export const persistenceMethods = {
271
468
  void (async () => {
272
469
  try {
273
470
  const mainRepo = findMainRepo();
274
- const branchExists = branchExistsInRepo(mainRepo, name);
275
471
  await new Promise((resolve, reject) => {
276
472
  let stderr = "";
277
473
  let child;
278
474
  try {
279
- child = spawn("git", branchExists ? ["worktree", "add", targetPath, name] : ["worktree", "add", targetPath, "-b", name], {
475
+ child = spawn("git", getWorktreeAddArgs(name, targetPath, mainRepo), {
280
476
  cwd: mainRepo,
281
477
  stdio: ["ignore", "ignore", "pipe"],
282
478
  });
@@ -310,11 +506,20 @@ export const persistenceMethods = {
310
506
  })();
311
507
  void createPromise
312
508
  .then(() => {
509
+ clearDashboardOperationFailures({ targetKind: "worktree", operation: "create", worktreePath: targetPath });
313
510
  this.footerFlash = `Created: ${name}`;
314
511
  this.footerFlashTicks = 3;
315
512
  })
316
513
  .catch((error) => {
317
514
  const message = error instanceof Error ? error.message : String(error);
515
+ recordDashboardFailure(this, {
516
+ targetKind: "worktree",
517
+ operation: "create",
518
+ title: `Failed to create worktree "${name}"`,
519
+ message,
520
+ worktreePath: targetPath,
521
+ worktreeName: name,
522
+ });
318
523
  this.footerFlash = `Failed: ${message}`;
319
524
  this.footerFlashTicks = 5;
320
525
  if (this.mode === "dashboard") {
@@ -322,9 +527,8 @@ export const persistenceMethods = {
322
527
  }
323
528
  });
324
529
  const finalizeCreate = () => {
325
- pendingCreates.delete(targetPath);
530
+ clearPendingCreate();
326
531
  this.dashboardPendingActions.set(DashboardPendingActions.worktreeKey(targetPath), null);
327
- this.worktreeCreateJob = null;
328
532
  this.invalidateDesktopStateSnapshot();
329
533
  this.refreshLocalDashboardModel();
330
534
  this.metadataServer?.notifyChange?.();
@@ -351,7 +555,17 @@ export const persistenceMethods = {
351
555
  this.dashboardPendingActions.set(DashboardPendingActions.worktreeKey(path), "removing", {
352
556
  timeoutMs: 180_000,
353
557
  onTimeout: () => {
354
- this.footerFlash = `Timed out removing ${path.split("/").pop() ?? path}`;
558
+ const name = path.split("/").pop() ?? path;
559
+ const message = `Timed out removing worktree "${name}"`;
560
+ recordDashboardFailure(this, {
561
+ targetKind: "worktree",
562
+ operation: "remove",
563
+ title: `Failed to remove worktree "${name}"`,
564
+ message,
565
+ worktreePath: path,
566
+ worktreeName: name,
567
+ });
568
+ this.footerFlash = message;
355
569
  this.footerFlashTicks = 5;
356
570
  this.invalidateDesktopStateSnapshot();
357
571
  this.refreshLocalDashboardModel();
@@ -374,6 +588,7 @@ export const persistenceMethods = {
374
588
  }
375
589
  if (!existsSync(path)) {
376
590
  await removeOrphanedDesktopWorktree(this, mainRepo, path);
591
+ clearDashboardOperationFailures({ targetKind: "worktree", operation: "remove", worktreePath: path });
377
592
  resolveRemoval({ path, status: "removed" });
378
593
  return;
379
594
  }
@@ -382,6 +597,7 @@ export const persistenceMethods = {
382
597
  const worktreeBaseDir = getWorktreeBaseDir();
383
598
  if (path.startsWith(`${worktreeBaseDir}/`) || path === worktreeBaseDir) {
384
599
  await removeOrphanedDesktopWorktree(this, mainRepo, path);
600
+ clearDashboardOperationFailures({ targetKind: "worktree", operation: "remove", worktreePath: path });
385
601
  resolveRemoval({ path, status: "removed" });
386
602
  return;
387
603
  }
@@ -391,10 +607,7 @@ export const persistenceMethods = {
391
607
  if (attachedSession) {
392
608
  throw new Error(`Cannot remove "${matching.name}" while agent "${attachedSession.label || attachedSession.id}" is attached`);
393
609
  }
394
- const attachedService = this.buildLiveServiceStates().find((service) => service.worktreePath === path);
395
- if (attachedService) {
396
- throw new Error(`Cannot remove "${matching.name}" while service "${attachedService.label || attachedService.id}" is attached`);
397
- }
610
+ detachWorktreeServices(this, path);
398
611
  await new Promise((resolve, reject) => {
399
612
  let stderr = "";
400
613
  let child;
@@ -434,13 +647,29 @@ export const persistenceMethods = {
434
647
  });
435
648
  this.offlineSessions = this.offlineSessions.filter((session) => session.worktreePath !== path);
436
649
  this.offlineServices = this.offlineServices.filter((service) => service.worktreePath !== path);
650
+ removeFlatGraveyardAgentsForWorktree(path);
437
651
  this.saveState();
652
+ clearDashboardOperationFailures({ targetKind: "worktree", operation: "remove", worktreePath: path });
438
653
  resolveRemoval({ path, status: "removed" });
439
654
  }
440
655
  catch (error) {
441
656
  rejectRemoval(error);
442
657
  }
443
658
  })();
659
+ void removalPromise.catch((error) => {
660
+ const message = error instanceof Error ? error.message : String(error);
661
+ const name = path.split("/").pop() ?? path;
662
+ recordDashboardFailure(this, {
663
+ targetKind: "worktree",
664
+ operation: "remove",
665
+ title: `Failed to remove worktree "${name}"`,
666
+ message,
667
+ worktreePath: path,
668
+ worktreeName: name,
669
+ });
670
+ this.footerFlash = `Failed: ${message}`;
671
+ this.footerFlashTicks = 5;
672
+ });
444
673
  const finalizeRemoval = () => {
445
674
  this.dashboardPendingActions.set(DashboardPendingActions.worktreeKey(path), null);
446
675
  pendingRemovals.delete(path);
@@ -516,18 +745,6 @@ async function pruneGitWorktrees(mainRepo) {
516
745
  }
517
746
  catch { }
518
747
  }
519
- function branchExistsInRepo(mainRepo, branch) {
520
- try {
521
- execFileSync("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
522
- cwd: mainRepo,
523
- stdio: "ignore",
524
- });
525
- return true;
526
- }
527
- catch {
528
- return false;
529
- }
530
- }
531
748
  function sortDesktopWorktrees(worktrees) {
532
749
  let mainRepo;
533
750
  try {
@@ -547,6 +764,47 @@ function sortDesktopWorktrees(worktrees) {
547
764
  return a.name.localeCompare(b.name);
548
765
  });
549
766
  }
767
+ function collectWorktreeAgents(host, path) {
768
+ const byId = new Map();
769
+ for (const session of host.offlineSessions) {
770
+ if (session.worktreePath !== path)
771
+ continue;
772
+ byId.set(session.id, session);
773
+ }
774
+ return [...byId.values()].sort((a, b) => {
775
+ const aTime = a.createdAt ? Date.parse(a.createdAt) : 0;
776
+ const bTime = b.createdAt ? Date.parse(b.createdAt) : 0;
777
+ return bTime - aTime || a.id.localeCompare(b.id);
778
+ });
779
+ }
780
+ function collectWorktreeServices(host, path) {
781
+ const byId = new Map();
782
+ for (const service of host.offlineServices ?? []) {
783
+ if (service.worktreePath !== path)
784
+ continue;
785
+ byId.set(service.id, service);
786
+ }
787
+ for (const service of host.buildLiveServiceStates?.() ?? []) {
788
+ if (service.worktreePath !== path)
789
+ continue;
790
+ byId.set(service.id, service);
791
+ }
792
+ return [...byId.values()].sort((a, b) => {
793
+ const aTime = a.createdAt ? Date.parse(a.createdAt) : 0;
794
+ const bTime = b.createdAt ? Date.parse(b.createdAt) : 0;
795
+ return bTime - aTime || a.id.localeCompare(b.id);
796
+ });
797
+ }
798
+ async function waitForWorktreeSessionsToStop(host, path, timeoutMs = 10_000) {
799
+ const deadline = Date.now() + timeoutMs;
800
+ while (Date.now() < deadline) {
801
+ const remaining = host.sessions.some((session) => host.sessionWorktreePaths.get(session.id) === path && host.isSessionRuntimeLive(session) && !session.exited);
802
+ if (!remaining)
803
+ return;
804
+ await new Promise((resolve) => setTimeout(resolve, 100));
805
+ }
806
+ throw new Error(`Timed out offlining agents for worktree "${basename(path)}"`);
807
+ }
550
808
  async function removeOrphanedDesktopWorktree(host, mainRepo, path) {
551
809
  await pruneGitWorktrees(mainRepo);
552
810
  if (existsSync(path)) {
@@ -557,4 +815,67 @@ async function removeOrphanedDesktopWorktree(host, mainRepo, path) {
557
815
  host.offlineServices = host.offlineServices.filter((service) => service.worktreePath !== path);
558
816
  host.saveState();
559
817
  }
818
+ function detachWorktreeServices(host, path) {
819
+ for (const { target, metadata } of host.tmuxRuntimeManager.listProjectManagedWindows(process.cwd())) {
820
+ if (metadata.kind !== "service" || metadata.worktreePath !== path)
821
+ continue;
822
+ markLifecycleUsed(host, metadata.sessionId);
823
+ try {
824
+ host.tmuxRuntimeManager.killWindow(target);
825
+ }
826
+ catch { }
827
+ }
828
+ host.offlineServices = host.offlineServices.filter((service) => service.worktreePath !== path);
829
+ removePersistedServicesForWorktree(path);
830
+ }
831
+ function removePersistedServicesForWorktree(path) {
832
+ const statePath = getStatePath();
833
+ if (!existsSync(statePath))
834
+ return;
835
+ try {
836
+ const state = JSON.parse(readFileSync(statePath, "utf-8"));
837
+ const nextServices = (state.services ?? []).filter((service) => service.worktreePath !== path);
838
+ if (nextServices.length === (state.services ?? []).length)
839
+ return;
840
+ writeFileSync(statePath, JSON.stringify({ ...state, services: nextServices }, null, 2) + "\n");
841
+ }
842
+ catch { }
843
+ }
844
+ function markLifecycleUsed(host, itemId) {
845
+ try {
846
+ if (typeof host.noteLastUsedItem === "function") {
847
+ host.noteLastUsedItem(itemId);
848
+ return;
849
+ }
850
+ if (host.mode === "dashboard" || host.mode === "project-service") {
851
+ markLastUsed(process.cwd(), {
852
+ itemId,
853
+ clientSession: host.tmuxRuntimeManager?.currentClientSession?.() ?? undefined,
854
+ });
855
+ }
856
+ }
857
+ catch { }
858
+ }
859
+ function takeFlatGraveyardAgentsForWorktree(path) {
860
+ const graveyardPath = getGraveyardPath();
861
+ if (!existsSync(graveyardPath))
862
+ return [];
863
+ try {
864
+ const entries = JSON.parse(readFileSync(graveyardPath, "utf-8"));
865
+ if (!Array.isArray(entries))
866
+ return [];
867
+ const matching = entries.filter((entry) => entry?.worktreePath === path);
868
+ const remaining = entries.filter((entry) => entry?.worktreePath !== path);
869
+ if (matching.length > 0) {
870
+ writeFileSync(graveyardPath, JSON.stringify(remaining, null, 2) + "\n");
871
+ }
872
+ return matching;
873
+ }
874
+ catch {
875
+ return [];
876
+ }
877
+ }
878
+ function removeFlatGraveyardAgentsForWorktree(path) {
879
+ void takeFlatGraveyardAgentsForWorktree(path);
880
+ }
560
881
  //# sourceMappingURL=persistence-methods.js.map