@mu-crew/mu 4.1.1 → 4.2.0

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.js CHANGED
@@ -2,7 +2,7 @@ import { createRequire as __muCreateRequire } from "node:module"; const require
2
2
  import {
3
3
  STALLED_TOOL_MS,
4
4
  loadDashboard
5
- } from "./chunk-LBBZAG5C.js";
5
+ } from "./chunk-UL2QE43S.js";
6
6
  import {
7
7
  AGENT_STATE_GLYPH,
8
8
  DEFAULT_CTL_STUCK_AFTER_MS,
@@ -64,7 +64,7 @@ import {
64
64
  transitionActionReceipt,
65
65
  waitForEvents,
66
66
  waitWithActions
67
- } from "./chunk-B74N2EAS.js";
67
+ } from "./chunk-3ZLI6YTV.js";
68
68
  import {
69
69
  COURSE_CHECK_AGE_MS,
70
70
  COURSE_CHECK_CLOSED,
@@ -137,7 +137,7 @@ import {
137
137
  syncEnabled,
138
138
  syncPass,
139
139
  verifyAgainstManifest
140
- } from "./chunk-DEYBQDTO.js";
140
+ } from "./chunk-RVWNQLFE.js";
141
141
  import {
142
142
  AgentAbortNeedsCtlError,
143
143
  AgentAbortTimeoutError,
@@ -164,6 +164,11 @@ import {
164
164
  CAPTURE_TRIGGER_DDL,
165
165
  CAPTURE_VERSION,
166
166
  CURRENT_SCHEMA_VERSION,
167
+ CarrierJobKeptError,
168
+ CarrierJobUnresolvedError,
169
+ CarrierJobUnstoppedError,
170
+ CarrierStopFailedError,
171
+ CarrierStopRefusedError,
167
172
  ClaimerNotRegisteredError,
168
173
  CloseNeedsEvidenceError,
169
174
  CrossWorkstreamEdgeError,
@@ -178,11 +183,13 @@ import {
178
183
  GroupNotFoundError,
179
184
  HOST_ENV_KEYS,
180
185
  HOST_PLACEHOLDERS,
186
+ HOST_TRANSPORTS,
181
187
  HlcOverflowError,
182
188
  HlcParseError,
183
189
  HostChannelBusyError,
184
190
  HostCheckFailedError,
185
191
  HostExistsError,
192
+ HostHandbackError,
186
193
  HostInUseError,
187
194
  HostNotFoundError,
188
195
  HostProfileError,
@@ -190,6 +197,10 @@ import {
190
197
  HostTemplateError,
191
198
  HumanTaskError,
192
199
  InvalidSubstateError,
200
+ JOB_HANDLE_RE,
201
+ LeaseJobInvalidError,
202
+ LeaseJobTimeoutError,
203
+ LeaseJobUnreadableError,
193
204
  LogKindReservedError,
194
205
  MACHINE_LOCAL_ENTITIES,
195
206
  MACHINE_LOCAL_TABLES,
@@ -252,6 +263,7 @@ import {
252
263
  WorktreeHeldError,
253
264
  WorktreePathNotEmptyError,
254
265
  WorktreeVcsRequiredError,
266
+ abandonCarrierJob,
255
267
  abortAgent,
256
268
  acceptTask,
257
269
  activeMux,
@@ -267,6 +279,7 @@ import {
267
279
  assertRevision,
268
280
  assertValidPaneId,
269
281
  beginWait,
282
+ bindCarrierJobAgent,
270
283
  bindHostChannel,
271
284
  capture,
272
285
  captureTriggersCurrent,
@@ -294,12 +307,16 @@ import {
294
307
  envVarNameForCli,
295
308
  expandHostTemplate,
296
309
  expectsCtl,
310
+ fillCarrierJob,
297
311
  finishDelegate,
298
312
  forgetWorktree,
299
313
  formatHlc,
300
314
  formatPair,
301
315
  getAgent,
302
316
  getAgentByPane,
317
+ getCarrierJob,
318
+ getCarrierJobByAgent,
319
+ getCarrierJobByName,
303
320
  getDelegateCall,
304
321
  getHost,
305
322
  getPrerequisites,
@@ -310,6 +327,7 @@ import {
310
327
  getWorktreeAt,
311
328
  getWorktreeFor,
312
329
  hostEnvWords,
330
+ hostMuleStarter,
313
331
  hostStarter,
314
332
  idFromTitle,
315
333
  idFromTitleVerbose,
@@ -328,6 +346,8 @@ import {
328
346
  latestSeq,
329
347
  listAgents,
330
348
  listBlocked,
349
+ listCarrierJobs,
350
+ listCarrierJobsByName,
331
351
  listDelegates,
332
352
  listDelegatesOf,
333
353
  listGoals,
@@ -347,11 +367,13 @@ import {
347
367
  listWorktrees,
348
368
  locksDir,
349
369
  mapLegacyStatus,
370
+ markCarrierLaunch,
350
371
  markDelegate,
351
372
  murmurAvailable,
352
373
  muxByName,
353
374
  mux_exports,
354
375
  nextHlc,
376
+ noteCarrierJob,
355
377
  observationFromReading,
356
378
  observeAgentStates,
357
379
  openDb,
@@ -360,6 +382,7 @@ import {
360
382
  parkTask,
361
383
  parseHlc,
362
384
  parseVerdict,
385
+ placeholderUses,
363
386
  previewClose,
364
387
  readAgent,
365
388
  readAgentStates,
@@ -370,11 +393,13 @@ import {
370
393
  receiveHlc,
371
394
  reclaimWorktreePath,
372
395
  reconcile,
396
+ recordCarrierPane,
373
397
  recordCursors,
374
398
  recoveriesFor,
375
399
  registerDelegateCall,
376
400
  registerOwnedWait,
377
401
  registerWorktree,
402
+ releaseCarrierJob,
378
403
  releaseHostChannel,
379
404
  releaseHostChannelsForAgent,
380
405
  releaseTask,
@@ -383,6 +408,7 @@ import {
383
408
  removeHost,
384
409
  removeWorktree,
385
410
  reparentTask,
411
+ reserveCarrierJob,
386
412
  reserveHostChannel,
387
413
  resetCommandResolverForTests,
388
414
  resetMux,
@@ -393,6 +419,7 @@ import {
393
419
  resolveCliCommandWithSource,
394
420
  resolvePair,
395
421
  runHostTemplate,
422
+ runStop,
396
423
  selectRemoteRecovery,
397
424
  selectTaskNotes,
398
425
  sendToAgent,
@@ -430,7 +457,7 @@ import {
430
457
  worktreePath,
431
458
  worktreeState,
432
459
  worktreesRoot
433
- } from "./chunk-L7I3BLXP.js";
460
+ } from "./chunk-SFV5RPZW.js";
434
461
  import {
435
462
  defaultStateDir
436
463
  } from "./chunk-PZ5GOCWH.js";
@@ -581,6 +608,11 @@ export {
581
608
  COURSE_CHECK_AGE_MS,
582
609
  COURSE_CHECK_CLOSED,
583
610
  CURRENT_SCHEMA_VERSION,
611
+ CarrierJobKeptError,
612
+ CarrierJobUnresolvedError,
613
+ CarrierJobUnstoppedError,
614
+ CarrierStopFailedError,
615
+ CarrierStopRefusedError,
584
616
  ClaimerNotRegisteredError,
585
617
  CloseNeedsEvidenceError,
586
618
  CrossWorkstreamEdgeError,
@@ -603,12 +635,14 @@ export {
603
635
  GroupNotFoundError,
604
636
  HOST_ENV_KEYS,
605
637
  HOST_PLACEHOLDERS,
638
+ HOST_TRANSPORTS,
606
639
  HazardFailError,
607
640
  HlcOverflowError,
608
641
  HlcParseError,
609
642
  HostChannelBusyError,
610
643
  HostCheckFailedError,
611
644
  HostExistsError,
645
+ HostHandbackError,
612
646
  HostInUseError,
613
647
  HostNotFoundError,
614
648
  HostProfileError,
@@ -617,8 +651,12 @@ export {
617
651
  HumanNeededInvalidError,
618
652
  HumanTaskError,
619
653
  InvalidSubstateError,
654
+ JOB_HANDLE_RE,
620
655
  KNOWN_INTENTS,
621
656
  KNOWN_VERBS,
657
+ LeaseJobInvalidError,
658
+ LeaseJobTimeoutError,
659
+ LeaseJobUnreadableError,
622
660
  LogKindReservedError,
623
661
  MACHINE_LOCAL_ENTITIES,
624
662
  MACHINE_LOCAL_TABLES,
@@ -712,6 +750,7 @@ export {
712
750
  WorktreeHeldError,
713
751
  WorktreePathNotEmptyError,
714
752
  WorktreeVcsRequiredError,
753
+ abandonCarrierJob,
715
754
  abortAgent,
716
755
  acceptTask,
717
756
  activeMux,
@@ -739,6 +778,7 @@ export {
739
778
  authorizeTasks,
740
779
  autoPermitted,
741
780
  beginWait,
781
+ bindCarrierJobAgent,
742
782
  bindHostChannel,
743
783
  buildTaskBrief,
744
784
  callerWorkstreams,
@@ -788,6 +828,7 @@ export {
788
828
  executeNext,
789
829
  expandHostTemplate,
790
830
  expectsCtl,
831
+ fillCarrierJob,
791
832
  findCaseCollisions,
792
833
  finishDelegate,
793
834
  flushSegment,
@@ -799,6 +840,9 @@ export {
799
840
  getAgent,
800
841
  getAgentByPane,
801
842
  getAutoConfig,
843
+ getCarrierJob,
844
+ getCarrierJobByAgent,
845
+ getCarrierJobByName,
802
846
  getDelegateCall,
803
847
  getHost,
804
848
  getParallelTracks,
@@ -811,6 +855,7 @@ export {
811
855
  getWorktreeAt,
812
856
  getWorktreeFor,
813
857
  hostEnvWords,
858
+ hostMuleStarter,
814
859
  hostStarter,
815
860
  idFromTitle,
816
861
  idFromTitleVerbose,
@@ -832,6 +877,8 @@ export {
832
877
  latestSeq,
833
878
  listAgents,
834
879
  listBlocked,
880
+ listCarrierJobs,
881
+ listCarrierJobsByName,
835
882
  listDelegates,
836
883
  listDelegatesOf,
837
884
  listGoals,
@@ -855,12 +902,14 @@ export {
855
902
  localMachineId,
856
903
  locksDir,
857
904
  mapLegacyStatus,
905
+ markCarrierLaunch,
858
906
  markDelegate,
859
907
  migratePi,
860
908
  murmurAvailable,
861
909
  mux_exports as mux,
862
910
  muxByName,
863
911
  nextHlc,
912
+ noteCarrierJob,
864
913
  observationFromReading,
865
914
  observeAgentStates,
866
915
  opSubject,
@@ -875,6 +924,7 @@ export {
875
924
  parseVerdict,
876
925
  peerStatuses,
877
926
  pendingAttempts,
927
+ placeholderUses,
878
928
  planAttempt,
879
929
  previewClose,
880
930
  probeFilesystem,
@@ -889,12 +939,14 @@ export {
889
939
  receiveHlc,
890
940
  reclaimWorktreePath,
891
941
  reconcile,
942
+ recordCarrierPane,
892
943
  recordCursors,
893
944
  recordHumanNeeded,
894
945
  recoveriesFor,
895
946
  registerDelegateCall,
896
947
  registerOwnedWait,
897
948
  registerWorktree,
949
+ releaseCarrierJob,
898
950
  releaseHostChannel,
899
951
  releaseHostChannelsForAgent,
900
952
  releaseStall,
@@ -913,6 +965,7 @@ export {
913
965
  reparentTask,
914
966
  reserveActionReceipt,
915
967
  reserveAttempt,
968
+ reserveCarrierJob,
916
969
  reserveHostChannel,
917
970
  resetCommandResolverForTests,
918
971
  resetMux,
@@ -925,6 +978,7 @@ export {
925
978
  resolvePair,
926
979
  resolvePeerRef,
927
980
  runHostTemplate,
981
+ runStop,
928
982
  saveResume,
929
983
  segmentLineCount,
930
984
  segmentPath,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tracks.ts"],"sourcesContent":["// mu — parallel-track detection via union-find with diamond merge.\n//\n// Port of a parallel-tracks union-find algorithm cribbed from a\n// prior internal multi-agent runtime. The killer feature: when two goals share a prerequisite, their subgraphs\n// overlap and they collapse into ONE track, so two agents are never\n// assigned tasks that share a dependency.\n//\n// goal_a goal_b goal_a goal_b\n// \\ / \\ /\n// shared → shared (1 track)\n// | |\n// leaf leaf\n//\n// Algorithm:\n// 1. Get all open goals (tasks with no outgoing edges, not CLOSED).\n// 2. In one recursive query, compute each goal's prerequisite subgraph\n// (everything transitively reachable via reverse edges).\n// 3. Build union-find: merge goals while observing shared tasks.\n// 4. Each connected component is one Track.\n\nimport { compareStrings } from \"./compare.js\";\nimport type { Db } from \"./db.js\";\nimport { listGoals, listReady, type TaskRow } from \"./tasks.js\";\n\nexport interface Track {\n /** Goal tasks (no outgoing edges) belonging to this track. */\n roots: TaskRow[];\n /** Every task id reachable as a prerequisite of any root in this track. */\n taskIds: ReadonlySet<string>;\n /** Number of READY tasks (per the SQL view) within this track's subgraph. */\n readyCount: number;\n /** True when every non-CLOSED task in the track is OPEN/parked: the\n * track holds work, but none of it is schedulable. */\n parked: boolean;\n}\n\n/**\n * Identify independent task subtrees suitable for parallel assignment\n * within a workstream. Open goals only; CLOSED goals are excluded as\n * they no longer represent work to schedule.\n *\n * Scoping: only goals belonging to `workstream` are considered.\n * Cross-workstream edges are forbidden by addTask, so a goal's\n * prerequisite subgraph is naturally workstream-internal.\n */\nexport function getParallelTracks(db: Db, workstream: string): Track[] {\n // listGoals already filters via the SQL view (status <> CLOSED),\n // but defence-in-depth: a stale db snapshot or future view tweak\n // shouldn't let closed goals leak into track count.\n const goals = listGoals(db, workstream).filter((g) => g.status !== \"CLOSED\");\n if (goals.length === 0) return [];\n\n // 2. Compute every goal's prerequisite subgraph in one traversal.\n // Running one recursive query per goal made the TUI's fast tick scale\n // with the number of goals. The goal id carried through this CTE keeps\n // the same inclusive per-goal sets in one SQLite round-trip.\n const reach = db\n .prepare(\n `WITH RECURSIVE\n active_goals(id, local_id) AS (\n SELECT g.id, g.local_id\n FROM goals g\n JOIN workstreams ws ON ws.id = g.workstream_id\n WHERE ws.name = ?\n ),\n reach(goal_id, node_id) AS (\n SELECT id, id FROM active_goals\n UNION\n SELECT r.goal_id, e.from_task_id\n FROM reach r\n JOIN task_edges e ON e.to_task_id = r.node_id\n )\n SELECT goal.local_id AS goal_id, node.local_id AS task_id,\n node.status AS status, node.substate AS substate\n FROM reach r\n JOIN tasks goal ON goal.id = r.goal_id\n JOIN tasks node ON node.id = r.node_id`,\n )\n .all(workstream) as Array<{\n goal_id: string;\n task_id: string;\n status: string;\n substate: string;\n }>;\n\n const subgraphs = new Map(goals.map((goal) => [goal.name, new Set<string>()]));\n const uf = new UnionFind(goals.map((goal) => goal.name));\n const firstGoalByTask = new Map<string, string>();\n // Substate of every non-CLOSED task reached, for the per-track `parked` flag.\n const openSubstate = new Map<string, string>();\n for (const row of reach) {\n if (row.status !== \"CLOSED\") openSubstate.set(row.task_id, row.substate);\n subgraphs.get(row.goal_id)?.add(row.task_id);\n const firstGoal = firstGoalByTask.get(row.task_id);\n if (firstGoal === undefined) firstGoalByTask.set(row.task_id, row.goal_id);\n else uf.union(firstGoal, row.goal_id);\n }\n\n // 4. Group goals + subgraph task ids by union-find root.\n const componentTaskIds = new Map<string, Set<string>>();\n const componentRoots = new Map<string, TaskRow[]>();\n for (const goal of goals) {\n const root = uf.find(goal.name);\n let bucket = componentTaskIds.get(root);\n if (!bucket) {\n bucket = new Set<string>();\n componentTaskIds.set(root, bucket);\n componentRoots.set(root, []);\n }\n componentRoots.get(root)?.push(goal);\n const sub = subgraphs.get(goal.name);\n if (sub) {\n for (const id of sub) bucket.add(id);\n }\n }\n\n // 5. Compute ready counts per track.\n const readyIds = new Set(listReady(db, workstream).map((t) => t.name));\n const tracks: Track[] = [];\n for (const [root, taskIds] of componentTaskIds) {\n const trackRoots = componentRoots.get(root) ?? [];\n let readyCount = 0;\n let openCount = 0;\n let parkedCount = 0;\n for (const id of taskIds) {\n if (readyIds.has(id)) readyCount++;\n const sub = openSubstate.get(id);\n if (sub === undefined) continue;\n openCount++;\n if (sub === \"parked\") parkedCount++;\n }\n const parked = openCount > 0 && parkedCount === openCount;\n tracks.push({ roots: trackRoots, taskIds, readyCount, parked });\n }\n\n // Stable order: by primary root's localId so output is deterministic.\n tracks.sort((a, b) => {\n const an = a.roots[0]?.name ?? \"\";\n const bn = b.roots[0]?.name ?? \"\";\n return compareStrings(an, bn);\n });\n return tracks;\n}\n\nclass UnionFind {\n private readonly parent = new Map<string, string>();\n private readonly rank = new Map<string, number>();\n\n constructor(items: readonly string[]) {\n for (const item of items) {\n this.parent.set(item, item);\n this.rank.set(item, 0);\n }\n }\n\n find(x: string): string {\n let root = x;\n while (true) {\n const next = this.parent.get(root);\n if (next === undefined || next === root) break;\n root = next;\n }\n // Path compression.\n let curr = x;\n while (curr !== root) {\n const next = this.parent.get(curr);\n if (next === undefined) break;\n this.parent.set(curr, root);\n curr = next;\n }\n return root;\n }\n\n union(a: string, b: string): void {\n const rootA = this.find(a);\n const rootB = this.find(b);\n if (rootA === rootB) return;\n const rankA = this.rank.get(rootA) ?? 0;\n const rankB = this.rank.get(rootB) ?? 0;\n if (rankA < rankB) {\n this.parent.set(rootA, rootB);\n } else if (rankA > rankB) {\n this.parent.set(rootB, rootA);\n } else {\n this.parent.set(rootB, rootA);\n this.rank.set(rootA, rankA + 1);\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/tracks.ts"],"sourcesContent":["// mu — parallel-track detection via union-find with diamond merge.\n//\n// Port of a parallel-tracks union-find algorithm cribbed from a\n// prior internal multi-agent runtime. The killer feature: when two goals share a prerequisite, their subgraphs\n// overlap and they collapse into ONE track, so two agents are never\n// assigned tasks that share a dependency.\n//\n// goal_a goal_b goal_a goal_b\n// \\ / \\ /\n// shared → shared (1 track)\n// | |\n// leaf leaf\n//\n// Algorithm:\n// 1. Get all open goals (tasks with no outgoing edges, not CLOSED).\n// 2. In one recursive query, compute each goal's prerequisite subgraph\n// (everything transitively reachable via reverse edges).\n// 3. Build union-find: merge goals while observing shared tasks.\n// 4. Each connected component is one Track.\n\nimport { compareStrings } from \"./compare.js\";\nimport type { Db } from \"./db.js\";\nimport { listGoals, listReady, type TaskRow } from \"./tasks.js\";\n\nexport interface Track {\n /** Goal tasks (no outgoing edges) belonging to this track. */\n roots: TaskRow[];\n /** Every task id reachable as a prerequisite of any root in this track. */\n taskIds: ReadonlySet<string>;\n /** Number of READY tasks (per the SQL view) within this track's subgraph. */\n readyCount: number;\n /** True when every non-CLOSED task in the track is OPEN/parked: the\n * track holds work, but none of it is schedulable. */\n parked: boolean;\n}\n\n/**\n * Identify independent task subtrees suitable for parallel assignment\n * within a workstream. Open goals only; CLOSED goals are excluded as\n * they no longer represent work to schedule.\n *\n * Scoping: only goals belonging to `workstream` are considered.\n * Cross-workstream edges are forbidden by addTask, so a goal's\n * prerequisite subgraph is naturally workstream-internal.\n */\nexport function getParallelTracks(db: Db, workstream: string): Track[] {\n // listGoals already filters via the SQL view (status <> CLOSED),\n // but defence-in-depth: a stale db snapshot or future view tweak\n // shouldn't let closed goals leak into track count.\n const goals = listGoals(db, workstream).filter((g) => g.status !== \"CLOSED\");\n if (goals.length === 0) return [];\n\n // 2. Compute every goal's prerequisite subgraph in one traversal.\n // Running one recursive query per goal made the TUI's fast tick scale\n // with the number of goals. The goal id carried through this CTE keeps\n // the same inclusive per-goal sets in one SQLite round-trip.\n const reach = db\n .prepare(\n `WITH RECURSIVE\n active_goals(id, local_id) AS (\n SELECT g.id, g.local_id\n FROM goals g\n JOIN workstreams ws ON ws.id = g.workstream_id\n WHERE ws.name = ?\n ),\n reach(goal_id, node_id) AS (\n SELECT id, id FROM active_goals\n UNION\n SELECT r.goal_id, e.from_task_id\n FROM reach r\n JOIN task_edges e ON e.to_task_id = r.node_id\n )\n SELECT goal.local_id AS goal_id, node.local_id AS task_id,\n node.status AS status, node.substate AS substate\n FROM reach r\n JOIN tasks goal ON goal.id = r.goal_id\n JOIN tasks node ON node.id = r.node_id`,\n )\n .all(workstream) as Array<{\n goal_id: string;\n task_id: string;\n status: string;\n substate: string;\n }>;\n\n const subgraphs = new Map(goals.map((goal) => [goal.name, new Set<string>()]));\n const uf = new UnionFind(goals.map((goal) => goal.name));\n const firstGoalByTask = new Map<string, string>();\n // Substate of every non-CLOSED task reached, for the per-track `parked` flag.\n const openSubstate = new Map<string, string>();\n for (const row of reach) {\n if (row.status !== \"CLOSED\") openSubstate.set(row.task_id, row.substate);\n subgraphs.get(row.goal_id)?.add(row.task_id);\n const firstGoal = firstGoalByTask.get(row.task_id);\n if (firstGoal === undefined) firstGoalByTask.set(row.task_id, row.goal_id);\n else uf.union(firstGoal, row.goal_id);\n }\n\n // 4. Group goals + subgraph task ids by union-find root.\n const componentTaskIds = new Map<string, Set<string>>();\n const componentRoots = new Map<string, TaskRow[]>();\n for (const goal of goals) {\n const root = uf.find(goal.name);\n let bucket = componentTaskIds.get(root);\n if (!bucket) {\n bucket = new Set<string>();\n componentTaskIds.set(root, bucket);\n componentRoots.set(root, []);\n }\n componentRoots.get(root)?.push(goal);\n const sub = subgraphs.get(goal.name);\n if (sub) {\n for (const id of sub) bucket.add(id);\n }\n }\n\n // 5. Compute ready counts per track.\n const readyIds = new Set(listReady(db, workstream).map((t) => t.name));\n const tracks: Track[] = [];\n for (const [root, taskIds] of componentTaskIds) {\n const trackRoots = componentRoots.get(root) ?? [];\n let readyCount = 0;\n let openCount = 0;\n let parkedCount = 0;\n for (const id of taskIds) {\n if (readyIds.has(id)) readyCount++;\n const sub = openSubstate.get(id);\n if (sub === undefined) continue;\n openCount++;\n if (sub === \"parked\") parkedCount++;\n }\n const parked = openCount > 0 && parkedCount === openCount;\n tracks.push({ roots: trackRoots, taskIds, readyCount, parked });\n }\n\n // Stable order: by primary root's localId so output is deterministic.\n tracks.sort((a, b) => {\n const an = a.roots[0]?.name ?? \"\";\n const bn = b.roots[0]?.name ?? \"\";\n return compareStrings(an, bn);\n });\n return tracks;\n}\n\nclass UnionFind {\n private readonly parent = new Map<string, string>();\n private readonly rank = new Map<string, number>();\n\n constructor(items: readonly string[]) {\n for (const item of items) {\n this.parent.set(item, item);\n this.rank.set(item, 0);\n }\n }\n\n find(x: string): string {\n let root = x;\n while (true) {\n const next = this.parent.get(root);\n if (next === undefined || next === root) break;\n root = next;\n }\n // Path compression.\n let curr = x;\n while (curr !== root) {\n const next = this.parent.get(curr);\n if (next === undefined) break;\n this.parent.set(curr, root);\n curr = next;\n }\n return root;\n }\n\n union(a: string, b: string): void {\n const rootA = this.find(a);\n const rootB = this.find(b);\n if (rootA === rootB) return;\n const rankA = this.rank.get(rootA) ?? 0;\n const rankB = this.rank.get(rootB) ?? 0;\n if (rankA < rankB) {\n this.parent.set(rootA, rootB);\n } else if (rankA > rankB) {\n this.parent.set(rootB, rootA);\n } else {\n this.parent.set(rootB, rootA);\n this.rank.set(rootA, rankA + 1);\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -3,14 +3,14 @@ import {
3
3
  formatElapsed,
4
4
  loadDashboard,
5
5
  matches
6
- } from "./chunk-LBBZAG5C.js";
6
+ } from "./chunk-UL2QE43S.js";
7
7
  import {
8
8
  KNOWN_VERBS,
9
9
  ambientIngest,
10
10
  currentCallers,
11
11
  parseOpKey,
12
12
  syncEnabled
13
- } from "./chunk-DEYBQDTO.js";
13
+ } from "./chunk-RVWNQLFE.js";
14
14
  import {
15
15
  DEFAULT_SUBSTATE,
16
16
  TASK_STATUSES,
@@ -27,7 +27,7 @@ import {
27
27
  spawnerFromEnv,
28
28
  tmuxAttachMode,
29
29
  verdictLineLabel
30
- } from "./chunk-L7I3BLXP.js";
30
+ } from "./chunk-SFV5RPZW.js";
31
31
  import "./chunk-PZ5GOCWH.js";
32
32
 
33
33
  // src/cli/tui/index.ts
@@ -667,8 +667,8 @@ function taskLine(t) {
667
667
  function useYank() {
668
668
  const { stdout } = useStdout();
669
669
  return async (target, all = false) => {
670
- const plan = target.kind === "scrollback" ? await planAttach(target) : null;
671
- const command = copyCommand(target, { all, attachLine: plan?.line ?? null });
670
+ const plan = typeof target !== "string" && target.kind === "scrollback" ? await planAttach(target) : null;
671
+ const command = typeof target === "string" ? target : copyCommand(target, { all, attachLine: plan?.line ?? null });
672
672
  const y = yankSequence(command);
673
673
  const why = plan !== null && !plan.ok ? ` (attach unavailable: ${plan.reason})` : "";
674
674
  if (!y.ok) return `not copied (${y.why}): ${command}${why}`;
@@ -1240,17 +1240,16 @@ function attentionStrip(attention, ws) {
1240
1240
  ])
1241
1241
  ];
1242
1242
  }
1243
- var attentionRole = (a) => a.kind === "dead" || a.kind === "delegate" && a.text.startsWith("delegate failed") ? "bad" : "warn";
1243
+ var attentionRole = (a) => a.kind === "dead" ? "bad" : "warn";
1244
1244
  function attentionRows(snap, ws) {
1245
1245
  return snap.attention.map((a) => {
1246
1246
  const agent = snap.agents.find((x) => x.key === a.target);
1247
1247
  const id = a.kind === "human" || a.kind === "delegate" ? a.target?.slice((a.workstream?.length ?? 0) + 1) : void 0;
1248
1248
  const drill = agent ? scrollbackOf(agent) : a.workstream !== null && id ? { kind: "notes", workstream: a.workstream, id, title: a.text } : void 0;
1249
- return rowOf(
1250
- a.key,
1251
- [{ text: attentionLabel(a, ws), role: attentionRole(a) }],
1252
- drill ? { drill } : {}
1253
- );
1249
+ return rowOf(a.key, [{ text: attentionLabel(a, ws), role: attentionRole(a) }], {
1250
+ ...drill ? { drill } : {},
1251
+ ...a.fix !== void 0 ? { fix: a.fix } : {}
1252
+ });
1254
1253
  });
1255
1254
  }
1256
1255
  function waveText(w) {
@@ -1481,7 +1480,7 @@ function layout(rows, lines = FIXED_BY_PRIORITY) {
1481
1480
  return { shown, height: Math.max(0, rows - TOP_LINES - shown.size) };
1482
1481
  }
1483
1482
  var HINT_HEAD = `\u2190/\u2192 tab q quit [/] workstream ${NAV_HINT} move`;
1484
- var HINT_OVERVIEW = `${HINT_HEAD} Enter open y copy +/- rate`;
1483
+ var HINT_OVERVIEW = `${HINT_HEAD} Enter open y copy fix +/- rate`;
1485
1484
  var HINT = `${HINT_HEAD} Enter open / filter c clear y copy +/- rate`;
1486
1485
  var HINT_TASKS = `${HINT_HEAD} Enter open s sort o status / filter c clear y copy +/- rate`;
1487
1486
  var hintAgents = (attachKey) => `${HINT_HEAD} Enter scrollback ${attachKey} / filter c clear y copy +/- rate`;
@@ -1660,7 +1659,7 @@ function App({
1660
1659
  const target = all[sel]?.drill;
1661
1660
  if (target?.kind === "scrollback") void attach(target);
1662
1661
  } else if (input === "y") {
1663
- const target = all[sel]?.drill;
1662
+ const target = all[sel]?.fix ?? all[sel]?.drill;
1664
1663
  if (target !== void 0) void yank(target).then(setFlash);
1665
1664
  }
1666
1665
  },
@@ -1845,4 +1844,4 @@ async function runDashboard(opts) {
1845
1844
  export {
1846
1845
  runDashboard
1847
1846
  };
1848
- //# sourceMappingURL=tui-EN2CDP6G.js.map
1847
+ //# sourceMappingURL=tui-3ZPLSLDW.js.map