@martintrojer/mu 0.4.1 → 0.4.3
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/dist/cli.js +318 -67
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +72 -48
- package/dist/index.js +58 -14
- package/dist/index.js.map +1 -1
- package/docs/USAGE_GUIDE.md +38 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -471,23 +471,11 @@ declare namespace tmux$1 {
|
|
|
471
471
|
/**
|
|
472
472
|
* What kind of reconciliation pass to run.
|
|
473
473
|
*
|
|
474
|
-
* "full" Default for `mu agent list`. Prunes
|
|
475
|
-
* the registry row, which fires the
|
|
476
|
-
* that flips IN_PROGRESS tasks back
|
|
477
|
-
* [reaper] notes), runs status detection
|
|
478
|
-
* surviving panes, surfaces orphans.
|
|
479
|
-
*
|
|
480
|
-
* "status-only" The "freshen the operator's view" mode. Runs status
|
|
481
|
-
* detection (DB writes that update agent status +
|
|
482
|
-
* pane title — desired side-effects of a refresh) and
|
|
483
|
-
* orphan surface. Does NOT prune (so a dead pane's
|
|
484
|
-
* row stays visible until a real `mu agent list`) and
|
|
485
|
-
* does NOT reap. Used by `mu state` and
|
|
486
|
-
* `mu agent attach` — the verbs an operator polls to
|
|
487
|
-
* answer "is worker-X busy or idle right
|
|
488
|
-
* now?". Status detection skips placeholder agents
|
|
489
|
-
* whose pane id starts with `%pending-` (mid-spawn,
|
|
490
|
-
* no usable scrollback yet).
|
|
474
|
+
* "full" Default for `mu state` and `mu agent list`. Prunes
|
|
475
|
+
* ghosts (deleting the registry row, which fires the
|
|
476
|
+
* deleteAgent reaper that flips IN_PROGRESS tasks back
|
|
477
|
+
* to OPEN with [reaper] notes), runs status detection
|
|
478
|
+
* against surviving panes, surfaces orphans.
|
|
491
479
|
*
|
|
492
480
|
* "report-only" Pure observation. Counts would-be-pruned ghosts
|
|
493
481
|
* without deleting; skips status detection entirely
|
|
@@ -498,13 +486,11 @@ declare namespace tmux$1 {
|
|
|
498
486
|
* snap_undo_reconcile_destroys_recovered_agents) and
|
|
499
487
|
* `mu doctor` (read-only diagnostic).
|
|
500
488
|
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
* indefinitely. Splitting prune-suppression from status-suppression
|
|
505
|
-
* is the fix.
|
|
489
|
+
* Mid-spawn placeholders are protected directly in the prune loop, so read
|
|
490
|
+
* paths no longer need a separate mode just to avoid racing spawn's workspace
|
|
491
|
+
* pre-stage.
|
|
506
492
|
*/
|
|
507
|
-
type ReconcileMode = "full" | "
|
|
493
|
+
type ReconcileMode = "full" | "report-only";
|
|
508
494
|
interface ReconcileOptions {
|
|
509
495
|
/** The workstream whose registry rows we're reconciling. */
|
|
510
496
|
workstream: string;
|
|
@@ -526,9 +512,9 @@ interface ReconcileOptions {
|
|
|
526
512
|
mode?: ReconcileMode;
|
|
527
513
|
}
|
|
528
514
|
interface ReconcileReport {
|
|
529
|
-
/** Number of registry rows whose pane was gone. In
|
|
530
|
-
*
|
|
531
|
-
*
|
|
515
|
+
/** Number of registry rows whose pane was gone. In `report-only` mode
|
|
516
|
+
* this is the count of rows that WOULD have been pruned; in `full`
|
|
517
|
+
* mode it's the count actually deleted. */
|
|
532
518
|
prunedGhosts: number;
|
|
533
519
|
/** Number of agents whose status was changed by scrollback detection.
|
|
534
520
|
* Always 0 in `report-only` mode (status detection is skipped). */
|
|
@@ -1287,6 +1273,9 @@ declare function updateAgentStatus(db: Db, name: string, status: AgentStatus, wo
|
|
|
1287
1273
|
* initial render before status detection runs, and 'spawning' is a
|
|
1288
1274
|
* transient state. */
|
|
1289
1275
|
declare const STATUS_EMOJI: Record<AgentStatus, string>;
|
|
1276
|
+
/** Single rendering helper for agent status glyphs. Keep callers off
|
|
1277
|
+
* STATUS_EMOJI indexing so glyph fallback policy stays centralised. */
|
|
1278
|
+
declare function agentStatusGlyph(status: AgentStatus): string;
|
|
1290
1279
|
/** Build the pane title for `agent` based on current DB state.
|
|
1291
1280
|
* Pure (no tmux side effect; no DB write). Read-only on the DB. */
|
|
1292
1281
|
declare function composeAgentTitle(db: Db, agent: AgentRow): string;
|
|
@@ -1411,22 +1400,16 @@ interface ListLiveAgentsOptions {
|
|
|
1411
1400
|
/**
|
|
1412
1401
|
* Which kind of reconciliation pass to run. Forwarded to
|
|
1413
1402
|
* `reconcile()`'s same-name option. Default `"full"` (the
|
|
1414
|
-
* documented mutating behaviour `mu agent list` has always had
|
|
1403
|
+
* documented mutating behaviour `mu agent list` has always had,
|
|
1404
|
+
* now also used by `mu state` and `mu agent attach`).
|
|
1415
1405
|
*
|
|
1416
|
-
*
|
|
1417
|
-
*
|
|
1418
|
-
*
|
|
1419
|
-
*
|
|
1420
|
-
* (busy/needs_input) stays fresh without a periodic poll
|
|
1421
|
-
* racing in-flight spawns.
|
|
1422
|
-
* - `mu doctor`, `mu undo` → `"report-only"`: count drift,
|
|
1423
|
-
* mutate nothing. `mu undo` MUST use this so a post-restore
|
|
1424
|
-
* reconcile doesn't delete the rows the snapshot just
|
|
1425
|
-
* restored (snap_undo_reconcile_destroys_recovered_agents).
|
|
1406
|
+
* `mu doctor` and `mu undo` pass `"report-only"`: count drift,
|
|
1407
|
+
* mutate nothing. `mu undo` MUST use this so a post-restore
|
|
1408
|
+
* reconcile doesn't delete the rows the snapshot just restored
|
|
1409
|
+
* (snap_undo_reconcile_destroys_recovered_agents).
|
|
1426
1410
|
*
|
|
1427
|
-
*
|
|
1428
|
-
*
|
|
1429
|
-
* out from under `createWorkspace`'s FK insert
|
|
1411
|
+
* Mid-spawn placeholders (pane id `%pending-<name>`) are protected
|
|
1412
|
+
* directly in reconcile's prune loop, independent of mode
|
|
1430
1413
|
* (bug_agent_spawn_workspace_fk_failure).
|
|
1431
1414
|
*
|
|
1432
1415
|
* BREAKING: this replaces the previous `dryRun?: boolean`
|
|
@@ -1445,11 +1428,10 @@ interface LiveAgentsView {
|
|
|
1445
1428
|
}
|
|
1446
1429
|
/**
|
|
1447
1430
|
* Return the live, reality-reconciled view of agents in a workstream.
|
|
1448
|
-
* `mu
|
|
1449
|
-
*
|
|
1450
|
-
* `mode: "
|
|
1451
|
-
*
|
|
1452
|
-
* `mode: "report-only"` to mutate nothing at all.
|
|
1431
|
+
* `mu state`, `mu agent list`, and `mu agent attach` call this with the
|
|
1432
|
+
* default `mode: "full"` (mutating); read-only diagnostic / restore paths
|
|
1433
|
+
* (`mu doctor`, `mu undo`) call it with `mode: "report-only"` to mutate
|
|
1434
|
+
* nothing at all.
|
|
1453
1435
|
*/
|
|
1454
1436
|
declare function listLiveAgents(db: Db, opts: ListLiveAgentsOptions): Promise<LiveAgentsView>;
|
|
1455
1437
|
|
|
@@ -3242,6 +3224,15 @@ interface WorkstreamSummary {
|
|
|
3242
3224
|
* otherwise such rows are orphaned forever (the previous
|
|
3243
3225
|
* `nothingToDo` heuristic short-circuited on them). */
|
|
3244
3226
|
registered: boolean;
|
|
3227
|
+
/** "Presumed parked on another machine" derived signal. Present
|
|
3228
|
+
* iff `parkedStatus(db, name)` reports `parked: true` (most recent
|
|
3229
|
+
* agent_logs row is a `db export` event, no alive agents, no
|
|
3230
|
+
* IN_PROGRESS tasks, threshold elapsed). Consumed by
|
|
3231
|
+
* `mu workstream list` and the TUI tab strip / workstreams card.
|
|
3232
|
+
* See src/parked.ts. */
|
|
3233
|
+
parked?: {
|
|
3234
|
+
sinceDays: number;
|
|
3235
|
+
};
|
|
3245
3236
|
}
|
|
3246
3237
|
interface DestroyResult {
|
|
3247
3238
|
/** True iff `tmux kill-session` actually killed something. */
|
|
@@ -3591,8 +3582,9 @@ declare function loadWorkstreamSnapshotFast(db: Db, workstream: string, opts?: L
|
|
|
3591
3582
|
* doctor, which reports over those slow-tier observations). Returns only the
|
|
3592
3583
|
* fields the fast snapshot deliberately leaves empty or undecorated.
|
|
3593
3584
|
*
|
|
3594
|
-
*
|
|
3595
|
-
*
|
|
3585
|
+
* The slow snapshot tier runs full reconciliation: missing panes are
|
|
3586
|
+
* reaped, while mid-spawn placeholders remain protected by the prune
|
|
3587
|
+
* loop's pending-pane guard.
|
|
3596
3588
|
*/
|
|
3597
3589
|
declare function loadWorkstreamSnapshotSlow(db: Db, workstream: string, opts?: LoadWorkstreamSnapshotOptions, baseSnapshot?: WorkstreamSnapshot): Promise<WorkstreamSnapshotSlowFields>;
|
|
3598
3590
|
/** Merge the latest slow-tier subprocess observations into a fresh fast tier. */
|
|
@@ -3727,4 +3719,36 @@ declare function deleteSnapshot(db: Db, snapshotId: number): DeleteSnapshotResul
|
|
|
3727
3719
|
|
|
3728
3720
|
declare function restoreSnapshot(db: Db, snapshotId: number): RestoreSnapshotResult;
|
|
3729
3721
|
|
|
3730
|
-
|
|
3722
|
+
/** Days that must have elapsed since the most recent `db export`
|
|
3723
|
+
* event before a workstream is considered parked. Default 1: prevents
|
|
3724
|
+
* a same-session "I exported to verify" from instantly flipping the
|
|
3725
|
+
* TUI tab to dim. Tuning higher would just delay the banner. */
|
|
3726
|
+
declare const WORKSTREAM_PARKED_THRESHOLD_DAYS = 1;
|
|
3727
|
+
interface ParkedStatus {
|
|
3728
|
+
parked: boolean;
|
|
3729
|
+
/** Whole days since the most recent `db export` event. Present iff
|
|
3730
|
+
* `parked === true`. */
|
|
3731
|
+
sinceDays?: number;
|
|
3732
|
+
}
|
|
3733
|
+
/**
|
|
3734
|
+
* Compute the parked status for one workstream. Pure read; no writes.
|
|
3735
|
+
*
|
|
3736
|
+
* Returns `{ parked: false }` when:
|
|
3737
|
+
* - the workstream has no `db export` event in agent_logs, OR
|
|
3738
|
+
* - any agent_logs row newer than the most recent `db export` exists
|
|
3739
|
+
* (i.e. local activity since export), OR
|
|
3740
|
+
* - the workstream has any alive agents (status != 'closed'), OR
|
|
3741
|
+
* - the workstream has any IN_PROGRESS tasks, OR
|
|
3742
|
+
* - the most recent `db export` is younger than the threshold.
|
|
3743
|
+
*
|
|
3744
|
+
* Otherwise returns `{ parked: true, sinceDays: <whole days> }`.
|
|
3745
|
+
*
|
|
3746
|
+
* `now` defaults to wall-clock; tests pass it explicitly to keep the
|
|
3747
|
+
* threshold edge deterministic.
|
|
3748
|
+
*/
|
|
3749
|
+
declare function parkedStatus(db: Db, workstream: string, opts?: {
|
|
3750
|
+
now?: Date;
|
|
3751
|
+
thresholdDays?: number;
|
|
3752
|
+
}): ParkedStatus;
|
|
3753
|
+
|
|
3754
|
+
export { type AddNoteOptions, type AddTaskOptions, type AddToArchiveResult, type AdoptAgentOptions, type AdoptAgentResult, AgentDiedOnSpawnError, AgentExistsError, AgentNotFoundError, AgentNotInWorkstreamError, type AgentRow, AgentSpawnCliNotFoundError, AgentSpawnStartupError, type AgentStatus, type AppendLogOptions, type Archive, ArchiveAlreadyExistsError, ArchiveLabelInvalidError, ArchiveNotFoundError, type ArchiveSearchHit, ArchiveSourceAmbiguousError, type ArchiveSourceSummary, type ArchiveSummary, type ArchivedTaskRow, type BlockEdgeResult, CURRENT_SCHEMA_VERSION, type CaptureOptions, type CaptureSnapshotResult, type ClaimResult, type ClaimTaskOptions, ClaimerNotRegisteredError, type ClassifiedEvent, type CloseAgentOptions, type CloseAgentResult, type CommandResolutionResult, type CommandResolver, CrossWorkstreamEdgeError, CycleError, type Db, type DbExportManifest, type DbExportManifestWorkstream, DbExportTargetExistsError, DbImportConflictError, type DbImportDecision, DbImportManifestMissingError, DbImportSchemaTooNewError, DbImportSchemaTooOldError, DbImportSourceStaleError, type DbImportSummaryItem, type DbReplayEdgeItem, DbReplayLocalIdConflictError, type DbReplayNoteItem, type DbReplayPlan, type DbReplayResult, type DbReplayTaskConflict, type DbReplayTaskItem, DbReplayWorkstreamMissingError, type DeleteSnapshotResult, type DeleteTaskResult, type DestroyResult, type DetectedStatus, type DoctorCheck, type DoctorStatus, type DoctorSummary, EVENT_VERB_PREFIXES, EXPECTED_TABLES, type EvidenceOption, type ExportArchiveOptions, type ExportArchiveResult, type ExportDbOptions, type ExportDbResult, type ExportManifest, type ExportResult, type ExportSource, type ExportSourceManifest, type ExportTaskEntry, type ExportWorkstreamOptions, type FreeAgentResult, type FullDag, HomeDirAsProjectRootError, type IdFromTitleResult, type ImportDbOptions, type ImportDbResult, type InsertAgentInput, type KickAgentOptions, type KickAgentResult, type KickProcessExecutor, type KickSignal, type ListArchivedTasksOptions, type ListLiveAgentsOptions, type ListLogsOptions, type ListNotesOptions, type ListReadyOptions, type ListSnapshotsOptions, type ListTasksOptions, type LiveAgentsView, type LoadFullDagOptions, type LoadWorkstreamSnapshotOptions, type LogKind, type LogRow, type NewSessionOptions, type NewWindowOptions, NoForegroundProcessError, type OpenDbOptions, type OwnedTasksSummary, PANE_ID_RE, PaneNotFoundError, type ParkedStatus, type PruneMode, type PruneOptions, PruneOptionsInvalidError, type PruneResult, type ReconcileMode, type ReconcileOptions, type ReconcileReport, type RejectDeferOptions, type RejectDeferResult, type ReleaseResult, type ReleaseTaskOptions, type RemoveBlockEdgeResult, type RemoveFromArchiveResult, type RenderBucketInput, type RenderBucketResult, type ReparentTaskResult, type ReplayDbOptions, type RestoreArchiveOptions, type RestoreArchiveResult, type RestoreSnapshotResult, type RoiBucket, STATUSES_TERMINAL_OR_PARKED, STATUS_EMOJI, SchemaTooOldError, type SearchArchivesOptions, type SearchTasksOptions, type SendOptions, type SetStatusResult, type SlugifyResult, SnapshotFileMissingError, SnapshotNotFoundError, type SnapshotRow, SnapshotVersionMismatchError, type SpawnAgentOptions, type SplitWindowOptions, type StrandedWorkspaceOrphan, TASK_STATUSES, TASK_STATUS_LIST, TaskAlreadyOwnedError, type TaskEdgeWithStatus, type TaskEdges, type TaskEdgesWithStatus, TaskExistsError, TaskHasOpenDependentsError, TaskNotFoundError, TaskNotInWorkstreamError, type TaskNoteRow, type TaskRow, type TaskStatus, type TaskWaitOptions, type TaskWaitRef, type TaskWaitResult, type TaskWaitTaskState, TmuxError, type TmuxExecResult, type TmuxExecutor, type TmuxPane, type TmuxSession, type TmuxWindow, type Track, type UpdateTaskOptions, type UpdateTaskResult, type VcsBackend, type VcsBackendName, type CreateWorkspaceOptions$1 as VcsCreateWorkspaceOptions, type CreateWorkspaceResult as VcsCreateWorkspaceResult, type FreeWorkspaceOptions$1 as VcsFreeWorkspaceOptions, type FreeWorkspaceResult$1 as VcsFreeWorkspaceResult, WORKSPACE_STALE_THRESHOLD, WORKSTREAM_PARKED_THRESHOLD_DAYS, type CreateWorkspaceOptions as WorkspaceCreateOptions, WorkspaceExistsError, type WorkspaceFailure, type FreeWorkspaceOptions as WorkspaceFreeOptions, type FreeWorkspaceResult as WorkspaceFreeResult, WorkspaceNotFoundError, type WorkspaceOrphan, WorkspacePathNotEmptyError, WorkspacePreservedError, type RecreateWorkspaceOptions as WorkspaceRecreateOptions, type RecreateWorkspaceResult as WorkspaceRecreateResult, type WorkspaceRow, type WorkspaceStaleness, WorkstreamExistsError, WorkstreamNameInvalidError, type WorkstreamOptions, type WorkstreamSnapshot, type WorkstreamSnapshotSlowFields, type WorkstreamSummary, addBlockEdge, addNote, addTask, addToArchive, adoptAgent, agentStatusGlyph, agentStatusHistogram, appendLog, assertValidPaneId, backendByName, buildImportPlan, buildReplayPlan, capturePane, captureSnapshot, checkCommandResolvable, claimTask, classifyEventVerb, closeAgent, closeTask, composeAgentTitle, countProblems, createArchive, createWorkspace, currentAgentName, currentPaneTitle, decorateWithDirty, decorateWithStaleness, defaultDbPath, defaultSendDelayMs, defaultSpawnLivenessMs, defaultStateDir, deferTask, deleteAgent, deleteArchive, deleteSnapshot, deleteTask, destroyWorkstream, detectBackend, detectPiStatus, emitEvent, ensureWorkstream, envVarNameForCli, exportArchive, exportDb, exportSourceForWorkstream, exportSourcesForArchive, exportWorkstream, extractTail, foregroundPgid, freeAgent, freeWorkspace, gcMaxAgeDays, gcMaxCount, gcSnapshots, getAgent, getAgentByPane, getArchive, getParallelTracks, getPrerequisites, getTask, getTaskEdges, getTaskEdgesWithStatus, getWaitPollCount, getWorkspaceForAgent, getWorkspaceStaleness, gitBackend, idFromTitle, idFromTitleVerbose, importDb, insertAgent, isKickSignal, isStaleVersion, isTaskStatus, isValidAgentName, isValidArchiveLabel, isValidPaneId, isValidTaskId, isValidWorkstreamName, isWorkspaceStale, jjBackend, kickAgent, killPane, killSession, latestSeq, listAgents, listAllOrphanWorkspaces, listArchivedTasks, listArchives, listBlocked, listGoals, listInProgress, listLiveAgents, listLogs, listNotes, listPanes, listPanesInSession, listReady, listRecentClosed, listSessions, listSnapshots, listTasks, listTasksByOwner, listWindows, listWorkspaceOrphans, listWorkspaces, listWorkstreams, loadDoctorChecks, loadDoctorSummary, loadFullDag, loadWorkstreamSnapshot, loadWorkstreamSnapshotFast, loadWorkstreamSnapshotSlow, mergeSnapshotFastSlow, newSession, newSessionWithPane, newWindow, noneBackend, openDb, openTask, paneExists, paneTTY, parkedStatus, parseAgentNameFromTitle, parsePsTtyOutput, pruneSnapshots, readAgent, reconcile, recreateWorkspace, refreshAgentTitle, rejectTask, releaseTask, remediationParagraph, removeBlockEdge, removeFromArchive, renderForest, renderTaskTree, renderToBucket, reparentTask, replayDb, resetCommandResolverForTests, resetKickProcessExecutor, resetSleep, resetTmuxExecutor, resetWaitPollCount, resolveActorIdentity, resolveCliCommand, resolveCliCommandWithSource, restoreArchive, restoreSnapshot, roiBucket, searchArchives, searchTasks, selectLayout, sendToAgent, sendToPane, sessionExists, setCommandResolverForTests, setKickProcessExecutor, setPaneTitle, setSleepForTests, setTaskStatus, setTmuxExecutor, setWaitSleepForTests, setWaitStuckWarnForTests, slBackend, sleep, slugifyTitle, slugifyTitleVerbose, snapshotFileSize, snapshotsDir, spawnAgent, splitWindow, summarizeOwnedTasks, summarizeWorkstream, tmux$1 as tmux, updateAgentStatus, updateTask, waitForTasks, workspacePath, workspacesRoot, yankCommandForCheck };
|
package/dist/index.js
CHANGED
|
@@ -603,7 +603,12 @@ var EVENT_VERB_PREFIXES = [
|
|
|
603
603
|
// src/exporting.ts — archive export emits the bucket-render summary
|
|
604
604
|
// as a machine-wide event (workstream=null; the export spans every
|
|
605
605
|
// source-ws in the archive).
|
|
606
|
-
"archive export"
|
|
606
|
+
"archive export",
|
|
607
|
+
// src/db-sync.ts — emitted per-workstream after a successful
|
|
608
|
+
// `mu db export`. Used as the marker for src/parked.ts (the
|
|
609
|
+
// "presumed parked on another machine" heuristic for `mu workstream
|
|
610
|
+
// list` / TUI tab strip).
|
|
611
|
+
"db export"
|
|
607
612
|
];
|
|
608
613
|
function classifyEventVerb(payload) {
|
|
609
614
|
for (const verb of EVENT_VERB_PREFIXES) {
|
|
@@ -1121,17 +1126,19 @@ async function reconcile(db, opts) {
|
|
|
1121
1126
|
let statusChanges = 0;
|
|
1122
1127
|
const orphans = [];
|
|
1123
1128
|
const survivors = [];
|
|
1129
|
+
const pendingSurvivors = [];
|
|
1124
1130
|
for (const agent of dbAgents) {
|
|
1125
|
-
if (
|
|
1131
|
+
if (isPendingPaneId(agent.paneId)) {
|
|
1132
|
+
pendingSurvivors.push(agent);
|
|
1133
|
+
} else if (tmuxByPaneId.has(agent.paneId)) {
|
|
1126
1134
|
survivors.push(agent);
|
|
1127
1135
|
} else {
|
|
1128
1136
|
if (mode === "full") deleteAgent(db, agent.name, agent.workstreamName);
|
|
1129
1137
|
prunedGhosts++;
|
|
1130
1138
|
}
|
|
1131
1139
|
}
|
|
1132
|
-
if (mode
|
|
1140
|
+
if (mode === "full") {
|
|
1133
1141
|
for (const agent of survivors) {
|
|
1134
|
-
if (isPendingPaneId(agent.paneId)) continue;
|
|
1135
1142
|
const scrollback = await capturePane(agent.paneId, { lines: 100 });
|
|
1136
1143
|
const detected = detectPiStatus(scrollback);
|
|
1137
1144
|
if (shouldOverwriteAgentStatus(agent.status, detected) && detected !== agent.status) {
|
|
@@ -5269,6 +5276,31 @@ function exportArchive(db, opts) {
|
|
|
5269
5276
|
return { ...result, archiveLabel: opts.label, sourceCount: sources.length };
|
|
5270
5277
|
}
|
|
5271
5278
|
|
|
5279
|
+
// src/parked.ts
|
|
5280
|
+
var WORKSTREAM_PARKED_THRESHOLD_DAYS = 1;
|
|
5281
|
+
function parkedStatus(db, workstream, opts = {}) {
|
|
5282
|
+
const wsRow = db.prepare("SELECT id FROM workstreams WHERE name = ?").get(workstream);
|
|
5283
|
+
if (wsRow === void 0) return { parked: false };
|
|
5284
|
+
const latest = db.prepare(
|
|
5285
|
+
"SELECT kind, payload, created_at FROM agent_logs WHERE workstream_id = ? ORDER BY seq DESC LIMIT 1"
|
|
5286
|
+
).get(wsRow.id);
|
|
5287
|
+
if (latest === void 0) return { parked: false };
|
|
5288
|
+
if (latest.kind !== "event") return { parked: false };
|
|
5289
|
+
if (!latest.payload.startsWith("db export ")) return { parked: false };
|
|
5290
|
+
const aliveAgent = db.prepare("SELECT 1 AS x FROM agents WHERE workstream_id = ? AND status != 'closed' LIMIT 1").get(wsRow.id);
|
|
5291
|
+
if (aliveAgent !== void 0) return { parked: false };
|
|
5292
|
+
const inProgress = db.prepare("SELECT 1 AS x FROM tasks WHERE workstream_id = ? AND status = 'IN_PROGRESS' LIMIT 1").get(wsRow.id);
|
|
5293
|
+
if (inProgress !== void 0) return { parked: false };
|
|
5294
|
+
const threshold = Math.max(0, opts.thresholdDays ?? WORKSTREAM_PARKED_THRESHOLD_DAYS);
|
|
5295
|
+
const exportedAt = Date.parse(latest.created_at);
|
|
5296
|
+
if (Number.isNaN(exportedAt)) return { parked: false };
|
|
5297
|
+
const now = (opts.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
5298
|
+
const deltaMs = now - exportedAt;
|
|
5299
|
+
const deltaDays = Math.floor(deltaMs / (24 * 60 * 60 * 1e3));
|
|
5300
|
+
if (deltaDays < threshold) return { parked: false };
|
|
5301
|
+
return { parked: true, sinceDays: deltaDays };
|
|
5302
|
+
}
|
|
5303
|
+
|
|
5272
5304
|
// src/workstream.ts
|
|
5273
5305
|
var WORKSTREAM_NAME_RE = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
5274
5306
|
var RESERVED_WORKSTREAM_PREFIX = "mu-";
|
|
@@ -5340,6 +5372,7 @@ async function listWorkstreams(db) {
|
|
|
5340
5372
|
}
|
|
5341
5373
|
async function summarizeWorkstream(db, opts) {
|
|
5342
5374
|
const tmuxSession = opts.tmuxSession ?? `mu-${opts.workstream}`;
|
|
5375
|
+
const parked = parkedStatus(db, opts.workstream);
|
|
5343
5376
|
return {
|
|
5344
5377
|
name: opts.workstream,
|
|
5345
5378
|
tmuxSession,
|
|
@@ -5349,7 +5382,8 @@ async function summarizeWorkstream(db, opts) {
|
|
|
5349
5382
|
noteCount: countNotes(db, opts.workstream),
|
|
5350
5383
|
edgeCount: countEdges(db, opts.workstream),
|
|
5351
5384
|
workspaceCount: listWorkspaces(db, opts.workstream).length,
|
|
5352
|
-
registered: isRegistered(db, opts.workstream)
|
|
5385
|
+
registered: isRegistered(db, opts.workstream),
|
|
5386
|
+
...parked.parked ? { parked: { sinceDays: parked.sinceDays ?? 0 } } : {}
|
|
5353
5387
|
};
|
|
5354
5388
|
}
|
|
5355
5389
|
function isRegistered(db, workstream) {
|
|
@@ -6397,6 +6431,9 @@ var STATUS_EMOJI = {
|
|
|
6397
6431
|
terminated: "\uF057"
|
|
6398
6432
|
// nf-fa-times_circle
|
|
6399
6433
|
};
|
|
6434
|
+
function agentStatusGlyph(status) {
|
|
6435
|
+
return STATUS_EMOJI[status] ?? "?";
|
|
6436
|
+
}
|
|
6400
6437
|
var MAX_TITLE_LEN = 64;
|
|
6401
6438
|
var PENDING_PANE_PREFIX = "%pending-";
|
|
6402
6439
|
function pendingPaneIdFor(agentName) {
|
|
@@ -6410,7 +6447,7 @@ function composeAgentTitle(db, agent) {
|
|
|
6410
6447
|
const tasks = listTasksByOwner(db, agent.workstreamName, agent.name);
|
|
6411
6448
|
let title = agent.name;
|
|
6412
6449
|
if (showStatus) {
|
|
6413
|
-
title += ` \xB7 ${
|
|
6450
|
+
title += ` \xB7 ${agentStatusGlyph(agent.status)}`;
|
|
6414
6451
|
}
|
|
6415
6452
|
if (tasks.length === 1) {
|
|
6416
6453
|
title += ` \xB7 ${tasks[0]?.name}`;
|
|
@@ -7003,6 +7040,10 @@ function exportDb(db, file, opts = {}) {
|
|
|
7003
7040
|
const manifestPath = `${target}.manifest.json`;
|
|
7004
7041
|
const targetExists = existsSync12(target);
|
|
7005
7042
|
if (targetExists && opts.force !== true) throw new DbExportTargetExistsError(target);
|
|
7043
|
+
const preEventManifest = buildExportManifest(db);
|
|
7044
|
+
for (const ws of preEventManifest.workstreams) {
|
|
7045
|
+
emitEvent(db, ws.name, `db export ${ws.name} seq=${ws.latestSeq}`);
|
|
7046
|
+
}
|
|
7006
7047
|
const manifest = buildExportManifest(db);
|
|
7007
7048
|
mkdirSync4(dirname5(target), { recursive: true });
|
|
7008
7049
|
try {
|
|
@@ -7666,7 +7707,7 @@ function loadDoctorSummary(db, snapshot) {
|
|
|
7666
7707
|
checks.push({
|
|
7667
7708
|
name: "agents",
|
|
7668
7709
|
status: "warn",
|
|
7669
|
-
detail: `${ghosts} ghost pane${ghosts === 1 ? "" : "s"}; run \`mu agent list
|
|
7710
|
+
detail: `${ghosts} ghost pane${ghosts === 1 ? "" : "s"}; run \`mu state\` or \`mu agent list\` to reap`
|
|
7670
7711
|
});
|
|
7671
7712
|
}
|
|
7672
7713
|
const orphanPanes = snapshot.view.orphans.length;
|
|
@@ -7703,7 +7744,7 @@ function loadDoctorChecks(db, snapshot) {
|
|
|
7703
7744
|
function yankCommandForCheck(check) {
|
|
7704
7745
|
switch (check.name) {
|
|
7705
7746
|
case "agents":
|
|
7706
|
-
return "mu
|
|
7747
|
+
return "mu state";
|
|
7707
7748
|
case "panes":
|
|
7708
7749
|
return "mu agent adopt";
|
|
7709
7750
|
case "workspaces":
|
|
@@ -7721,10 +7762,10 @@ function remediationParagraph(check) {
|
|
|
7721
7762
|
switch (check.name) {
|
|
7722
7763
|
case "agents":
|
|
7723
7764
|
return [
|
|
7724
|
-
"A 'ghost pane' is a
|
|
7725
|
-
"
|
|
7726
|
-
"
|
|
7727
|
-
"
|
|
7765
|
+
"A 'ghost pane' is a registered agent whose tmux pane is gone.",
|
|
7766
|
+
"Doctor only reports the count. Run `mu state` or `mu agent list`",
|
|
7767
|
+
"to reap ghost agents and return their IN_PROGRESS tasks to OPEN.",
|
|
7768
|
+
"The TUI is read-only, but its slow tick uses the same state reap."
|
|
7728
7769
|
];
|
|
7729
7770
|
case "panes":
|
|
7730
7771
|
return [
|
|
@@ -7793,7 +7834,7 @@ async function loadWorkstreamSnapshotFast(db, workstream, opts = {}) {
|
|
|
7793
7834
|
};
|
|
7794
7835
|
}
|
|
7795
7836
|
async function loadWorkstreamSnapshotSlow(db, workstream, opts = {}, baseSnapshot) {
|
|
7796
|
-
const view = await listLiveAgents(db, { workstream
|
|
7837
|
+
const view = await listLiveAgents(db, { workstream });
|
|
7797
7838
|
let workspaces = listWorkspaces(db, workstream);
|
|
7798
7839
|
if (opts.withDirty === true) workspaces = await decorateWithDirty(workspaces);
|
|
7799
7840
|
const commits = await loadRecentCommits(opts.withRecentCommits);
|
|
@@ -7836,7 +7877,7 @@ function emptyLiveAgentsView() {
|
|
|
7836
7877
|
return {
|
|
7837
7878
|
agents: [],
|
|
7838
7879
|
orphans: [],
|
|
7839
|
-
report: { prunedGhosts: 0, statusChanges: 0, orphans: [], mode: "
|
|
7880
|
+
report: { prunedGhosts: 0, statusChanges: 0, orphans: [], mode: "full" }
|
|
7840
7881
|
};
|
|
7841
7882
|
}
|
|
7842
7883
|
function minimalSnapshot(workstream) {
|
|
@@ -7952,6 +7993,7 @@ export {
|
|
|
7952
7993
|
TaskNotInWorkstreamError,
|
|
7953
7994
|
TmuxError,
|
|
7954
7995
|
WORKSPACE_STALE_THRESHOLD,
|
|
7996
|
+
WORKSTREAM_PARKED_THRESHOLD_DAYS,
|
|
7955
7997
|
WorkspaceExistsError,
|
|
7956
7998
|
WorkspaceNotFoundError,
|
|
7957
7999
|
WorkspacePathNotEmptyError,
|
|
@@ -7963,6 +8005,7 @@ export {
|
|
|
7963
8005
|
addTask,
|
|
7964
8006
|
addToArchive,
|
|
7965
8007
|
adoptAgent,
|
|
8008
|
+
agentStatusGlyph,
|
|
7966
8009
|
agentStatusHistogram,
|
|
7967
8010
|
appendLog,
|
|
7968
8011
|
assertValidPaneId,
|
|
@@ -8078,6 +8121,7 @@ export {
|
|
|
8078
8121
|
openTask,
|
|
8079
8122
|
paneExists,
|
|
8080
8123
|
paneTTY,
|
|
8124
|
+
parkedStatus,
|
|
8081
8125
|
parseAgentNameFromTitle,
|
|
8082
8126
|
parsePsTtyOutput,
|
|
8083
8127
|
pruneSnapshots,
|