aimux-cli 0.1.13 → 0.1.15
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.
- package/README.md +54 -0
- package/dist/agent-prompt-delivery.d.ts +31 -0
- package/dist/agent-prompt-delivery.js +119 -0
- package/dist/agent-prompt-delivery.js.map +1 -0
- package/dist/agent-tracker.js +4 -2
- package/dist/agent-tracker.js.map +1 -1
- package/dist/atomic-write.d.ts +1 -0
- package/dist/atomic-write.js +9 -0
- package/dist/atomic-write.js.map +1 -0
- package/dist/builtin-metadata-watchers.js +10 -6
- package/dist/builtin-metadata-watchers.js.map +1 -1
- package/dist/daemon.js +3 -6
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/index.d.ts +26 -15
- package/dist/dashboard/index.js +26 -56
- package/dist/dashboard/index.js.map +1 -1
- package/dist/dashboard/operation-failures.d.ts +30 -0
- package/dist/dashboard/operation-failures.js +86 -0
- package/dist/dashboard/operation-failures.js.map +1 -0
- package/dist/dashboard/pending-actions.d.ts +9 -2
- package/dist/dashboard/pending-actions.js +66 -7
- package/dist/dashboard/pending-actions.js.map +1 -1
- package/dist/dashboard/quick-jump.d.ts +3 -1
- package/dist/dashboard/quick-jump.js +27 -8
- package/dist/dashboard/quick-jump.js.map +1 -1
- package/dist/dashboard/runtime-evidence.d.ts +4 -0
- package/dist/dashboard/runtime-evidence.js +22 -0
- package/dist/dashboard/runtime-evidence.js.map +1 -0
- package/dist/dashboard/session-actions.d.ts +1 -1
- package/dist/dashboard/session-actions.js +3 -1
- package/dist/dashboard/session-actions.js.map +1 -1
- package/dist/dashboard/session-registry.d.ts +1 -0
- package/dist/dashboard/session-registry.js +47 -30
- package/dist/dashboard/session-registry.js.map +1 -1
- package/dist/dashboard/state.d.ts +9 -0
- package/dist/dashboard/state.js +33 -1
- package/dist/dashboard/state.js.map +1 -1
- package/dist/dashboard/targets.js +11 -3
- package/dist/dashboard/targets.js.map +1 -1
- package/dist/dashboard/ui-state-store.d.ts +3 -3
- package/dist/dashboard/ui-state-store.js +23 -14
- package/dist/dashboard/ui-state-store.js.map +1 -1
- package/dist/default-plugins/gh-pr-context.d.ts +8 -0
- package/dist/default-plugins/gh-pr-context.js +256 -0
- package/dist/default-plugins/gh-pr-context.js.map +1 -0
- package/dist/default-plugins/transcript-length.d.ts +1 -0
- package/dist/default-plugins/transcript-length.js +10 -0
- package/dist/default-plugins/transcript-length.js.map +1 -1
- package/dist/fast-control.d.ts +8 -3
- package/dist/fast-control.js +13 -14
- package/dist/fast-control.js.map +1 -1
- package/dist/main.js +22 -35
- package/dist/main.js.map +1 -1
- package/dist/managed-launch-env.d.ts +10 -0
- package/dist/managed-launch-env.js +51 -0
- package/dist/managed-launch-env.js.map +1 -0
- package/dist/metadata-server.d.ts +41 -0
- package/dist/metadata-server.js +272 -15
- package/dist/metadata-server.js.map +1 -1
- package/dist/metadata-store.d.ts +1 -0
- package/dist/metadata-store.js +3 -5
- package/dist/metadata-store.js.map +1 -1
- package/dist/multiplexer/agent-io-methods.d.ts +1 -1
- package/dist/multiplexer/agent-io-methods.js +36 -29
- package/dist/multiplexer/agent-io-methods.js.map +1 -1
- package/dist/multiplexer/archives.d.ts +1 -0
- package/dist/multiplexer/archives.js +143 -25
- package/dist/multiplexer/archives.js.map +1 -1
- package/dist/multiplexer/dashboard-actions-methods.d.ts +10 -1
- package/dist/multiplexer/dashboard-actions-methods.js +16 -4
- package/dist/multiplexer/dashboard-actions-methods.js.map +1 -1
- package/dist/multiplexer/dashboard-control.d.ts +13 -1
- package/dist/multiplexer/dashboard-control.js +150 -121
- package/dist/multiplexer/dashboard-control.js.map +1 -1
- package/dist/multiplexer/dashboard-interaction.d.ts +2 -1
- package/dist/multiplexer/dashboard-interaction.js +175 -113
- package/dist/multiplexer/dashboard-interaction.js.map +1 -1
- package/dist/multiplexer/dashboard-model.d.ts +5 -1
- package/dist/multiplexer/dashboard-model.js +83 -8
- package/dist/multiplexer/dashboard-model.js.map +1 -1
- package/dist/multiplexer/dashboard-ops.d.ts +32 -1
- package/dist/multiplexer/dashboard-ops.js +517 -15
- package/dist/multiplexer/dashboard-ops.js.map +1 -1
- package/dist/multiplexer/dashboard-state-methods.d.ts +7 -0
- package/dist/multiplexer/dashboard-state-methods.js +79 -4
- package/dist/multiplexer/dashboard-state-methods.js.map +1 -1
- package/dist/multiplexer/dashboard-tail-methods.d.ts +12 -2
- package/dist/multiplexer/dashboard-tail-methods.js +18 -6
- package/dist/multiplexer/dashboard-tail-methods.js.map +1 -1
- package/dist/multiplexer/dashboard-view-methods.d.ts +3 -1
- package/dist/multiplexer/dashboard-view-methods.js +53 -38
- package/dist/multiplexer/dashboard-view-methods.js.map +1 -1
- package/dist/multiplexer/graveyard-view-model.d.ts +68 -0
- package/dist/multiplexer/graveyard-view-model.js +185 -0
- package/dist/multiplexer/graveyard-view-model.js.map +1 -0
- package/dist/multiplexer/index.d.ts +18 -11
- package/dist/multiplexer/index.js +30 -18
- package/dist/multiplexer/index.js.map +1 -1
- package/dist/multiplexer/navigation.d.ts +1 -1
- package/dist/multiplexer/navigation.js +31 -11
- package/dist/multiplexer/navigation.js.map +1 -1
- package/dist/multiplexer/notifications.d.ts +1 -0
- package/dist/multiplexer/notifications.js +27 -19
- package/dist/multiplexer/notifications.js.map +1 -1
- package/dist/multiplexer/persistence-methods.d.ts +16 -0
- package/dist/multiplexer/persistence-methods.js +349 -28
- package/dist/multiplexer/persistence-methods.js.map +1 -1
- package/dist/multiplexer/runtime-lifecycle-methods.js +81 -12
- package/dist/multiplexer/runtime-lifecycle-methods.js.map +1 -1
- package/dist/multiplexer/runtime-state.d.ts +6 -2
- package/dist/multiplexer/runtime-state.js +144 -23
- package/dist/multiplexer/runtime-state.js.map +1 -1
- package/dist/multiplexer/service-state-snapshot.d.ts +14 -0
- package/dist/multiplexer/service-state-snapshot.js +121 -0
- package/dist/multiplexer/service-state-snapshot.js.map +1 -0
- package/dist/multiplexer/services.d.ts +19 -1
- package/dist/multiplexer/services.js +114 -16
- package/dist/multiplexer/services.js.map +1 -1
- package/dist/multiplexer/session-actions.d.ts +3 -0
- package/dist/multiplexer/session-actions.js +10 -8
- package/dist/multiplexer/session-actions.js.map +1 -1
- package/dist/multiplexer/session-launch.d.ts +1 -1
- package/dist/multiplexer/session-launch.js +63 -22
- package/dist/multiplexer/session-launch.js.map +1 -1
- package/dist/multiplexer/session-runtime-core.d.ts +2 -1
- package/dist/multiplexer/session-runtime-core.js +107 -69
- package/dist/multiplexer/session-runtime-core.js.map +1 -1
- package/dist/multiplexer/subscreens.d.ts +1 -0
- package/dist/multiplexer/subscreens.js +17 -12
- package/dist/multiplexer/subscreens.js.map +1 -1
- package/dist/multiplexer/tool-picker.d.ts +6 -2
- package/dist/multiplexer/tool-picker.js +231 -81
- package/dist/multiplexer/tool-picker.js.map +1 -1
- package/dist/multiplexer/worktree-graveyard.d.ts +13 -0
- package/dist/multiplexer/worktree-graveyard.js +21 -0
- package/dist/multiplexer/worktree-graveyard.js.map +1 -0
- package/dist/multiplexer/worktrees.d.ts +1 -0
- package/dist/multiplexer/worktrees.js +151 -69
- package/dist/multiplexer/worktrees.js.map +1 -1
- package/dist/notification-context.js +9 -15
- package/dist/notification-context.js.map +1 -1
- package/dist/notifications.d.ts +22 -0
- package/dist/notifications.js +66 -11
- package/dist/notifications.js.map +1 -1
- package/dist/orchestration-actions.js +3 -3
- package/dist/orchestration-actions.js.map +1 -1
- package/dist/orchestration-dispatcher.d.ts +4 -3
- package/dist/orchestration-dispatcher.js +9 -7
- package/dist/orchestration-dispatcher.js.map +1 -1
- package/dist/orchestration-routing.d.ts +2 -2
- package/dist/orchestration-routing.js +3 -7
- package/dist/orchestration-routing.js.map +1 -1
- package/dist/paths.d.ts +3 -0
- package/dist/paths.js +9 -0
- package/dist/paths.js.map +1 -1
- package/dist/plugin-runtime.d.ts +5 -0
- package/dist/plugin-runtime.js +98 -34
- package/dist/plugin-runtime.js.map +1 -1
- package/dist/project-events.js +4 -2
- package/dist/project-events.js.map +1 -1
- package/dist/session-bootstrap.d.ts +0 -3
- package/dist/session-bootstrap.js +12 -104
- package/dist/session-bootstrap.js.map +1 -1
- package/dist/session-semantics.d.ts +44 -7
- package/dist/session-semantics.js +164 -69
- package/dist/session-semantics.js.map +1 -1
- package/dist/shell-args.d.ts +1 -0
- package/dist/shell-args.js +57 -0
- package/dist/shell-args.js.map +1 -0
- package/dist/shell-hooks.d.ts +3 -0
- package/dist/shell-hooks.js +19 -2
- package/dist/shell-hooks.js.map +1 -1
- package/dist/statusline-model.d.ts +5 -1
- package/dist/statusline-model.js +18 -29
- package/dist/statusline-model.js.map +1 -1
- package/dist/task-dispatcher.d.ts +3 -3
- package/dist/task-dispatcher.js +6 -5
- package/dist/task-dispatcher.js.map +1 -1
- package/dist/task-workflow.d.ts +2 -0
- package/dist/task-workflow.js +15 -6
- package/dist/task-workflow.js.map +1 -1
- package/dist/tasks.d.ts +4 -1
- package/dist/tasks.js +19 -1
- package/dist/tasks.js.map +1 -1
- package/dist/tmux/doctor.d.ts +1 -0
- package/dist/tmux/doctor.js +4 -1
- package/dist/tmux/doctor.js.map +1 -1
- package/dist/tmux/runtime-manager.d.ts +6 -0
- package/dist/tmux/runtime-manager.js +56 -18
- package/dist/tmux/runtime-manager.js.map +1 -1
- package/dist/tmux/statusline.js +17 -16
- package/dist/tmux/statusline.js.map +1 -1
- package/dist/tmux/window-open.d.ts +1 -0
- package/dist/tmux/window-open.js +10 -10
- package/dist/tmux/window-open.js.map +1 -1
- package/dist/tool-output-watchers.d.ts +2 -0
- package/dist/tool-output-watchers.js +123 -12
- package/dist/tool-output-watchers.js.map +1 -1
- package/dist/tui/screens/dashboard-renderers.d.ts +2 -21
- package/dist/tui/screens/dashboard-renderers.js +126 -32
- package/dist/tui/screens/dashboard-renderers.js.map +1 -1
- package/dist/tui/screens/overlay-renderers.d.ts +11 -1
- package/dist/tui/screens/overlay-renderers.js +68 -28
- package/dist/tui/screens/overlay-renderers.js.map +1 -1
- package/dist/tui/screens/subscreen-renderers.js +156 -33
- package/dist/tui/screens/subscreen-renderers.js.map +1 -1
- package/dist/workflow.js +2 -2
- package/dist/workflow.js.map +1 -1
- package/dist/worktree.d.ts +3 -0
- package/dist/worktree.js +27 -2
- package/dist/worktree.js.map +1 -1
- package/package.json +5 -1
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|