@ferris1225/pi-subagents 4.1.2 → 4.1.4
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 +584 -506
- package/agents/cleaner.md +4 -4
- package/agents/documenter.md +46 -44
- package/agents/explorer.md +15 -11
- package/agents/reviewer.md +7 -4
- package/agents/worker.md +7 -5
- package/package.json +55 -55
- package/src/agents.ts +42 -1
- package/src/config.ts +5 -5
- package/src/dispatch.ts +647 -637
- package/src/fixloop.ts +90 -127
- package/src/monitor.ts +97 -27
- package/src/prompt.ts +3 -3
- package/src/rpc-run.ts +6 -3
- package/src/runtime.ts +5 -0
- package/src/setup.ts +151 -136
- package/src/spawn.ts +8 -2
- package/src/thread-lifecycle.ts +46 -14
- package/src/tools.ts +44 -30
- package/src/widget.ts +65 -19
package/src/tools.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Thread controls and lookup tools around the subagent runtime:
|
|
3
|
-
* subagent_control (steer/retarget/park/resume), subagent_wait (in-turn
|
|
4
|
-
* lookup), subagent_status, and destructive subagent_stop.
|
|
3
|
+
* subagent_control (steer/retarget/park/resume/fork), subagent_wait (in-turn
|
|
4
|
+
* result lookup), subagent_status, and destructive subagent_stop.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
@@ -12,8 +12,7 @@ import { DEFAULT_MAX_RESULT_LINES, loadConfig } from "./config.ts";
|
|
|
12
12
|
import { formatCompletionBlock, formatUsage, matchRunIds } from "./format.ts";
|
|
13
13
|
import { emptyUsage } from "./rpc-run.ts";
|
|
14
14
|
import {
|
|
15
|
-
|
|
16
|
-
formatUsageCompact,
|
|
15
|
+
formatTaskSummary,
|
|
17
16
|
isRunActiveStatus,
|
|
18
17
|
monitor,
|
|
19
18
|
runLabel,
|
|
@@ -51,7 +50,7 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
51
50
|
Type.String({ description: "Instruction queued by steer after the current child tool batch." }),
|
|
52
51
|
),
|
|
53
52
|
objective: Type.Optional(
|
|
54
|
-
Type.String({ description: "Replacement objective for retarget
|
|
53
|
+
Type.String({ description: "Replacement objective for retarget; optional appended objective for resume/fork. Omit on resume/fork to continue the current retained objective." }),
|
|
55
54
|
),
|
|
56
55
|
});
|
|
57
56
|
|
|
@@ -64,14 +63,14 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
64
63
|
"retarget replaces the objective in that same active top-level child.",
|
|
65
64
|
"Managed downstream documenter/reviewer/fix stages are controlled by the parent queue rather than its settled RPC control: use park or stop there, then resume with an objective to redirect retained context.",
|
|
66
65
|
"park aborts to a stable checkpoint, terminates the child, preserves context, and releases its concurrency slot.",
|
|
67
|
-
"resume restarts a parked, completed, or failed retained thread with the same run id; objective
|
|
68
|
-
"fork copies a parked/completed/failed retained session branch into a new logical thread and run id; an isolated checkpoint must be settled and integrated first; objective
|
|
66
|
+
"resume restarts a parked, completed, or failed retained thread with the same run id and cumulative active time; omit objective to continue the current goal, or provide one to append it to retained context and make it the displayed current goal.",
|
|
67
|
+
"fork copies a parked/completed/failed retained session branch into a new logical thread and run id; an isolated checkpoint must be settled and integrated first; omit objective to continue the current goal, or provide one to append a new branch goal.",
|
|
69
68
|
].join(" "),
|
|
70
69
|
promptSnippet: "Control a subagent thread: steer/retarget an active top-level child; park/stop a managed downstream stage; resume or fork retained context.",
|
|
71
70
|
promptGuidelines: [
|
|
72
71
|
"Use subagent_control steer to refine an active top-level RPC child without restarting it; the instruction is delivered after its current tool batch.",
|
|
73
72
|
"Use subagent_control retarget only while that top-level child is active. During a managed downstream stage, park it and resume with a replacement objective instead.",
|
|
74
|
-
"Use subagent_control park to checkpoint useful context while releasing the process/concurrency slot, and resume to continue the same run id later.",
|
|
73
|
+
"Use subagent_control park to checkpoint useful context while releasing the process/concurrency slot, and resume to continue the same run id later. Resume without objective keeps the current goal; resume with objective appends that goal to retained context.",
|
|
75
74
|
"Use subagent_control fork only on a parked or settled retained thread; isolated work must settle and integrate before it can fork. Fork creates a new run id while leaving the source untouched.",
|
|
76
75
|
"Use subagent_stop only for destructive cancellation; it retires that thread's retained session without retiring independent forks.",
|
|
77
76
|
],
|
|
@@ -110,13 +109,15 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
110
109
|
thread.task = objective;
|
|
111
110
|
thread.control.retargetPending(objective);
|
|
112
111
|
monitor.setTask(thread.id, objective);
|
|
113
|
-
|
|
112
|
+
monitor.setContinuationKind(thread.id, "retarget");
|
|
113
|
+
return { content: [{ type: "text", text: `Updated queued run #${thread.id} to the replacement objective; no child was spawned by this control action.` }], details: {} };
|
|
114
114
|
}
|
|
115
115
|
if (!(["starting", "running", "steering", "interrupting", "retrying"] as const).includes(phase as any)) {
|
|
116
116
|
return { content: [{ type: "text", text: `Run #${thread.id} is ${thread.state}; use resume with objective to restart retained context.` }], details: {} };
|
|
117
117
|
}
|
|
118
118
|
thread.task = objective;
|
|
119
119
|
monitor.setTask(thread.id, objective);
|
|
120
|
+
monitor.setContinuationKind(thread.id, "retarget");
|
|
120
121
|
await thread.control.retarget(objective);
|
|
121
122
|
return { content: [{ type: "text", text: `Retargeted run #${thread.id} in the same session; the aborted objective will not be delivered as a completion.` }], details: {} };
|
|
122
123
|
}
|
|
@@ -146,11 +147,19 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
146
147
|
if (params.objective !== undefined && !objective) {
|
|
147
148
|
return { content: [{ type: "text", text: "resume objective must be non-blank when provided." }], details: {} };
|
|
148
149
|
}
|
|
150
|
+
const hadRetainedSession = Boolean(thread.sessionId && thread.sessionDir);
|
|
149
151
|
const pending = await thread.resume(objective, ctx);
|
|
150
152
|
if (pending.exitCode !== -1) {
|
|
151
153
|
return { content: [{ type: "text", text: getResultOutput(pending) }], details: {} };
|
|
152
154
|
}
|
|
153
|
-
|
|
155
|
+
const currentObjective = formatTaskSummary(objective ?? thread.task, 80, false);
|
|
156
|
+
const mode = objective
|
|
157
|
+
? `appended objective: ${currentObjective}`
|
|
158
|
+
: `continuing current objective: ${currentObjective}`;
|
|
159
|
+
const context = hadRetainedSession
|
|
160
|
+
? "the same retained session and prior context are preserved"
|
|
161
|
+
: "no prior child session existed, so only the logical run and objective are continued";
|
|
162
|
+
return { content: [{ type: "text", text: `Resumed run #${thread.id}, ${mode}; ${context}, and cumulative active time is preserved. Completion will arrive automatically.` }], details: {}, terminate: true };
|
|
154
163
|
}
|
|
155
164
|
case "fork": {
|
|
156
165
|
const objective = params.objective === undefined ? undefined : nonBlank(params.objective);
|
|
@@ -164,7 +173,7 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
164
173
|
return {
|
|
165
174
|
content: [{
|
|
166
175
|
type: "text",
|
|
167
|
-
text: `Forked run #${thread.id} into new run #${pending.runId}${objective ?
|
|
176
|
+
text: `Forked run #${thread.id} into new run #${pending.runId}; ${objective ? `appended branch objective: ${formatTaskSummary(objective, 80, false)}` : `continuing current objective: ${formatTaskSummary(thread.task, 80, false)}`}. Retained context is copied, the source is unchanged, and child completion will arrive automatically.`,
|
|
168
177
|
}],
|
|
169
178
|
details: { sourceRunId: thread.id, childRunId: pending.runId, result: pending },
|
|
170
179
|
terminate: true,
|
|
@@ -352,10 +361,7 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
352
361
|
});
|
|
353
362
|
|
|
354
363
|
// Status overview: what is running right now and what finished this session,
|
|
355
|
-
// with per-run details (id,
|
|
356
|
-
// main agent can decide whether to wait, stop, or re-dispatch. Learned from
|
|
357
|
-
// nicobailon/pi-subagents ({action:"status"} + status files): inspect before
|
|
358
|
-
// you act, and report run ids when handing off.
|
|
364
|
+
// with per-run details (id, role, model, usage, elapsed, activity).
|
|
359
365
|
const SubagentStatusParams = Type.Object({
|
|
360
366
|
id: Type.Optional(
|
|
361
367
|
Type.String({
|
|
@@ -368,7 +374,7 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
368
374
|
name: "subagent_status",
|
|
369
375
|
label: "Subagent Status",
|
|
370
376
|
description: [
|
|
371
|
-
"List active background sub-agent runs (id,
|
|
377
|
+
"List active background sub-agent runs (id, role, model, thinking, usage, elapsed, current activity) and recently finished results.",
|
|
372
378
|
"Pass id to read the full result of a finished run; pass no id for the overview.",
|
|
373
379
|
"Use it to decide whether to subagent_wait, subagent_stop, or re-dispatch — never to poll: results arrive by themselves.",
|
|
374
380
|
].join(" "),
|
|
@@ -412,20 +418,30 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
412
418
|
const activeThread = runtime.threads.get(active.id);
|
|
413
419
|
const managedDownstream =
|
|
414
420
|
activeThread?.state === "running" && activeThread.control.getPhase() === "settled";
|
|
421
|
+
const activeChild = runs.find((run) =>
|
|
422
|
+
run.parentRunId === active.id && isRunActiveStatus(run.status)
|
|
423
|
+
);
|
|
424
|
+
const owner = active.managedWorkflow ? `${active.agent} workflow` : active.agent;
|
|
425
|
+
const retainedStage = active.managedWorkflow && activeThread?.agentName !== active.agent
|
|
426
|
+
? activeThread?.agentName
|
|
427
|
+
: undefined;
|
|
415
428
|
const metadata = [
|
|
416
429
|
activeThread?.isolation === "worktree" ? `worktree ${active.integrationStatus ?? activeThread.worktree?.state ?? "active"}` : undefined,
|
|
417
430
|
activeThread?.forkedFromRunId !== undefined ? `forked from #${activeThread.forkedFromRunId}` : undefined,
|
|
418
431
|
(activeThread?.forkChildRunIds.length ?? 0) > 0 ? `forks ${activeThread!.forkChildRunIds.map((id) => `#${id}`).join(",")}` : undefined,
|
|
419
432
|
].filter(Boolean).join(" · ");
|
|
433
|
+
const stageStatus = activeChild
|
|
434
|
+
? monitor.summarize(activeChild)
|
|
435
|
+
: active.activity ?? statusLabel(active.status);
|
|
420
436
|
return {
|
|
421
437
|
content: [
|
|
422
438
|
{
|
|
423
439
|
type: "text",
|
|
424
440
|
text: parked
|
|
425
|
-
? `Run #${active.id} ${
|
|
441
|
+
? `Run #${active.id} ${owner} is parked with retained${retainedStage ? ` ${retainedStage} stage` : ""} context${metadata ? ` (${metadata})` : ""}. Use subagent_control resume to restart it, or subagent_stop to retire it.`
|
|
426
442
|
: managedDownstream
|
|
427
|
-
? `Run #${active.id} ${
|
|
428
|
-
: `Run #${active.id} ${
|
|
443
|
+
? `Run #${active.id} ${owner} is in a managed downstream stage (${stageStatus}${metadata ? ` · ${metadata}` : ""}). Use subagent_wait for its result, subagent_control park to checkpoint it, or subagent_stop to cancel it. Steer/retarget are unavailable until you park and resume the retained stage.`
|
|
444
|
+
: `Run #${active.id} ${owner} is still active (${active.activity ?? statusLabel(active.status)}${metadata ? ` · ${metadata}` : ""}). Use subagent_wait to block for its result, subagent_control to steer/park it, or subagent_stop to cancel it.`,
|
|
429
445
|
},
|
|
430
446
|
],
|
|
431
447
|
details: {},
|
|
@@ -434,36 +450,34 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
434
450
|
return { content: [{ type: "text", text: `No subagent run matches "${requested}".` }], details: {} };
|
|
435
451
|
}
|
|
436
452
|
|
|
437
|
-
const now = Date.now();
|
|
438
453
|
const activeRuns = monitor.getRuns().filter(
|
|
439
454
|
(run) => isRunActiveStatus(run.status),
|
|
440
455
|
);
|
|
441
456
|
const activeLines = activeRuns.map((run) => {
|
|
442
457
|
const thread = runtime.threads.get(run.id);
|
|
443
|
-
const model = run.modelFallbackFrom
|
|
444
|
-
? `${run.model ?? "?"} (main after ${run.modelFallbackFrom} failed)`
|
|
445
|
-
: (run.model ?? "?");
|
|
446
458
|
const parts = [
|
|
447
|
-
`#${run.id} ${run
|
|
459
|
+
`#${run.id} ${monitor.summarize(run)}`,
|
|
448
460
|
run.label,
|
|
449
|
-
model,
|
|
450
|
-
formatUsageCompact(run.usage),
|
|
451
|
-
formatElapsed(run, now),
|
|
452
|
-
thread?.isolation === "worktree" ? `worktree ${run.integrationStatus ?? thread.worktree?.state ?? "active"}` : undefined,
|
|
453
461
|
thread?.forkedFromRunId !== undefined ? `forked from #${thread.forkedFromRunId}` : undefined,
|
|
454
462
|
(thread?.forkChildRunIds.length ?? 0) > 0 ? `forks ${thread!.forkChildRunIds.map((id) => `#${id}`).join(",")}` : undefined,
|
|
463
|
+
run.activity ?? statusLabel(run.status),
|
|
455
464
|
].filter(Boolean);
|
|
456
|
-
return `- ${parts.join(" · ")}
|
|
465
|
+
return `- ${parts.join(" · ")}`;
|
|
457
466
|
});
|
|
458
467
|
const parkedThreads = [...runtime.threads.values()].filter((thread) => thread.state === "parked");
|
|
459
468
|
const parkedLines = parkedThreads.map((thread) => {
|
|
469
|
+
const run = monitor.findRun(thread.id);
|
|
470
|
+
const owner = run?.managedWorkflow ? `${run.agent} workflow` : run?.agent ?? thread.agentName;
|
|
471
|
+
const retainedStage = run?.managedWorkflow && thread.agentName !== run.agent
|
|
472
|
+
? ` · retained stage ${thread.agentName}`
|
|
473
|
+
: "";
|
|
460
474
|
const relations = [
|
|
461
475
|
thread.forkedFromRunId !== undefined ? `forked from #${thread.forkedFromRunId}` : undefined,
|
|
462
476
|
thread.forkChildRunIds.length > 0 ? `forks ${thread.forkChildRunIds.map((id) => `#${id}`).join(",")}` : undefined,
|
|
463
477
|
].filter(Boolean);
|
|
464
478
|
const relation = relations.length > 0 ? ` · ${relations.join(" · ")}` : "";
|
|
465
479
|
const isolation = thread.isolation === "worktree" ? ` · worktree ${thread.worktree?.state ?? "active"}` : "";
|
|
466
|
-
return `- #${thread.id} ${
|
|
480
|
+
return `- #${thread.id} ${owner} · ${run?.label ?? runLabel(thread.task)} · parked${thread.sessionDir ? " · context retained" : " · not started"}${retainedStage}${isolation}${relation}`;
|
|
467
481
|
});
|
|
468
482
|
const completed = [...runtime.settledRuns.entries()].slice(-5);
|
|
469
483
|
const completedLines = completed.map(([id, result]) => {
|
package/src/widget.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
5
5
|
import {
|
|
6
|
+
continuationLabel,
|
|
6
7
|
formatElapsed,
|
|
7
8
|
formatTaskSummary,
|
|
8
9
|
isRunActiveStatus,
|
|
@@ -25,10 +26,22 @@ function compactLine(left: string, right: string, width: number): string {
|
|
|
25
26
|
return `${truncateToWidth(left, leftWidth, "…")}${separator}${right}`;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
/** Keep continuation semantics intact while independently compacting the task. */
|
|
30
|
+
function formatContinuationTask(label: string, task: string, width: number): string {
|
|
31
|
+
if (width <= 0) return "";
|
|
32
|
+
const labelWidth = visibleWidth(label);
|
|
33
|
+
if (labelWidth >= width) return truncateToWidth(label, width, "");
|
|
34
|
+
const separator = " · ";
|
|
35
|
+
const taskWidth = width - labelWidth - visibleWidth(separator);
|
|
36
|
+
if (taskWidth <= 0) return label;
|
|
37
|
+
const summary = formatTaskSummary(task, taskWidth);
|
|
38
|
+
return summary ? `${label}${separator}${summary}` : label;
|
|
39
|
+
}
|
|
40
|
+
|
|
28
41
|
/** One compact primary line per genuinely active run, plus an optional indented
|
|
29
|
-
* activity line. The primary line reserves
|
|
30
|
-
* width before truncating the task. Settled and parked threads never
|
|
31
|
-
* elapsed time cannot keep ticking beside a terminal status. */
|
|
42
|
+
* activity line. The primary line reserves stage model/thinking when present and
|
|
43
|
+
* elapsed width before truncating the task. Settled and parked threads never
|
|
44
|
+
* appear, so elapsed time cannot keep ticking beside a terminal status. */
|
|
32
45
|
function runPrimaryLine(
|
|
33
46
|
run: RunView,
|
|
34
47
|
theme: Theme,
|
|
@@ -38,22 +51,31 @@ function runPrimaryLine(
|
|
|
38
51
|
): string {
|
|
39
52
|
const dim = (text: string): string => theme.fg("dim", text);
|
|
40
53
|
const icon = statusIcon(run.status, theme);
|
|
41
|
-
const
|
|
54
|
+
const displayName = run.managedWorkflow ? `${run.agent} workflow` : run.agent;
|
|
55
|
+
const name = theme.fg("accent", theme.bold(displayName));
|
|
42
56
|
const identity = `${prefix}${icon} ${name}`;
|
|
43
57
|
const elapsed = formatElapsed(run, now);
|
|
44
58
|
// Render only the resolved model id plus thinking level. Provider auth and
|
|
45
59
|
// other configuration never enter monitor state or this line.
|
|
46
60
|
const modelId = run.model?.split("/").at(-1);
|
|
47
|
-
const modelSource =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
const modelSource = run.managedWorkflow
|
|
62
|
+
? ""
|
|
63
|
+
: formatTaskSummary(
|
|
64
|
+
modelId ? `${modelId}${run.thinking ? `/${run.thinking}` : ""}` : run.thinking ? `thinking:${run.thinking}` : "",
|
|
65
|
+
64,
|
|
66
|
+
false,
|
|
67
|
+
);
|
|
52
68
|
// A chain child shows its role in the chain plus a task-derived label; the
|
|
53
69
|
// templated fix brief itself would only repeat the parent review's content.
|
|
70
|
+
const continuation = run.parentRunId === undefined
|
|
71
|
+
? continuationLabel(run.continuationKind, run.forkedFromRunId)
|
|
72
|
+
: undefined;
|
|
54
73
|
const taskSource = run.parentRunId !== undefined
|
|
55
74
|
? [run.relationLabel, run.label].filter((part): part is string => Boolean(part)).join(" · ")
|
|
56
75
|
: formatTaskSummary(run.task, 64);
|
|
76
|
+
const taskDesiredSource = [continuation, taskSource]
|
|
77
|
+
.filter((part): part is string => Boolean(part))
|
|
78
|
+
.join(" · ");
|
|
57
79
|
const primaryPartCount = 2 + (modelSource ? 1 : 0) + (elapsed ? 1 : 0);
|
|
58
80
|
const contentWidth = Math.max(
|
|
59
81
|
0,
|
|
@@ -64,15 +86,40 @@ function runPrimaryLine(
|
|
|
64
86
|
);
|
|
65
87
|
const modelDesired = visibleWidth(modelSource);
|
|
66
88
|
const modelFloor = Math.min(modelDesired, Math.min(12, contentWidth));
|
|
67
|
-
let modelWidth =
|
|
68
|
-
|
|
69
|
-
|
|
89
|
+
let modelWidth = 0;
|
|
90
|
+
if (modelSource) {
|
|
91
|
+
if (continuation) {
|
|
92
|
+
const continuationWidth = visibleWidth(continuation);
|
|
93
|
+
// Preserve the full semantic label and the usual eight-column task tail
|
|
94
|
+
// before giving the remaining space to model/thinking.
|
|
95
|
+
const taskFloor = Math.min(
|
|
96
|
+
visibleWidth(taskDesiredSource),
|
|
97
|
+
continuationWidth +
|
|
98
|
+
(taskSource ? visibleWidth(" · ") + Math.min(8, visibleWidth(taskSource)) : 0),
|
|
99
|
+
contentWidth,
|
|
100
|
+
);
|
|
101
|
+
const effectiveModelFloor = Math.min(
|
|
102
|
+
modelFloor,
|
|
103
|
+
Math.max(0, contentWidth - continuationWidth),
|
|
104
|
+
);
|
|
105
|
+
modelWidth = Math.min(
|
|
106
|
+
modelDesired,
|
|
107
|
+
Math.max(effectiveModelFloor, contentWidth - taskFloor),
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
modelWidth = Math.min(modelDesired, Math.max(modelFloor, contentWidth - 8));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
70
113
|
let taskWidth = contentWidth - modelWidth;
|
|
71
|
-
if (visibleWidth(
|
|
72
|
-
modelWidth = Math.min(modelDesired, modelWidth + taskWidth - visibleWidth(
|
|
114
|
+
if (visibleWidth(taskDesiredSource) < taskWidth) {
|
|
115
|
+
modelWidth = Math.min(modelDesired, modelWidth + taskWidth - visibleWidth(taskDesiredSource));
|
|
73
116
|
taskWidth = contentWidth - modelWidth;
|
|
74
117
|
}
|
|
75
|
-
const task = taskWidth
|
|
118
|
+
const task = taskWidth <= 0
|
|
119
|
+
? ""
|
|
120
|
+
: continuation
|
|
121
|
+
? formatContinuationTask(continuation, taskSource, taskWidth)
|
|
122
|
+
: formatTaskSummary(taskSource, taskWidth, run.parentRunId === undefined);
|
|
76
123
|
const modelThinking = modelWidth > 0 ? formatTaskSummary(modelSource, modelWidth, false) : "";
|
|
77
124
|
const primaryLeft = [
|
|
78
125
|
identity,
|
|
@@ -94,9 +141,8 @@ function runActivityLine(run: RunView, theme: Theme, width: number, indent: stri
|
|
|
94
141
|
}
|
|
95
142
|
|
|
96
143
|
/** Render active runs as a tree: main-agent dispatches are roots and managed
|
|
97
|
-
* documenter/reviewer/fix steps nest under the stable parent row.
|
|
98
|
-
*
|
|
99
|
-
* available through subagent_status when a thread must be controlled. */
|
|
144
|
+
* documenter/reviewer/fix steps nest under the stable parent row. Fork labels
|
|
145
|
+
* include their source id; other control ids remain available through status. */
|
|
100
146
|
export function formatActiveRunLines(
|
|
101
147
|
runs: readonly RunView[],
|
|
102
148
|
theme: Theme,
|
|
@@ -134,7 +180,7 @@ export function formatActiveRunLines(
|
|
|
134
180
|
|
|
135
181
|
function hasTickingRun(): boolean {
|
|
136
182
|
return monitor.getRuns().some(
|
|
137
|
-
(run) => isRunActiveStatus(run.status) && run.
|
|
183
|
+
(run) => isRunActiveStatus(run.status) && run.activeSince !== undefined,
|
|
138
184
|
);
|
|
139
185
|
}
|
|
140
186
|
|