@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/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 ghosts (deleting
475
- * the registry row, which fires the deleteAgent reaper
476
- * that flips IN_PROGRESS tasks back to OPEN with
477
- * [reaper] notes), runs status detection against
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
- * Surfaced live by bug_pane_title_glyph_stuck_at_needs_input: the
502
- * old `dryRun: boolean` flag conflated "don't prune" with "don't
503
- * detect status", so state-card pollers showed stale status
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" | "status-only" | "report-only";
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 status-only and
530
- * report-only modes this is the count of rows that WOULD have
531
- * been pruned; in `full` mode it's the count actually deleted. */
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
- * Read-only callers split two ways:
1417
- * - `mu state`, `mu agent attach`
1418
- * `"status-only"`: refresh status + title (writes to DB),
1419
- * skip prune + reap. The operator's primary signal
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
- * Skipping prune is what protects mid-spawn placeholders (pane
1428
- * id `%pending-<name>`) from being treated as ghosts and pruned
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 agent list` calls this with `mode: "full"` (mutating); status
1449
- * pollers (`mu state`, `mu agent attach`) call it with
1450
- * `mode: "status-only"` to refresh status without pruning; read-only
1451
- * diagnostic / restore paths (`mu doctor`, `mu undo`) call it with
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
- * status-only refresh: don't prune mid-spawn placeholders or reap
3595
- * unreachable agents every render-mode is a polling read surface.
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
- 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 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, 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, 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, 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 };
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 (tmuxByPaneId.has(agent.paneId)) {
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 !== "report-only") {
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 ${STATUS_EMOJI[agent.status]}`;
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 agent list";
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 tmux pane that mu's reconcile pass would",
7725
- "prune on the next mutation. Run `mu agent list` to see the",
7726
- "current state, then `mu agent close <name>` if the agent is",
7727
- "stale. The TUI is read-only \u2014 no auto-prune."
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, mode: "status-only" });
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: "status-only" }
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,