pi-subagents 0.22.0 → 0.23.1
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/CHANGELOG.md +29 -0
- package/README.md +13 -10
- package/agents/reviewer.md +2 -2
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +24 -6
- package/src/agents/agent-management.ts +3 -1
- package/src/agents/agents.ts +22 -4
- package/src/extension/doctor.ts +1 -0
- package/src/extension/index.ts +12 -6
- package/src/extension/schemas.ts +1 -1
- package/src/intercom/intercom-bridge.ts +140 -11
- package/src/intercom/result-intercom.ts +8 -3
- package/src/manager-ui/agent-manager.ts +6 -5
- package/src/runs/background/async-execution.ts +22 -7
- package/src/runs/background/async-job-tracker.ts +2 -2
- package/src/runs/background/async-resume.ts +57 -31
- package/src/runs/background/async-status.ts +7 -1
- package/src/runs/background/result-watcher.ts +3 -1
- package/src/runs/background/run-status.ts +22 -19
- package/src/runs/background/stale-run-reconciler.ts +3 -0
- package/src/runs/background/subagent-runner.ts +52 -7
- package/src/runs/foreground/chain-clarify.ts +2 -3
- package/src/runs/foreground/chain-execution.ts +55 -21
- package/src/runs/foreground/execution.ts +9 -5
- package/src/runs/foreground/subagent-executor.ts +157 -23
- package/src/runs/shared/single-output.ts +21 -6
- package/src/shared/settings.ts +19 -0
- package/src/shared/types.ts +26 -1
- package/src/slash/slash-commands.ts +1 -0
- package/src/tui/render.ts +202 -16
- package/src/tui/subagents-status.ts +18 -3
package/src/shared/types.ts
CHANGED
|
@@ -207,6 +207,7 @@ export interface SingleResult {
|
|
|
207
207
|
|
|
208
208
|
export interface Details {
|
|
209
209
|
mode: SubagentRunMode | "management";
|
|
210
|
+
runId?: string;
|
|
210
211
|
context?: "fresh" | "fork";
|
|
211
212
|
results: SingleResult[];
|
|
212
213
|
controlEvents?: ControlEvent[];
|
|
@@ -296,11 +297,15 @@ export interface AsyncStatus {
|
|
|
296
297
|
steps?: Array<{
|
|
297
298
|
agent: string;
|
|
298
299
|
status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
|
|
300
|
+
sessionFile?: string;
|
|
299
301
|
activityState?: ActivityState;
|
|
300
302
|
lastActivityAt?: number;
|
|
301
303
|
currentTool?: string;
|
|
304
|
+
currentToolArgs?: string;
|
|
302
305
|
currentToolStartedAt?: number;
|
|
303
306
|
currentPath?: string;
|
|
307
|
+
recentTools?: Array<{ tool: string; args: string; endMs: number }>;
|
|
308
|
+
recentOutput?: string[];
|
|
304
309
|
turnCount?: number;
|
|
305
310
|
toolCount?: number;
|
|
306
311
|
startedAt?: number;
|
|
@@ -320,6 +325,10 @@ export interface AsyncStatus {
|
|
|
320
325
|
sessionFile?: string;
|
|
321
326
|
}
|
|
322
327
|
|
|
328
|
+
export type AsyncJobStep = NonNullable<AsyncStatus["steps"]>[number] & {
|
|
329
|
+
index?: number;
|
|
330
|
+
};
|
|
331
|
+
|
|
323
332
|
export interface AsyncJobState {
|
|
324
333
|
asyncId: string;
|
|
325
334
|
asyncDir: string;
|
|
@@ -338,7 +347,7 @@ export interface AsyncJobState {
|
|
|
338
347
|
currentStep?: number;
|
|
339
348
|
chainStepCount?: number;
|
|
340
349
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
341
|
-
steps?:
|
|
350
|
+
steps?: AsyncJobStep[];
|
|
342
351
|
stepsTotal?: number;
|
|
343
352
|
runningSteps?: number;
|
|
344
353
|
completedSteps?: number;
|
|
@@ -353,10 +362,26 @@ export interface AsyncJobState {
|
|
|
353
362
|
controlEventCursor?: number;
|
|
354
363
|
}
|
|
355
364
|
|
|
365
|
+
export interface ForegroundResumeChild {
|
|
366
|
+
agent: string;
|
|
367
|
+
index: number;
|
|
368
|
+
sessionFile?: string;
|
|
369
|
+
status: SubagentResultStatus;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export interface ForegroundResumeRun {
|
|
373
|
+
runId: string;
|
|
374
|
+
mode: SubagentRunMode;
|
|
375
|
+
cwd: string;
|
|
376
|
+
updatedAt: number;
|
|
377
|
+
children: ForegroundResumeChild[];
|
|
378
|
+
}
|
|
379
|
+
|
|
356
380
|
export interface SubagentState {
|
|
357
381
|
baseCwd: string;
|
|
358
382
|
currentSessionId: string | null;
|
|
359
383
|
asyncJobs: Map<string, AsyncJobState>;
|
|
384
|
+
foregroundRuns?: Map<string, ForegroundResumeRun>;
|
|
360
385
|
foregroundControls: Map<string, {
|
|
361
386
|
runId: string;
|
|
362
387
|
mode: SubagentRunMode;
|
|
@@ -279,6 +279,7 @@ async function runSlashSubagent(
|
|
|
279
279
|
ctx: ExtensionContext,
|
|
280
280
|
params: SubagentParamsLike,
|
|
281
281
|
): Promise<void> {
|
|
282
|
+
if (ctx.hasUI) ctx.ui.setToolsExpanded(false);
|
|
282
283
|
const requestId = randomUUID();
|
|
283
284
|
const initialDetails = buildSlashInitialResult(requestId, params);
|
|
284
285
|
const initialText = extractSlashMessageText(initialDetails.result.content) || "Running subagent...";
|
package/src/tui/render.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Rendering functions for subagent results
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import * as path from "node:path";
|
|
5
6
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
6
7
|
import { getMarkdownTheme, type ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
7
8
|
import { Container, Markdown, Spacer, Text, visibleWidth, type Component } from "@mariozechner/pi-tui";
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
} from "../shared/types.ts";
|
|
15
16
|
import { formatTokens, formatUsage, formatDuration, formatToolCall, shortenPath } from "../shared/formatters.ts";
|
|
16
17
|
import { getDisplayItems, getLastActivity, getSingleResultOutput } from "../shared/utils.ts";
|
|
18
|
+
import { flatToLogicalStepIndex } from "../runs/background/parallel-groups.ts";
|
|
17
19
|
|
|
18
20
|
type Theme = ExtensionContext["ui"]["theme"];
|
|
19
21
|
|
|
@@ -264,10 +266,10 @@ function formatWidgetAgents(agents: string[]): string {
|
|
|
264
266
|
}
|
|
265
267
|
|
|
266
268
|
function widgetJobName(job: AsyncJobState): string {
|
|
267
|
-
|
|
268
|
-
if (job.mode === "
|
|
269
|
-
if (job.
|
|
270
|
-
if (job.agents?.length) return job.agents
|
|
269
|
+
if (job.mode === "parallel") return "parallel";
|
|
270
|
+
if (job.mode === "chain") return "chain";
|
|
271
|
+
if (job.mode === "single" && job.agents?.length === 1) return job.agents[0]!;
|
|
272
|
+
if (job.agents?.length) return formatWidgetAgents(job.agents);
|
|
271
273
|
return job.mode ?? "subagent";
|
|
272
274
|
}
|
|
273
275
|
|
|
@@ -293,6 +295,7 @@ function widgetActivity(job: AsyncJobState): string {
|
|
|
293
295
|
if (activity && facts.length) return `${activity} · ${facts.join(" · ")}`;
|
|
294
296
|
if (activity) return activity;
|
|
295
297
|
if (facts.length) return facts.join(" · ");
|
|
298
|
+
if (job.status === "running") return "thinking…";
|
|
296
299
|
if (job.status === "queued") return "queued…";
|
|
297
300
|
if (job.status === "paused") return "Paused";
|
|
298
301
|
if (job.status === "failed") return "Failed";
|
|
@@ -308,7 +311,7 @@ function widgetStatusGlyph(job: AsyncJobState, theme: Theme): string {
|
|
|
308
311
|
}
|
|
309
312
|
|
|
310
313
|
function widgetStepGlyph(status: string, theme: Theme): string {
|
|
311
|
-
if (status === "running") return theme.fg("accent",
|
|
314
|
+
if (status === "running") return theme.fg("accent", spinnerFrame());
|
|
312
315
|
if (status === "complete" || status === "completed") return theme.fg("success", "✓");
|
|
313
316
|
if (status === "failed") return theme.fg("error", "✗");
|
|
314
317
|
if (status === "paused") return theme.fg("warning", "■");
|
|
@@ -337,14 +340,62 @@ function widgetStepActivity(step: NonNullable<AsyncJobState["steps"]>[number]):
|
|
|
337
340
|
return facts.join(" · ");
|
|
338
341
|
}
|
|
339
342
|
|
|
343
|
+
function widgetAggregateStepStatus(steps: NonNullable<AsyncJobState["steps"]>): string {
|
|
344
|
+
if (steps.some((step) => step.status === "running")) return "running";
|
|
345
|
+
if (steps.some((step) => step.status === "failed")) return "failed";
|
|
346
|
+
if (steps.some((step) => step.status === "paused")) return "paused";
|
|
347
|
+
if (steps.length > 0 && steps.every((step) => step.status === "complete" || step.status === "completed")) return "complete";
|
|
348
|
+
return "pending";
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function widgetParallelOutcome(steps: NonNullable<AsyncJobState["steps"]>, total: number): string {
|
|
352
|
+
const running = steps.filter((step) => step.status === "running").length;
|
|
353
|
+
const done = steps.filter((step) => step.status === "complete" || step.status === "completed").length;
|
|
354
|
+
const failed = steps.filter((step) => step.status === "failed").length;
|
|
355
|
+
const paused = steps.filter((step) => step.status === "paused").length;
|
|
356
|
+
const parts = [`${done}/${total} done`];
|
|
357
|
+
if (running > 0) parts.unshift(formatAgentRunningLabel(running));
|
|
358
|
+
if (failed > 0) parts.push(`${failed} failed`);
|
|
359
|
+
if (paused > 0) parts.push(`${paused} paused`);
|
|
360
|
+
return parts.join(" · ");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function widgetChainDetails(job: AsyncJobState, theme: Theme, expanded = false, width = getTermWidth()): string[] {
|
|
364
|
+
if (!job.steps?.length) return [];
|
|
365
|
+
const total = job.chainStepCount ?? job.steps.length;
|
|
366
|
+
const groups = job.parallelGroups ?? [];
|
|
367
|
+
const lines: string[] = [];
|
|
368
|
+
let flatIndex = 0;
|
|
369
|
+
for (let stepIndex = 0; stepIndex < total; stepIndex++) {
|
|
370
|
+
const group = groups.find((candidate) => candidate.stepIndex === stepIndex);
|
|
371
|
+
if (group) {
|
|
372
|
+
const steps = job.steps.slice(group.start, group.start + group.count);
|
|
373
|
+
const status = widgetAggregateStepStatus(steps);
|
|
374
|
+
lines.push(` ${widgetStepGlyph(status, theme)} Step ${stepIndex + 1}/${total}: ${themeBold(theme, "parallel group")} ${theme.fg("dim", "·")} ${theme.fg("dim", widgetParallelOutcome(steps, group.count))}`);
|
|
375
|
+
flatIndex = Math.max(flatIndex, group.start + group.count);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const step = job.steps[flatIndex];
|
|
379
|
+
if (!step) {
|
|
380
|
+
lines.push(` ${theme.fg("dim", `◦ Step ${stepIndex + 1}/${total}: pending`)}`);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
lines.push(...foregroundStyleWidgetStepLines(job, theme, step, "Step", stepIndex + 1, total, expanded, width));
|
|
384
|
+
flatIndex++;
|
|
385
|
+
}
|
|
386
|
+
return lines;
|
|
387
|
+
}
|
|
388
|
+
|
|
340
389
|
function widgetParallelAgentDetails(job: AsyncJobState, theme: Theme): string[] {
|
|
341
|
-
if (!job.
|
|
390
|
+
if (!job.steps?.length) return [];
|
|
342
391
|
if (job.mode !== "parallel" && job.mode !== "chain") return [];
|
|
392
|
+
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme);
|
|
343
393
|
const total = job.stepsTotal ?? job.steps.length;
|
|
344
394
|
return job.steps.map((step, index) => {
|
|
345
395
|
const marker = index === job.steps!.length - 1 ? "└" : "├";
|
|
346
396
|
const activity = widgetStepActivity(step);
|
|
347
|
-
|
|
397
|
+
const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
|
|
398
|
+
return ` ${theme.fg("dim", `${marker} ${widgetStepGlyph(step.status, theme)} ${itemTitle} ${index + 1}/${total}: ${step.agent} · ${widgetStepStatus(step.status, theme)}${activity ? ` · ${activity}` : ""}`)}`;
|
|
348
399
|
});
|
|
349
400
|
}
|
|
350
401
|
|
|
@@ -513,7 +564,7 @@ function widgetStats(job: AsyncJobState, theme: Theme): string {
|
|
|
513
564
|
const running = job.runningSteps ?? (job.status === "running" ? 1 : 0);
|
|
514
565
|
const done = job.completedSteps ?? (job.status === "complete" ? stepsTotal : 0);
|
|
515
566
|
if (job.mode === "parallel") {
|
|
516
|
-
if (job.status === "running") parts.push(formatAgentRunningLabel(running));
|
|
567
|
+
if (job.status === "running" && running > 0) parts.push(formatAgentRunningLabel(running));
|
|
517
568
|
if (stepsTotal > 0) parts.push(`${done}/${stepsTotal} done`);
|
|
518
569
|
} else {
|
|
519
570
|
const activeGroup = job.currentStep !== undefined
|
|
@@ -521,24 +572,159 @@ function widgetStats(job: AsyncJobState, theme: Theme): string {
|
|
|
521
572
|
: job.parallelGroups?.find((group) => group.start === 0);
|
|
522
573
|
const logicalStep = activeGroup?.stepIndex ?? job.currentStep ?? 0;
|
|
523
574
|
const total = job.chainStepCount ?? stepsTotal;
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
parts.push(`step ${logicalStep + 1}/${total} · parallel group: ${groupProgress}`);
|
|
575
|
+
const groupParts = [`${done}/${stepsTotal} done`];
|
|
576
|
+
if (job.status === "running" && running > 0) groupParts.unshift(formatAgentRunningLabel(running));
|
|
577
|
+
parts.push(`step ${logicalStep + 1}/${total} · parallel group: ${groupParts.join(" · ")}`);
|
|
528
578
|
}
|
|
529
579
|
} else if (job.currentStep !== undefined) {
|
|
530
|
-
|
|
580
|
+
if (job.mode === "chain" && job.parallelGroups?.length) {
|
|
581
|
+
const total = job.chainStepCount ?? stepsTotal;
|
|
582
|
+
parts.push(`step ${flatToLogicalStepIndex(job.currentStep, total, job.parallelGroups) + 1}/${total}`);
|
|
583
|
+
} else {
|
|
584
|
+
parts.push(`step ${job.currentStep + 1}/${stepsTotal}`);
|
|
585
|
+
}
|
|
531
586
|
} else if (stepsTotal > 1) {
|
|
532
587
|
parts.push(`steps ${stepsTotal}`);
|
|
533
588
|
}
|
|
589
|
+
if (job.toolCount !== undefined) parts.push(formatToolUseStat(job.toolCount));
|
|
534
590
|
if (job.totalTokens?.total) parts.push(formatTokenStat(job.totalTokens.total));
|
|
535
591
|
const endTime = job.status === "complete" || job.status === "failed" || job.status === "paused" ? (job.updatedAt ?? Date.now()) : Date.now();
|
|
536
592
|
if (job.startedAt) parts.push(formatDuration(Math.max(0, endTime - job.startedAt)));
|
|
537
593
|
return statJoin(theme, parts);
|
|
538
594
|
}
|
|
539
595
|
|
|
540
|
-
|
|
596
|
+
function widgetStepStats(theme: Theme, step: NonNullable<AsyncJobState["steps"]>[number]): string {
|
|
597
|
+
return statJoin(theme, [
|
|
598
|
+
step.turnCount !== undefined ? `${step.turnCount} turns` : "",
|
|
599
|
+
step.toolCount !== undefined ? formatToolUseStat(step.toolCount) : "",
|
|
600
|
+
step.tokens?.total ? formatTokenStat(step.tokens.total) : "",
|
|
601
|
+
step.durationMs !== undefined ? formatDuration(step.durationMs) : "",
|
|
602
|
+
]);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function widgetStepActivityLine(step: NonNullable<AsyncJobState["steps"]>[number], width: number, expanded: boolean): string {
|
|
606
|
+
const toolLine = formatCurrentToolLine(step, width, expanded);
|
|
607
|
+
if (toolLine) return toolLine;
|
|
608
|
+
const activity = formatActivityLabel(step.lastActivityAt, step.activityState);
|
|
609
|
+
if (activity) return activity;
|
|
610
|
+
if (step.status === "running") return "thinking…";
|
|
611
|
+
return "";
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function widgetOutputPath(job: AsyncJobState, step: NonNullable<AsyncJobState["steps"]>[number]): string | undefined {
|
|
615
|
+
if (typeof step.index !== "number") return undefined;
|
|
616
|
+
return path.join(job.asyncDir, `output-${step.index}.log`);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function foregroundStyleWidgetStepLines(
|
|
620
|
+
job: AsyncJobState,
|
|
621
|
+
theme: Theme,
|
|
622
|
+
step: NonNullable<AsyncJobState["steps"]>[number],
|
|
623
|
+
itemTitle: "Agent" | "Step",
|
|
624
|
+
index: number,
|
|
625
|
+
total: number,
|
|
626
|
+
expanded: boolean,
|
|
627
|
+
width: number,
|
|
628
|
+
): string[] {
|
|
629
|
+
const status = widgetStepStatus(step.status, theme);
|
|
630
|
+
const stats = widgetStepStats(theme, step);
|
|
631
|
+
const lines = [` ${widgetStepGlyph(step.status, theme)} ${itemTitle} ${index}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`];
|
|
632
|
+
const activity = widgetStepActivityLine(step, width, expanded);
|
|
633
|
+
if (activity) lines.push(` ${theme.fg("dim", `⎿ ${activity}`)}`);
|
|
634
|
+
if (step.status === "running") {
|
|
635
|
+
if (!expanded) lines.push(` ${theme.fg("accent", "Press Ctrl+O for live detail")}`);
|
|
636
|
+
const output = widgetOutputPath(job, step);
|
|
637
|
+
if (output) lines.push(` ${theme.fg("dim", `output: ${shortenPath(output)}`)}`);
|
|
638
|
+
if (expanded) {
|
|
639
|
+
const liveStatus = buildLiveStatusLine(step);
|
|
640
|
+
if (liveStatus && liveStatus !== activity) lines.push(` ${theme.fg("accent", liveStatus)}`);
|
|
641
|
+
for (const tool of step.recentTools?.slice(-3) ?? []) {
|
|
642
|
+
const maxArgsLen = Math.max(40, width - 30);
|
|
643
|
+
const argsPreview = tool.args.length <= maxArgsLen ? tool.args : `${tool.args.slice(0, maxArgsLen)}...`;
|
|
644
|
+
lines.push(` ${theme.fg("dim", `${tool.tool}${argsPreview ? `: ${argsPreview}` : ""}`)}`);
|
|
645
|
+
}
|
|
646
|
+
for (const line of step.recentOutput?.slice(-5) ?? []) {
|
|
647
|
+
lines.push(` ${theme.fg("dim", line)}`);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return lines;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded: boolean, width: number): string[] {
|
|
655
|
+
if (!job.steps?.length) return [` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`];
|
|
656
|
+
if (job.mode !== "parallel" && job.mode !== "chain") return [` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`];
|
|
657
|
+
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width);
|
|
658
|
+
const total = job.stepsTotal ?? job.steps.length;
|
|
659
|
+
const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
|
|
660
|
+
const lines: string[] = [];
|
|
661
|
+
for (const [index, step] of job.steps.entries()) {
|
|
662
|
+
lines.push(...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index + 1, total, expanded, width));
|
|
663
|
+
}
|
|
664
|
+
return lines;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function buildSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, expanded: boolean): string[] {
|
|
668
|
+
const stats = widgetStats(job, theme);
|
|
669
|
+
const count = job.mode === "chain" ? job.chainStepCount : job.stepsTotal ?? job.agents?.length ?? job.steps?.length;
|
|
670
|
+
const mode = widgetJobName(job);
|
|
671
|
+
const title = `async subagent ${mode}${count && count > 1 ? ` (${count})` : ""}`;
|
|
672
|
+
return [
|
|
673
|
+
`${theme.fg("toolTitle", themeBold(theme, title))} ${theme.fg("dim", "· background · /subagents-status")}`,
|
|
674
|
+
`${widgetStatusGlyph(job, theme)} ${themeBold(theme, mode)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
675
|
+
...foregroundStyleWidgetDetails(job, theme, expanded, width),
|
|
676
|
+
].map((line) => truncLine(line, width));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number): string[] {
|
|
680
|
+
const fullLines = buildSingleWidgetLines(job, theme, width, false);
|
|
681
|
+
if (fullLines.length <= 10 || !job.steps?.length || (job.mode !== "parallel" && !job.activeParallelGroup)) return fullLines;
|
|
682
|
+
|
|
683
|
+
const total = job.stepsTotal ?? job.steps.length;
|
|
684
|
+
const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
|
|
685
|
+
const lines = fullLines.slice(0, 2);
|
|
686
|
+
for (const [index, step] of job.steps.entries()) {
|
|
687
|
+
const status = widgetStepStatus(step.status, theme);
|
|
688
|
+
const activity = widgetStepActivityLine(step, width, false);
|
|
689
|
+
const stepStats = widgetStepStats(theme, step);
|
|
690
|
+
const activitySuffix = activity ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", activity)}` : "";
|
|
691
|
+
lines.push(` ${widgetStepGlyph(step.status, theme)} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
|
|
692
|
+
}
|
|
693
|
+
if (job.steps.some((step) => step.status === "running")) lines.push(theme.fg("accent", " Press Ctrl+O for live detail · /subagents-status for output paths"));
|
|
694
|
+
return lines.map((line) => truncLine(line, width));
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function fitWidgetLineBudget(lines: string[], theme: Theme, width: number, expanded: boolean): string[] {
|
|
698
|
+
const rows = process.stdout.rows || 30;
|
|
699
|
+
const budget = expanded
|
|
700
|
+
? Math.max(12, Math.min(24, Math.floor(rows * 0.55)))
|
|
701
|
+
: Math.max(10, Math.min(14, Math.floor(rows * 0.35)));
|
|
702
|
+
if (lines.length <= budget) return lines;
|
|
703
|
+
const visibleLines = Math.max(1, budget - 1);
|
|
704
|
+
const hiddenCount = lines.length - visibleLines;
|
|
705
|
+
const hint = expanded
|
|
706
|
+
? `… ${hiddenCount} live-detail lines hidden · /subagents-status for full detail`
|
|
707
|
+
: `… ${hiddenCount} lines hidden · Ctrl+O expands · /subagents-status for full detail`;
|
|
708
|
+
return [...lines.slice(0, visibleLines), truncLine(theme.fg("dim", hint), width)];
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function buildWidgetComponent(jobs: AsyncJobState[], expanded: boolean): (_tui: unknown, theme: Theme) => Component {
|
|
712
|
+
return (_tui, theme) => {
|
|
713
|
+
const width = getTermWidth();
|
|
714
|
+
const lines = expanded
|
|
715
|
+
? buildWidgetLines(jobs, theme, width, true)
|
|
716
|
+
: jobs.length === 1
|
|
717
|
+
? compactSingleWidgetLines(jobs[0]!, theme, width)
|
|
718
|
+
: buildWidgetLines(jobs, theme, width, false);
|
|
719
|
+
const container = new Container();
|
|
720
|
+
for (const line of fitWidgetLineBudget(lines, theme, width, expanded)) container.addChild(new Text(line, 1, 0));
|
|
721
|
+
return container;
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = getTermWidth(), expanded = false): string[] {
|
|
541
726
|
if (jobs.length === 0) return [];
|
|
727
|
+
if (jobs.length === 1) return buildSingleWidgetLines(jobs[0]!, theme, width, expanded);
|
|
542
728
|
const running = jobs.filter((job) => job.status === "running");
|
|
543
729
|
const queued = jobs.filter((job) => job.status === "queued");
|
|
544
730
|
const finished = jobs.filter((job) => job.status !== "running" && job.status !== "queued");
|
|
@@ -608,7 +794,7 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
608
794
|
function refreshAnimatedWidget(): void {
|
|
609
795
|
try {
|
|
610
796
|
if (!latestWidgetCtx?.hasUI || latestWidgetJobs.length === 0) return;
|
|
611
|
-
latestWidgetCtx.ui.setWidget(WIDGET_KEY,
|
|
797
|
+
latestWidgetCtx.ui.setWidget(WIDGET_KEY, buildWidgetComponent(latestWidgetJobs, latestWidgetCtx.ui.getToolsExpanded?.() ?? false));
|
|
612
798
|
latestWidgetCtx.ui.requestRender?.();
|
|
613
799
|
} catch (error) {
|
|
614
800
|
if (!isStaleExtensionContextError(error)) throw error;
|
|
@@ -662,7 +848,7 @@ export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void
|
|
|
662
848
|
latestWidgetCtx = ctx;
|
|
663
849
|
latestWidgetJobs = [...jobs];
|
|
664
850
|
|
|
665
|
-
ctx.ui.setWidget(WIDGET_KEY,
|
|
851
|
+
ctx.ui.setWidget(WIDGET_KEY, buildWidgetComponent(jobs, ctx.ui.getToolsExpanded?.() ?? false));
|
|
666
852
|
if (hasAnimatedWidgetJobs(jobs)) ensureWidgetAnimation();
|
|
667
853
|
else stopWidgetAnimation();
|
|
668
854
|
}
|
|
@@ -62,11 +62,20 @@ function stepGlyph(theme: Theme, status: string): string {
|
|
|
62
62
|
return theme.fg("dim", "◦");
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
function runGlyph(theme: Theme, status: AsyncRunSummary["state"]): string {
|
|
66
|
+
if (status === "running") return theme.fg("accent", "▶");
|
|
67
|
+
if (status === "queued") return theme.fg("dim", "◦");
|
|
68
|
+
if (status === "complete") return theme.fg("success", "✓");
|
|
69
|
+
if (status === "paused") return theme.fg("warning", "■");
|
|
70
|
+
return theme.fg("error", "✗");
|
|
71
|
+
}
|
|
72
|
+
|
|
65
73
|
function runLabel(theme: Theme, run: AsyncRunSummary, selected: boolean): string {
|
|
66
74
|
const prefix = selected ? theme.fg("accent", ">") : " ";
|
|
67
75
|
const stepLabel = formatAsyncRunProgressLabel(run);
|
|
68
76
|
const cwd = shortenPath(run.cwd ?? run.asyncDir);
|
|
69
|
-
|
|
77
|
+
const mode = ((theme as { bold?: (value: string) => string }).bold?.(run.mode)) ?? run.mode;
|
|
78
|
+
return `${prefix} ${runGlyph(theme, run.state)} ${mode} · ${stepLabel} · ${run.id.slice(0, 8)} · ${cwd}`;
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
function selectedIndex(rows: StatusRow[], cursor: number): number {
|
|
@@ -332,7 +341,13 @@ export class SubagentsStatusComponent implements Component {
|
|
|
332
341
|
if (run.sessionFile) {
|
|
333
342
|
lines.push(row(`session: ${truncateToWidth(shortenPath(run.sessionFile), innerW - 9)}`, width, this.theme));
|
|
334
343
|
}
|
|
335
|
-
|
|
344
|
+
if (run.mode === "chain" && (run.chainStepCount !== undefined || run.parallelGroups?.length)) {
|
|
345
|
+
lines.push(...this.renderChainProgressRows(run, width, innerW));
|
|
346
|
+
} else if (run.mode === "parallel") {
|
|
347
|
+
lines.push(...this.renderAgentRows(run, width, innerW));
|
|
348
|
+
} else {
|
|
349
|
+
lines.push(...this.renderStepRows(run, width, innerW));
|
|
350
|
+
}
|
|
336
351
|
return lines;
|
|
337
352
|
}
|
|
338
353
|
|
|
@@ -438,7 +453,7 @@ export class SubagentsStatusComponent implements Component {
|
|
|
438
453
|
: undefined;
|
|
439
454
|
|
|
440
455
|
const body: string[] = [];
|
|
441
|
-
body.push(...detailRows(`${
|
|
456
|
+
body.push(...detailRows(`${runGlyph(this.theme, run.state)} ${run.mode} · ${stepLabel} · ${statusColor(this.theme, run.state)} · ${duration}`, width, innerW, this.theme));
|
|
442
457
|
if (activity) body.push(...detailRows(activity, width, innerW, this.theme));
|
|
443
458
|
body.push(row("", width, this.theme));
|
|
444
459
|
if (run.mode === "chain" && (run.chainStepCount !== undefined || run.parallelGroups?.length)) {
|