@selesai/code 0.13.2 → 0.13.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/CHANGELOG.md +20 -0
- package/dist/defaults/models.json +85 -13
- package/dist/extensions/cost-reconcile.test.ts +200 -4
- package/dist/extensions/cost-reconcile.ts +88 -102
- package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
- package/dist/extensions/pi-intercom/README.md +4 -5
- package/dist/extensions/pi-intercom/config.test.ts +3 -31
- package/dist/extensions/pi-intercom/config.ts +0 -15
- package/dist/extensions/pi-intercom/index.ts +9 -46
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +49 -57
- package/dist/extensions/pi-intercom/package.json +1 -1
- package/dist/extensions/pi-intercom/reply-tracker.test.ts +20 -0
- package/dist/extensions/pi-intercom/reply-tracker.ts +8 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +27 -0
- package/dist/extensions/pi-subagents/docs/tool-reference.md +4 -1
- package/dist/extensions/pi-subagents/docs/workflows.md +2 -2
- package/dist/extensions/pi-subagents/package-lock.json +2 -2
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +48 -243
- package/dist/extensions/pi-subagents/skills/council-mode/references/pass-contracts.md +150 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +87 -37
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +29 -233
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +49 -8
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +13 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +34 -27
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/review-and-validation.md +73 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +157 -28
- package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -0
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +1 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +1 -0
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +45 -2
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +3 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +13 -2
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +5 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +10 -2
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +11 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +98 -1
- package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +138 -4
- package/dist/extensions/pi-subagents/src/runs/shared/background-process-options.ts +9 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-grant.ts +2 -5
- package/dist/extensions/pi-subagents/src/runs/shared/mutation-evidence.ts +52 -3
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +47 -1
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +45 -18
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +21 -2
- package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +15 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +34 -1
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +11 -3
- package/dist/extensions/pi-subagents/src/tui/render-helpers.ts +31 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +597 -112
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +40 -1
- package/dist/extensions/pi-subagents/src/workflows/host-command.ts +6 -1
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +53 -2
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +55 -6
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +111 -1
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +206 -38
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +522 -31
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +123 -0
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +48 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +57 -1
- package/dist/extensions/pi-subagents/test/unit/background-process-options.test.ts +17 -0
- package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +44 -4
- package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +91 -0
- package/dist/extensions/pi-subagents/test/unit/host-command.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +0 -1
- package/dist/extensions/pi-subagents/test/unit/mcp-direct-tool-grant.test.ts +20 -3
- package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +93 -17
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +103 -26
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +58 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +22 -2
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +21 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +13 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +54 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +48 -1
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +21 -6
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +11 -0
- package/dist/extensions/pi-web-agent/CHANGELOG.md +410 -0
- package/dist/extensions/pi-web-agent/README.md +131 -0
- package/dist/extensions/pi-web-agent/package.json +4 -2
- package/dist/extensions/pi-web-agent/src/backends/config.ts +62 -5
- package/dist/extensions/pi-web-agent/src/backends/doctor.ts +136 -0
- package/dist/extensions/pi-web-agent/src/backends/factory.ts +96 -6
- package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +183 -47
- package/dist/extensions/pi-web-agent/src/extension.ts +62 -25
- package/dist/extensions/pi-web-agent/src/extract/readability.ts +19 -11
- package/dist/extensions/pi-web-agent/src/orchestration/candidate-selector.ts +5 -4
- package/dist/extensions/pi-web-agent/src/orchestration/direct-url.ts +2 -25
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-quality.ts +5 -2
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-ranker.ts +2 -0
- package/dist/extensions/pi-web-agent/src/orchestration/research-orchestrator.ts +72 -7
- package/dist/extensions/pi-web-agent/src/orchestration/research-types.ts +8 -1
- package/dist/extensions/pi-web-agent/src/orchestration/research-worker.ts +28 -3
- package/dist/extensions/pi-web-agent/src/orchestration/source-profile.ts +4 -0
- package/dist/extensions/pi-web-agent/src/orchestration/url.ts +35 -0
- package/dist/extensions/pi-web-agent/src/presentation/explore-presentation.ts +18 -6
- package/dist/extensions/pi-web-agent/src/presentation/search-presentation.ts +14 -2
- package/dist/extensions/pi-web-agent/src/readers/github-reader.ts +150 -0
- package/dist/extensions/pi-web-agent/src/readers/limits.ts +3 -0
- package/dist/extensions/pi-web-agent/src/readers/pdf-reader.ts +87 -0
- package/dist/extensions/pi-web-agent/src/readers/resolver.ts +25 -0
- package/dist/extensions/pi-web-agent/src/readers/types.ts +11 -0
- package/dist/extensions/pi-web-agent/src/readers/youtube-reader.ts +79 -0
- package/dist/extensions/pi-web-agent/src/search/duckduckgo.ts +32 -5
- package/dist/extensions/pi-web-agent/src/search/exa.ts +109 -0
- package/dist/extensions/pi-web-agent/src/search/fanout.ts +154 -0
- package/dist/extensions/pi-web-agent/src/search/tavily.ts +113 -0
- package/dist/extensions/pi-web-agent/src/search/youcom.ts +109 -0
- package/dist/extensions/pi-web-agent/src/tools/web-search.ts +22 -9
- package/dist/extensions/pi-web-agent/src/types.ts +19 -4
- package/dist/extensions/tokenin-onboarding.ts +302 -0
- package/package.json +1 -1
|
@@ -549,11 +549,146 @@ describe("subagent async widget rendering", () => {
|
|
|
549
549
|
}], theme, 120);
|
|
550
550
|
|
|
551
551
|
const text = lines.join("\n");
|
|
552
|
-
assert.match(text, /
|
|
553
|
-
assert.match(text, /
|
|
552
|
+
assert.match(text, /Review auth — private auth task \(reviewer\) · running/);
|
|
553
|
+
assert.match(text, /Review billing — private billing task \(reviewer\) · running/);
|
|
554
|
+
assert.doesNotMatch(text, /Agent \d+\/2:/);
|
|
554
555
|
assert.equal(compactTaskText(" [prompt redacted] ", "Review auth"), "Review auth");
|
|
555
556
|
});
|
|
556
557
|
|
|
558
|
+
it("renders a named top-level parallel group card with nested readable agent rows", () => {
|
|
559
|
+
const now = Date.now();
|
|
560
|
+
const text = buildWidgetLines([{
|
|
561
|
+
asyncId: "named-parallel-group",
|
|
562
|
+
asyncDir: "/tmp/named-parallel-group",
|
|
563
|
+
status: "running",
|
|
564
|
+
mode: "parallel",
|
|
565
|
+
agents: ["scout", "reviewer", "worker"],
|
|
566
|
+
activeParallelGroup: true,
|
|
567
|
+
runningSteps: 2,
|
|
568
|
+
completedSteps: 0,
|
|
569
|
+
stepsTotal: 3,
|
|
570
|
+
updatedAt: now,
|
|
571
|
+
steps: [
|
|
572
|
+
{
|
|
573
|
+
index: 0,
|
|
574
|
+
agent: "scout",
|
|
575
|
+
label: "Gather context",
|
|
576
|
+
description: "Read source files",
|
|
577
|
+
status: "running",
|
|
578
|
+
currentTool: "read",
|
|
579
|
+
currentToolStartedAt: now - 2_000,
|
|
580
|
+
currentPath: "src/tui/render.ts",
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
index: 1,
|
|
584
|
+
agent: "reviewer",
|
|
585
|
+
sessionName: "reviewer: Review diff",
|
|
586
|
+
status: "running",
|
|
587
|
+
currentTool: "grep",
|
|
588
|
+
currentToolStartedAt: now - 1_000,
|
|
589
|
+
currentPath: "test/integration/render-widget.test.ts",
|
|
590
|
+
phase: "review",
|
|
591
|
+
workflowKey: "review",
|
|
592
|
+
outputName: "review.md",
|
|
593
|
+
children: [{
|
|
594
|
+
id: "nested-reviewer",
|
|
595
|
+
parentRunId: "named-parallel-group",
|
|
596
|
+
parentStepIndex: 1,
|
|
597
|
+
depth: 1,
|
|
598
|
+
path: [{ runId: "named-parallel-group", stepIndex: 1 }],
|
|
599
|
+
state: "running",
|
|
600
|
+
agent: "nested-reviewer",
|
|
601
|
+
lastUpdate: now,
|
|
602
|
+
}],
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
index: 2,
|
|
606
|
+
agent: "worker",
|
|
607
|
+
label: "Apply fixes",
|
|
608
|
+
status: "pending",
|
|
609
|
+
phase: "implementation",
|
|
610
|
+
workflowKey: "worker",
|
|
611
|
+
outputName: "fix.md",
|
|
612
|
+
},
|
|
613
|
+
],
|
|
614
|
+
}], theme, 220, true).join("\n");
|
|
615
|
+
|
|
616
|
+
assert.equal((text.match(/parallel group/g) ?? []).length, 1);
|
|
617
|
+
assert.match(text, /parallel · (?:2 agents running|2 running) · 0\/3 done/);
|
|
618
|
+
assert.match(text, /Gather context/);
|
|
619
|
+
assert.match(text, /Review diff/);
|
|
620
|
+
assert.doesNotMatch(text, /reviewer: Review diff/);
|
|
621
|
+
assert.match(text, /Apply fixes/);
|
|
622
|
+
assert.doesNotMatch(text, /Agent \d+\/3:/);
|
|
623
|
+
assert.match(text, /nested-reviewer/);
|
|
624
|
+
assert.match(text, /src\/tui\/render\.ts/);
|
|
625
|
+
assert.match(text, /out:review\.md/);
|
|
626
|
+
assert.match(text, /pending/);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it("groups active parallel-card rows before completed and pending rows", () => {
|
|
630
|
+
const text = buildWidgetLines([{
|
|
631
|
+
asyncId: "active-first-group",
|
|
632
|
+
asyncDir: "/tmp/active-first-group",
|
|
633
|
+
status: "running",
|
|
634
|
+
mode: "parallel",
|
|
635
|
+
agents: ["scout", "worker", "reviewer", "auditor"],
|
|
636
|
+
activeParallelGroup: true,
|
|
637
|
+
runningSteps: 2,
|
|
638
|
+
completedSteps: 1,
|
|
639
|
+
stepsTotal: 4,
|
|
640
|
+
steps: [
|
|
641
|
+
{ index: 0, agent: "scout", label: "done-scout", status: "complete" },
|
|
642
|
+
{ index: 1, agent: "worker", label: "pending-worker", status: "pending" },
|
|
643
|
+
{ index: 2, agent: "reviewer", label: "active-reviewer", status: "running" },
|
|
644
|
+
{ index: 3, agent: "auditor", label: "active-auditor", status: "running" },
|
|
645
|
+
],
|
|
646
|
+
}], theme, 220, true).join("\n");
|
|
647
|
+
const rowIndex = (label: string): number => text.split("\n").findIndex((line) => line.includes(label) && !line.includes("task:"));
|
|
648
|
+
const done = rowIndex("done-scout");
|
|
649
|
+
const pending = rowIndex("pending-worker");
|
|
650
|
+
const reviewer = rowIndex("active-reviewer");
|
|
651
|
+
const auditor = rowIndex("active-auditor");
|
|
652
|
+
|
|
653
|
+
assert.ok(reviewer >= 0 && auditor >= 0 && done >= 0 && pending >= 0);
|
|
654
|
+
assert.ok(reviewer < auditor, "active rows should retain stable input order");
|
|
655
|
+
assert.ok(auditor < done, "active rows should precede completed rows");
|
|
656
|
+
assert.ok(done < pending, "completed rows should precede pending rows");
|
|
657
|
+
assert.match(text, /done-scout.*complete/);
|
|
658
|
+
assert.match(text, /pending-worker.*pending/);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it("preserves completed, pending, and hidden overflow evidence in a parallel group card", () => {
|
|
662
|
+
const group = {
|
|
663
|
+
asyncId: "evidence-group",
|
|
664
|
+
asyncDir: "/tmp/evidence-group",
|
|
665
|
+
status: "running",
|
|
666
|
+
mode: "parallel",
|
|
667
|
+
agents: ["scout", "worker"],
|
|
668
|
+
activeParallelGroup: true,
|
|
669
|
+
runningSteps: 0,
|
|
670
|
+
completedSteps: 1,
|
|
671
|
+
stepsTotal: 2,
|
|
672
|
+
steps: [
|
|
673
|
+
{ index: 0, agent: "scout", label: "done-scout", status: "complete" },
|
|
674
|
+
{ index: 1, agent: "worker", label: "pending-worker", status: "pending" },
|
|
675
|
+
],
|
|
676
|
+
};
|
|
677
|
+
const text = buildWidgetLines([
|
|
678
|
+
group,
|
|
679
|
+
{ asyncId: "overflow-1", asyncDir: "/tmp/overflow-1", status: "running", agents: ["one"] },
|
|
680
|
+
{ asyncId: "overflow-2", asyncDir: "/tmp/overflow-2", status: "running", agents: ["two"] },
|
|
681
|
+
{ asyncId: "overflow-3", asyncDir: "/tmp/overflow-3", status: "running", agents: ["three"] },
|
|
682
|
+
{ asyncId: "overflow-4", asyncDir: "/tmp/overflow-4", status: "running", agents: ["four"] },
|
|
683
|
+
], theme, 220).join("\n");
|
|
684
|
+
|
|
685
|
+
assert.equal((text.match(/parallel group/g) ?? []).length, 1);
|
|
686
|
+
assert.match(text, /1\/2 done/);
|
|
687
|
+
assert.match(text, /done-scout.*complete/);
|
|
688
|
+
assert.match(text, /pending-worker.*pending/);
|
|
689
|
+
assert.match(text, /\+1 more \(1 running\)/);
|
|
690
|
+
});
|
|
691
|
+
|
|
557
692
|
it("shows peak context usage and falls back to token usage without a limit", () => {
|
|
558
693
|
const step = {
|
|
559
694
|
index: 0,
|
|
@@ -608,6 +743,7 @@ describe("subagent async widget rendering", () => {
|
|
|
608
743
|
const lines = (widget as (_tui: unknown, widgetTheme: typeof theme) => { render(width: number): string[] })(undefined, theme).render(180).map((line) => line.trimEnd());
|
|
609
744
|
const text = lines.join("\n");
|
|
610
745
|
assert.match(text, /async subagent parallel \(3\) · background/);
|
|
746
|
+
assert.match(text, /parallel group · 3 agents running · 0\/3 done/);
|
|
611
747
|
assert.match(text, /Agent 1\/3: reviewer · running · active now · 5 turns · 18 tool uses · 44k token/);
|
|
612
748
|
assert.match(text, /Agent 2\/3: reviewer · running · active 2s ago · 4 turns · 13 tool uses · 22k token/);
|
|
613
749
|
assert.match(text, /Agent 3\/3: reviewer · running · grep \| 1\.0s · 3 turns · 11 tool uses · 19k token/);
|
|
@@ -790,6 +926,110 @@ describe("subagent async widget rendering", () => {
|
|
|
790
926
|
resetWidgetLayout();
|
|
791
927
|
});
|
|
792
928
|
|
|
929
|
+
it("keeps the active runs.lanes stage in focus before collapsed history", () => {
|
|
930
|
+
resetWidgetLayout();
|
|
931
|
+
withStdoutSize(60, 120, () => {
|
|
932
|
+
const stageKeys = ["scope-scout", "red-tests", "label-helpers", "summary-title", "detail-row", "tiers-noise", "validation", "minimality-challenge", "fresh-review"];
|
|
933
|
+
const nodeIds = stageKeys.map((key) => `issue-1695.${key}`);
|
|
934
|
+
const statuses = stageKeys.map((_, index) => index < 3 ? "completed" : index === 3 ? "running" : "pending");
|
|
935
|
+
const workflowGraph = {
|
|
936
|
+
runId: "workflow-1695",
|
|
937
|
+
mode: "workflow",
|
|
938
|
+
phases: [{ title: "issue-1695 staged lane", nodeIds }],
|
|
939
|
+
nodes: stageKeys.map((key, index) => ({
|
|
940
|
+
id: nodeIds[index],
|
|
941
|
+
kind: "step",
|
|
942
|
+
agent: index === 0 ? "scout" : "worker",
|
|
943
|
+
label: key,
|
|
944
|
+
status: statuses[index],
|
|
945
|
+
flatIndex: index,
|
|
946
|
+
stepIndex: index,
|
|
947
|
+
})),
|
|
948
|
+
currentNodeId: nodeIds[3],
|
|
949
|
+
};
|
|
950
|
+
const job = {
|
|
951
|
+
asyncId: "workflow-1695",
|
|
952
|
+
asyncDir: "/tmp/workflow-1695",
|
|
953
|
+
status: "running",
|
|
954
|
+
mode: "workflow",
|
|
955
|
+
agents: ["scout", "worker"],
|
|
956
|
+
currentStep: 3,
|
|
957
|
+
stepsTotal: 4,
|
|
958
|
+
updatedAt: 200,
|
|
959
|
+
workflowGraph,
|
|
960
|
+
steps: stageKeys.slice(0, 4).map((key, index) => ({
|
|
961
|
+
index,
|
|
962
|
+
agent: index === 0 ? "scout" : "worker",
|
|
963
|
+
status: statuses[index],
|
|
964
|
+
label: key,
|
|
965
|
+
workflowKey: nodeIds[index],
|
|
966
|
+
phase: "issue-1695",
|
|
967
|
+
description: `${key} task`,
|
|
968
|
+
outputName: `${key}.md`,
|
|
969
|
+
...(index < 3 ? { lastActivityAt: 100 } : { currentTool: "read", currentToolStartedAt: 190, lastActivityAt: 195 }),
|
|
970
|
+
})),
|
|
971
|
+
};
|
|
972
|
+
const ui = createUiContext();
|
|
973
|
+
renderWidget(ui.ctx as never, [job]);
|
|
974
|
+
const lines = renderWidgetLines(ui.widgets.at(-1));
|
|
975
|
+
const text = lines.join("\n");
|
|
976
|
+
const activeIndex = lines.findIndex((line) => line.includes("summary-title"));
|
|
977
|
+
const hiddenIndex = lines.findIndex((line) => /lines hidden/.test(line));
|
|
978
|
+
assert.ok(hiddenIndex >= 0, `collapsed history should expose a hidden-lines marker:\n${text}`);
|
|
979
|
+
assert.ok(activeIndex >= 0, `active stage should remain visible:\n${text}`);
|
|
980
|
+
assert.ok(activeIndex < hiddenIndex, `active stage must precede the hidden-lines marker:\n${text}`);
|
|
981
|
+
assert.match(text, /stage\s+4\/9/i);
|
|
982
|
+
assert.match(text, /summary-title task/);
|
|
983
|
+
assert.match(lines[activeIndex]!, /worker/);
|
|
984
|
+
});
|
|
985
|
+
resetWidgetLayout();
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
it("prioritizes failed, blocked, and gate stages over completed history", () => {
|
|
989
|
+
const stages = [
|
|
990
|
+
{ index: 0, agent: "scout", label: "completed-history", workflowKey: "issue-1695.completed-history", status: "complete" },
|
|
991
|
+
{ index: 1, agent: "worker", label: "failed-stage", workflowKey: "issue-1695.failed-stage", status: "failed", error: "stage failed" },
|
|
992
|
+
{ index: 2, agent: "worker", label: "blocked-stage", workflowKey: "issue-1695.blocked-stage", status: "pending", toolBudgetBlocked: true },
|
|
993
|
+
{ index: 3, agent: "reviewer", label: "gate-stage", workflowKey: "issue-1695.gate-stage", status: "complete", review: { status: "blockers" } },
|
|
994
|
+
{ index: 4, agent: "worker", label: "completed-tail", workflowKey: "issue-1695.completed-tail", status: "complete" },
|
|
995
|
+
];
|
|
996
|
+
const workflowGraph = {
|
|
997
|
+
runId: "workflow-priority",
|
|
998
|
+
mode: "workflow",
|
|
999
|
+
phases: [{ title: "issue-1695 staged lane", nodeIds: stages.map((stage) => stage.workflowKey) }],
|
|
1000
|
+
nodes: stages.map((stage) => ({
|
|
1001
|
+
id: stage.workflowKey,
|
|
1002
|
+
kind: "step",
|
|
1003
|
+
agent: stage.agent,
|
|
1004
|
+
label: stage.label,
|
|
1005
|
+
status: stage.status === "complete" ? "completed" : stage.status === "failed" ? "failed" : "pending",
|
|
1006
|
+
flatIndex: stage.index,
|
|
1007
|
+
stepIndex: stage.index,
|
|
1008
|
+
})),
|
|
1009
|
+
currentNodeId: "issue-1695.failed-stage",
|
|
1010
|
+
};
|
|
1011
|
+
const text = buildWidgetLines([{
|
|
1012
|
+
asyncId: "workflow-priority",
|
|
1013
|
+
asyncDir: "/tmp/workflow-priority",
|
|
1014
|
+
status: "running",
|
|
1015
|
+
mode: "workflow",
|
|
1016
|
+
agents: ["scout", "worker", "reviewer"],
|
|
1017
|
+
currentStep: 1,
|
|
1018
|
+
stepsTotal: stages.length,
|
|
1019
|
+
workflowGraph,
|
|
1020
|
+
steps: stages,
|
|
1021
|
+
}], theme, 220, false).join("\n");
|
|
1022
|
+
const rowIndex = (label: string): number => text.split("\n").findIndex((line) => line.includes(label));
|
|
1023
|
+
const completed = rowIndex("completed-history");
|
|
1024
|
+
const failed = rowIndex("failed-stage");
|
|
1025
|
+
const blocked = rowIndex("blocked-stage");
|
|
1026
|
+
const gate = rowIndex("gate-stage");
|
|
1027
|
+
assert.ok(completed >= 0 && failed >= 0 && blocked >= 0 && gate >= 0, text);
|
|
1028
|
+
assert.ok(failed < completed, `failed stage should precede completed history:\n${text}`);
|
|
1029
|
+
assert.ok(blocked < completed, `blocked stage should precede completed history:\n${text}`);
|
|
1030
|
+
assert.ok(gate < completed, `gate stage should precede completed history:\n${text}`);
|
|
1031
|
+
});
|
|
1032
|
+
|
|
793
1033
|
it("keeps constrained progressive slots focused on active jobs", () => {
|
|
794
1034
|
resetWidgetLayout();
|
|
795
1035
|
withStdoutSize(22, 120, () => {
|
|
@@ -856,7 +1096,8 @@ describe("subagent async widget rendering", () => {
|
|
|
856
1096
|
|
|
857
1097
|
const text = renderWidgetLines(ui.widgets.at(-1)).join("\n");
|
|
858
1098
|
assert.match(text, /async subagent parallel · background/);
|
|
859
|
-
assert.match(text, /
|
|
1099
|
+
assert.match(text, /parallel group · 1 agent running · 0\/1 done/);
|
|
1100
|
+
assert.match(text, /reviewer · running/);
|
|
860
1101
|
assert.doesNotMatch(text, /subagents \(1\/1 running\)/);
|
|
861
1102
|
});
|
|
862
1103
|
resetWidgetLayout();
|
|
@@ -887,7 +1128,8 @@ describe("subagent async widget rendering", () => {
|
|
|
887
1128
|
const text = lines.join("\n");
|
|
888
1129
|
assert.match(text, /async subagent parallel \(3\) · background/);
|
|
889
1130
|
assert.match(text, /parallel · 2 agents running · 1\/3 done/);
|
|
890
|
-
assert.match(text, /
|
|
1131
|
+
assert.match(text, /Inspect the first row · running · 2 tool uses/);
|
|
1132
|
+
assert.doesNotMatch(text, /Agent 1\/3: reviewer/);
|
|
891
1133
|
assert.match(text, /⎿ active now/);
|
|
892
1134
|
assert.match(text, /Agent 2\/3: reviewer · running\n\s+⎿ read \| 2\.0s/);
|
|
893
1135
|
assert.match(text, /Press configured-expand-key for live detail/);
|
|
@@ -918,8 +1160,8 @@ describe("subagent async widget rendering", () => {
|
|
|
918
1160
|
},
|
|
919
1161
|
], theme, 180).join("\n");
|
|
920
1162
|
|
|
921
|
-
assert.match(text, /
|
|
922
|
-
assert.doesNotMatch(text, /
|
|
1163
|
+
assert.match(text, /Inspect the multi-job row · running/);
|
|
1164
|
+
assert.doesNotMatch(text, /reviewer: Inspect the multi-job row/);
|
|
923
1165
|
});
|
|
924
1166
|
|
|
925
1167
|
it("shows async job and step context badges", () => {
|
|
@@ -945,8 +1187,8 @@ describe("subagent async widget rendering", () => {
|
|
|
945
1187
|
], theme, 160).join("\n");
|
|
946
1188
|
|
|
947
1189
|
assert.match(text, /parallel \[mixed\]/);
|
|
948
|
-
assert.match(text, /
|
|
949
|
-
assert.match(text, /
|
|
1190
|
+
assert.match(text, /scout \[fresh\] · running/);
|
|
1191
|
+
assert.match(text, /worker \[fork\] · running/);
|
|
950
1192
|
});
|
|
951
1193
|
|
|
952
1194
|
it("shows model and thinking for active async widget rows", () => {
|
|
@@ -969,8 +1211,8 @@ describe("subagent async widget rendering", () => {
|
|
|
969
1211
|
], theme, 180);
|
|
970
1212
|
|
|
971
1213
|
const text = lines.join("\n");
|
|
972
|
-
assert.match(text, /
|
|
973
|
-
assert.match(text, /
|
|
1214
|
+
assert.match(text, /reviewer · running \(gpt-5\.5 · thinking high\)/);
|
|
1215
|
+
assert.match(text, /scout · running \(claude-haiku-4-5 · thinking low\)/);
|
|
974
1216
|
assert.doesNotMatch(text, /openai-codex\/gpt-5\.5/);
|
|
975
1217
|
assert.doesNotMatch(text, /gpt-5\.5:high/);
|
|
976
1218
|
});
|
|
@@ -993,9 +1235,9 @@ describe("subagent async widget rendering", () => {
|
|
|
993
1235
|
},
|
|
994
1236
|
], theme, 68);
|
|
995
1237
|
|
|
996
|
-
const row = lines.find((line) => line.includes("
|
|
997
|
-
assert.match(row, /
|
|
998
|
-
assert.doesNotMatch(row, /
|
|
1238
|
+
const row = lines.find((line) => line.includes("reviewer · running")) ?? "";
|
|
1239
|
+
assert.match(row, /reviewer · running/);
|
|
1240
|
+
assert.doesNotMatch(row, /reviewer \(/);
|
|
999
1241
|
});
|
|
1000
1242
|
|
|
1001
1243
|
it("shows inline live detail for expanded async parallel widget rows", () => {
|
|
@@ -1107,6 +1349,10 @@ describe("subagent async widget rendering", () => {
|
|
|
1107
1349
|
"Step 1/1: reviewer · running (gpt-5.5 · thinking xhigh) · 4 turns · 11 tool uses",
|
|
1108
1350
|
"reviewer · step 1/1 · 11 tool uses · 3m32s",
|
|
1109
1351
|
"Step 1/1: reviewer · running (gpt-5.5 · thinking xhigh) · 4 turns · 11 tool uses",
|
|
1352
|
+
"reviewer [fresh] · running · 11 tool uses · 3m37s",
|
|
1353
|
+
"reviewer · running · 11 tool uses · 3m39s",
|
|
1354
|
+
"reviewer · running · 11 tool uses · 3m42s",
|
|
1355
|
+
"reviewer · running · 11 tool uses · 3m44s",
|
|
1110
1356
|
"repo=nicobailon/pi-subagents",
|
|
1111
1357
|
"sha=d61aca... | 2m32s",
|
|
1112
1358
|
"output: /var/folders/x/T/pi-subagents-uid-501/async-subagent-runs/run-status-snapshots/output.log",
|
|
@@ -1116,12 +1362,16 @@ describe("subagent async widget rendering", () => {
|
|
|
1116
1362
|
};
|
|
1117
1363
|
|
|
1118
1364
|
const expandedText = buildWidgetLines([job], theme, 180, true).join("\n");
|
|
1119
|
-
assert.match(expandedText, /↻
|
|
1365
|
+
assert.match(expandedText, /↻ 11 progress updates/);
|
|
1120
1366
|
assert.match(expandedText, /latest: output: \/var\/folders\/x\/T\/pi-subagents-uid-501\/async-subagent-runs\/run-status-snapshots\/output.log/);
|
|
1121
1367
|
assert.match(expandedText, /repo=nicobailon\/pi-subagents/);
|
|
1122
1368
|
assert.match(expandedText, /sha=d61aca\.\.\. \| 2m32s/);
|
|
1123
1369
|
assert.doesNotMatch(expandedText, /3m27s/);
|
|
1124
1370
|
assert.doesNotMatch(expandedText, /3m29s/);
|
|
1371
|
+
assert.doesNotMatch(expandedText, /3m37s/);
|
|
1372
|
+
assert.doesNotMatch(expandedText, /3m39s/);
|
|
1373
|
+
assert.doesNotMatch(expandedText, /3m42s/);
|
|
1374
|
+
assert.doesNotMatch(expandedText, /3m44s/);
|
|
1125
1375
|
});
|
|
1126
1376
|
|
|
1127
1377
|
it("keeps mixed live output raw instead of hiding non-status lines", () => {
|
|
@@ -1196,41 +1446,163 @@ describe("subagent async widget rendering", () => {
|
|
|
1196
1446
|
assert.match(expandedText, /Checking final status: rejected/);
|
|
1197
1447
|
});
|
|
1198
1448
|
|
|
1199
|
-
it("
|
|
1449
|
+
it("dedupes one-child single async summary/title while retaining detail evidence", () => {
|
|
1200
1450
|
const now = Date.now();
|
|
1201
1451
|
const job = {
|
|
1202
1452
|
asyncId: "single-run",
|
|
1203
1453
|
asyncDir: "/tmp/single-run",
|
|
1204
1454
|
status: "running",
|
|
1205
1455
|
mode: "single",
|
|
1206
|
-
agents: ["
|
|
1456
|
+
agents: ["reviewer"],
|
|
1457
|
+
currentStep: 0,
|
|
1207
1458
|
stepsTotal: 1,
|
|
1208
1459
|
updatedAt: now,
|
|
1209
1460
|
steps: [
|
|
1210
1461
|
{
|
|
1211
1462
|
index: 0,
|
|
1212
|
-
agent: "
|
|
1213
|
-
sessionName: "
|
|
1463
|
+
agent: "reviewer",
|
|
1464
|
+
sessionName: " reviewer: Review the widget ",
|
|
1214
1465
|
status: "running",
|
|
1466
|
+
description: "Review the widget",
|
|
1215
1467
|
currentTool: "read",
|
|
1216
1468
|
currentToolArgs: "src/tui/render.ts",
|
|
1217
1469
|
currentToolStartedAt: now - 2000,
|
|
1218
|
-
|
|
1470
|
+
toolCount: 23,
|
|
1471
|
+
durationMs: 49_100,
|
|
1472
|
+
recentOutput: ["error: failed to inspect the widget"],
|
|
1219
1473
|
},
|
|
1220
1474
|
],
|
|
1221
1475
|
};
|
|
1222
1476
|
|
|
1223
1477
|
const collapsedText = buildWidgetLines([job], theme, 180).join("\n");
|
|
1224
|
-
|
|
1225
|
-
|
|
1478
|
+
const collapsedLines = collapsedText.split("\n");
|
|
1479
|
+
const collapsedSummary = collapsedLines.slice(0, 2).join("\n");
|
|
1480
|
+
assert.doesNotMatch(collapsedSummary, /step 1\/1/i);
|
|
1481
|
+
assert.match(collapsedSummary, /async subagent · background/);
|
|
1482
|
+
assert.match(collapsedSummary, /reviewer/);
|
|
1483
|
+
assert.match(collapsedLines[1] ?? "", /reviewer · running · 23 tool uses · 49\.1s/);
|
|
1484
|
+
assert.equal(collapsedLines.filter((line) => line.includes("reviewer · running · 23 tool uses · 49.1s")).length, 1);
|
|
1485
|
+
assert.doesNotMatch(collapsedText, /Step 1\/1: reviewer:/);
|
|
1486
|
+
assert.match(collapsedText, /reviewer · running · 23 tool uses · 49\.1s/);
|
|
1487
|
+
assert.match(collapsedText, /task: Review the widget/);
|
|
1226
1488
|
assert.match(collapsedText, /⎿ read: src\/tui\/render\.ts \| 2\.0s/);
|
|
1227
1489
|
assert.match(collapsedText, /Press configured-expand-key for live detail/);
|
|
1228
1490
|
assert.match(collapsedText, outputPathPattern("/tmp/single-run/output-0.log"));
|
|
1229
|
-
assert.doesNotMatch(collapsedText, /
|
|
1491
|
+
assert.doesNotMatch(collapsedText, /error: failed to inspect the widget/);
|
|
1230
1492
|
|
|
1231
1493
|
const expandedText = buildWidgetLines([job], theme, 180, true).join("\n");
|
|
1494
|
+
const expandedLines = expandedText.split("\n");
|
|
1495
|
+
const expandedSummary = expandedLines.slice(0, 2).join("\n");
|
|
1232
1496
|
assert.doesNotMatch(expandedText, /Press configured-expand-key for live detail/);
|
|
1233
|
-
assert.
|
|
1497
|
+
assert.doesNotMatch(expandedSummary, /step 1\/1/i);
|
|
1498
|
+
assert.match(expandedSummary, /async subagent · background/);
|
|
1499
|
+
assert.match(expandedSummary, /reviewer/);
|
|
1500
|
+
assert.match(expandedLines[1] ?? "", /reviewer · running · 23 tool uses · 49\.1s/);
|
|
1501
|
+
assert.equal(expandedLines.filter((line) => line.includes("reviewer · running · 23 tool uses · 49.1s")).length, 1);
|
|
1502
|
+
assert.doesNotMatch(expandedText, /Step 1\/1: reviewer:/);
|
|
1503
|
+
assert.match(expandedText, /reviewer · running · 23 tool uses · 49\.1s/);
|
|
1504
|
+
assert.match(expandedText, /task: Review the widget/);
|
|
1505
|
+
assert.match(expandedText, /⎿ read: src\/tui\/render\.ts \| 2\.0s/);
|
|
1506
|
+
assert.match(expandedText, /error: failed to inspect the widget/);
|
|
1507
|
+
assert.match(expandedText, outputPathPattern("/tmp/single-run/output-0.log"));
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
it("collapses a completed single child but keeps a mismatched step header", () => {
|
|
1511
|
+
const completed = buildWidgetLines([{
|
|
1512
|
+
asyncId: "single-complete",
|
|
1513
|
+
asyncDir: "/tmp/single-complete",
|
|
1514
|
+
status: "complete",
|
|
1515
|
+
mode: "single",
|
|
1516
|
+
agents: ["reviewer"],
|
|
1517
|
+
stepsTotal: 1,
|
|
1518
|
+
steps: [{ index: 0, agent: "reviewer", sessionName: "reviewer: Review the widget", status: "completed" }],
|
|
1519
|
+
}], theme, 180).join("\n");
|
|
1520
|
+
assert.match(completed, /reviewer · complete/);
|
|
1521
|
+
assert.doesNotMatch(completed, /Step 1\/1: reviewer:/);
|
|
1522
|
+
assert.match(completed, /task: Review the widget/);
|
|
1523
|
+
|
|
1524
|
+
const mismatched = buildWidgetLines([{
|
|
1525
|
+
asyncId: "single-mismatch",
|
|
1526
|
+
asyncDir: "/tmp/single-mismatch",
|
|
1527
|
+
status: "complete",
|
|
1528
|
+
mode: "single",
|
|
1529
|
+
agents: ["reviewer"],
|
|
1530
|
+
currentStep: 0,
|
|
1531
|
+
stepsTotal: 1,
|
|
1532
|
+
steps: [{ index: 0, agent: "reviewer", sessionName: "reviewer: Review the widget", status: "running" }],
|
|
1533
|
+
}], theme, 180).join("\n");
|
|
1534
|
+
assert.match(mismatched, /Step 1\/1: reviewer: Review the widget · running/);
|
|
1535
|
+
});
|
|
1536
|
+
|
|
1537
|
+
it("keeps explicit single-child error and gate evidence with a step header", () => {
|
|
1538
|
+
const text = buildWidgetLines([{
|
|
1539
|
+
asyncId: "single-evidence",
|
|
1540
|
+
asyncDir: "/tmp/single-evidence",
|
|
1541
|
+
status: "running",
|
|
1542
|
+
mode: "single",
|
|
1543
|
+
agents: ["reviewer"],
|
|
1544
|
+
currentStep: 0,
|
|
1545
|
+
stepsTotal: 1,
|
|
1546
|
+
steps: [{
|
|
1547
|
+
index: 0,
|
|
1548
|
+
agent: "reviewer",
|
|
1549
|
+
sessionName: "reviewer: Review the widget",
|
|
1550
|
+
status: "running",
|
|
1551
|
+
error: "failed to inspect the widget",
|
|
1552
|
+
review: { status: "blockers" },
|
|
1553
|
+
}],
|
|
1554
|
+
}], theme, 180, true).join("\n");
|
|
1555
|
+
assert.match(text, /Step 1\/1: reviewer: Review the widget · running/);
|
|
1556
|
+
assert.match(text, /error: failed to inspect the widget/);
|
|
1557
|
+
assert.match(text, /gate:review blockers/);
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1560
|
+
it("keeps a Step header for lane-bearing one-child single async jobs", () => {
|
|
1561
|
+
const job = {
|
|
1562
|
+
asyncId: "single-lane",
|
|
1563
|
+
asyncDir: "/tmp/single-lane",
|
|
1564
|
+
status: "running",
|
|
1565
|
+
mode: "single",
|
|
1566
|
+
agents: ["reviewer"],
|
|
1567
|
+
currentStep: 0,
|
|
1568
|
+
stepsTotal: 1,
|
|
1569
|
+
steps: [{
|
|
1570
|
+
index: 0,
|
|
1571
|
+
agent: "reviewer",
|
|
1572
|
+
sessionName: "reviewer: Review the widget",
|
|
1573
|
+
status: "running",
|
|
1574
|
+
phase: "fresh-review",
|
|
1575
|
+
label: "Review #1695",
|
|
1576
|
+
workflowKey: "review",
|
|
1577
|
+
outputName: "review.md",
|
|
1578
|
+
}],
|
|
1579
|
+
};
|
|
1580
|
+
|
|
1581
|
+
for (const expanded of [false, true]) {
|
|
1582
|
+
const text = buildWidgetLines([job], theme, 180, expanded).join("\n");
|
|
1583
|
+
assert.match(text, /Step 1\/1: reviewer: Review the widget · running/);
|
|
1584
|
+
assert.match(text, /phase:fresh-review/);
|
|
1585
|
+
assert.match(text, /out:review\.md/);
|
|
1586
|
+
}
|
|
1587
|
+
});
|
|
1588
|
+
|
|
1589
|
+
it("keeps failed, paused, and stopped detail evidence in single-child summaries", () => {
|
|
1590
|
+
for (const status of ["failed", "paused", "stopped"] as const) {
|
|
1591
|
+
const text = buildWidgetLines([{
|
|
1592
|
+
asyncId: `single-${status}`,
|
|
1593
|
+
asyncDir: `/tmp/single-${status}`,
|
|
1594
|
+
status,
|
|
1595
|
+
mode: "single",
|
|
1596
|
+
agents: ["reviewer"],
|
|
1597
|
+
currentStep: 0,
|
|
1598
|
+
stepsTotal: 1,
|
|
1599
|
+
steps: [{ index: 0, agent: "reviewer", status }],
|
|
1600
|
+
}], theme, 180).join("\n");
|
|
1601
|
+
const summary = text.split("\n").slice(0, 2).join("\n");
|
|
1602
|
+
|
|
1603
|
+
assert.doesNotMatch(summary, /step 1\/1/i);
|
|
1604
|
+
assert.match(text, new RegExp(`Step 1/1: reviewer · ${status}`));
|
|
1605
|
+
}
|
|
1234
1606
|
});
|
|
1235
1607
|
|
|
1236
1608
|
it("keeps generic activity fallback for single async jobs without steps", () => {
|
|
@@ -1275,6 +1647,91 @@ describe("subagent async widget rendering", () => {
|
|
|
1275
1647
|
assert.match(text, /step 2\/3 · parallel group: 1 agent running · 1\/2 done/);
|
|
1276
1648
|
});
|
|
1277
1649
|
|
|
1650
|
+
it("keeps the chain step number while nesting readable rows in an active parallel group card", () => {
|
|
1651
|
+
const text = buildWidgetLines([{
|
|
1652
|
+
asyncId: "active-chain-group",
|
|
1653
|
+
asyncDir: "/tmp/active-chain-group",
|
|
1654
|
+
status: "running",
|
|
1655
|
+
mode: "chain",
|
|
1656
|
+
agents: ["producer", "scout", "reviewer"],
|
|
1657
|
+
activeParallelGroup: true,
|
|
1658
|
+
currentStep: 2,
|
|
1659
|
+
chainStepCount: 3,
|
|
1660
|
+
parallelGroups: [{ start: 1, count: 2, stepIndex: 1 }],
|
|
1661
|
+
runningSteps: 1,
|
|
1662
|
+
completedSteps: 1,
|
|
1663
|
+
stepsTotal: 2,
|
|
1664
|
+
steps: [
|
|
1665
|
+
{ index: 1, agent: "scout", label: "Inspect source", status: "complete" },
|
|
1666
|
+
{ index: 2, agent: "reviewer", label: "Review diff", status: "running" },
|
|
1667
|
+
],
|
|
1668
|
+
}], theme, 220, true).join("\n");
|
|
1669
|
+
|
|
1670
|
+
assert.match(text, /chain · step 2\/3/);
|
|
1671
|
+
assert.match(text, /Step 2\/3: parallel group/);
|
|
1672
|
+
assert.match(text, /Inspect source/);
|
|
1673
|
+
assert.match(text, /Review diff/);
|
|
1674
|
+
assert.doesNotMatch(text, /Agent \d+\/3:/);
|
|
1675
|
+
});
|
|
1676
|
+
|
|
1677
|
+
it("renders child rows beneath an inactive chain parallel-group header", () => {
|
|
1678
|
+
const text = buildWidgetLines([{
|
|
1679
|
+
asyncId: "completed-chain-group",
|
|
1680
|
+
asyncDir: "/tmp/completed-chain-group",
|
|
1681
|
+
status: "running",
|
|
1682
|
+
mode: "chain",
|
|
1683
|
+
agents: ["scout", "reviewer", "writer"],
|
|
1684
|
+
activeParallelGroup: false,
|
|
1685
|
+
currentStep: 3,
|
|
1686
|
+
chainStepCount: 2,
|
|
1687
|
+
parallelGroups: [{ start: 0, count: 2, stepIndex: 0 }],
|
|
1688
|
+
stepsTotal: 3,
|
|
1689
|
+
steps: [
|
|
1690
|
+
{ index: 0, agent: "scout", label: "Gather context", status: "complete" },
|
|
1691
|
+
{ index: 1, agent: "reviewer", label: "Review diff", status: "complete" },
|
|
1692
|
+
{ index: 2, agent: "writer", label: "Apply fixes", status: "running" },
|
|
1693
|
+
],
|
|
1694
|
+
}], theme, 220, true).join("\n");
|
|
1695
|
+
const headerIndex = text.indexOf("Step 1/2: parallel group");
|
|
1696
|
+
|
|
1697
|
+
assert.ok(headerIndex >= 0);
|
|
1698
|
+
assert.ok(text.indexOf("Gather context", headerIndex) > headerIndex);
|
|
1699
|
+
assert.ok(text.indexOf("Review diff", headerIndex) > headerIndex);
|
|
1700
|
+
assert.match(text, /Step 2\/2: writer/);
|
|
1701
|
+
});
|
|
1702
|
+
|
|
1703
|
+
it("renders a pending dynamic fanout placeholder instead of an empty parallel group", () => {
|
|
1704
|
+
const text = buildWidgetLines([{
|
|
1705
|
+
asyncId: "dynamic-placeholder",
|
|
1706
|
+
asyncDir: "/tmp/dynamic-placeholder",
|
|
1707
|
+
status: "running",
|
|
1708
|
+
mode: "chain",
|
|
1709
|
+
agents: ["expand:reviewer"],
|
|
1710
|
+
activeParallelGroup: true,
|
|
1711
|
+
currentStep: 0,
|
|
1712
|
+
chainStepCount: 1,
|
|
1713
|
+
parallelGroups: [{ start: 0, count: 1, stepIndex: 0 }],
|
|
1714
|
+
runningSteps: 0,
|
|
1715
|
+
completedSteps: 0,
|
|
1716
|
+
stepsTotal: 1,
|
|
1717
|
+
steps: [{
|
|
1718
|
+
index: 0,
|
|
1719
|
+
agent: "expand:reviewer",
|
|
1720
|
+
label: "Dynamic fanout (reviews)",
|
|
1721
|
+
description: "Await review targets",
|
|
1722
|
+
status: "pending",
|
|
1723
|
+
workflowKey: "dynamic-review",
|
|
1724
|
+
outputName: "reviews.md",
|
|
1725
|
+
}],
|
|
1726
|
+
}], theme, 220, true).join("\n");
|
|
1727
|
+
|
|
1728
|
+
assert.match(text, /parallel group/);
|
|
1729
|
+
assert.match(text, /Step 1\/1: parallel group/);
|
|
1730
|
+
assert.match(text, /Dynamic fanout \(reviews\)/);
|
|
1731
|
+
assert.match(text, /pending/);
|
|
1732
|
+
assert.match(text, /next:await launch/);
|
|
1733
|
+
});
|
|
1734
|
+
|
|
1278
1735
|
it("uses logical chain steps after an async chain parallel group finishes", () => {
|
|
1279
1736
|
const job = {
|
|
1280
1737
|
asyncId: "run-chain",
|
|
@@ -1314,6 +1771,32 @@ describe("subagent async widget rendering", () => {
|
|
|
1314
1771
|
);
|
|
1315
1772
|
});
|
|
1316
1773
|
|
|
1774
|
+
it("keeps chain step structure when only one logical child is materialized", () => {
|
|
1775
|
+
const job = {
|
|
1776
|
+
asyncId: "partial-chain",
|
|
1777
|
+
asyncDir: "/tmp/partial-chain",
|
|
1778
|
+
status: "running",
|
|
1779
|
+
mode: "chain",
|
|
1780
|
+
agents: ["planner", "reviewer"],
|
|
1781
|
+
currentStep: 0,
|
|
1782
|
+
chainStepCount: 2,
|
|
1783
|
+
stepsTotal: 1,
|
|
1784
|
+
steps: [{
|
|
1785
|
+
index: 0,
|
|
1786
|
+
agent: "planner",
|
|
1787
|
+
sessionName: "planner: Plan the review",
|
|
1788
|
+
status: "running",
|
|
1789
|
+
}],
|
|
1790
|
+
};
|
|
1791
|
+
const text = buildWidgetLines([job], theme, 180).join("\n");
|
|
1792
|
+
|
|
1793
|
+
assert.match(text, /async subagent chain \(2\) · background/);
|
|
1794
|
+
assert.match(text, /chain · step 1\/2/);
|
|
1795
|
+
assert.match(text, /Step 1\/2: planner: Plan the review/);
|
|
1796
|
+
assert.doesNotMatch(text, /Step 1\/1: planner: Plan the review/);
|
|
1797
|
+
assert.doesNotMatch(text, /\s+[^\n]*planner · running/);
|
|
1798
|
+
});
|
|
1799
|
+
|
|
1317
1800
|
it("omits zero-running labels for pending active async parallel groups", () => {
|
|
1318
1801
|
const lines = buildWidgetLines([
|
|
1319
1802
|
{
|
|
@@ -1411,7 +1894,8 @@ describe("subagent async widget rendering", () => {
|
|
|
1411
1894
|
);
|
|
1412
1895
|
});
|
|
1413
1896
|
|
|
1414
|
-
it("
|
|
1897
|
+
it("advances running widget glyphs when the wall clock moves despite unchanged progress", () => {
|
|
1898
|
+
const originalNow = Date.now;
|
|
1415
1899
|
const job = {
|
|
1416
1900
|
asyncId: "run-stable",
|
|
1417
1901
|
asyncDir: "/tmp/run",
|
|
@@ -1423,12 +1907,19 @@ describe("subagent async widget rendering", () => {
|
|
|
1423
1907
|
currentToolStartedAt: 2_000,
|
|
1424
1908
|
lastActivityAt: 2_500,
|
|
1425
1909
|
};
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1910
|
+
try {
|
|
1911
|
+
Date.now = () => 1_000;
|
|
1912
|
+
const first = buildWidgetLines([job], theme, 120);
|
|
1913
|
+
Date.now = () => 1_125;
|
|
1914
|
+
const second = buildWidgetLines([job], theme, 120);
|
|
1915
|
+
const withoutRunningGlyphs = (lines: string[]) => lines.map((line) => line.replace(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/gu, ""));
|
|
1429
1916
|
|
|
1430
|
-
|
|
1431
|
-
|
|
1917
|
+
assert.notDeepEqual(second, first);
|
|
1918
|
+
assert.deepEqual(withoutRunningGlyphs(second), withoutRunningGlyphs(first), "only the running glyph should change");
|
|
1919
|
+
assert.notEqual(firstGrapheme(first[1] ?? ""), firstGrapheme(second[1] ?? ""));
|
|
1920
|
+
} finally {
|
|
1921
|
+
Date.now = originalNow;
|
|
1922
|
+
}
|
|
1432
1923
|
});
|
|
1433
1924
|
|
|
1434
1925
|
it("advances component running glyphs with the render clock", () => {
|
|
@@ -1473,8 +1964,8 @@ describe("subagent async widget rendering", () => {
|
|
|
1473
1964
|
assert.notEqual(firstRunningGlyph(first[0] ?? ""), firstRunningGlyph(second[0] ?? ""), "header glyph should advance");
|
|
1474
1965
|
assert.notEqual(firstRunningGlyph(first[1] ?? ""), firstRunningGlyph(second[1] ?? ""), "job glyph should advance");
|
|
1475
1966
|
assert.notEqual(
|
|
1476
|
-
firstRunningGlyph(first.find((line) => line.includes("
|
|
1477
|
-
firstRunningGlyph(second.find((line) => line.includes("
|
|
1967
|
+
firstRunningGlyph(first.find((line) => line.includes("reviewer · running")) ?? ""),
|
|
1968
|
+
firstRunningGlyph(second.find((line) => line.includes("reviewer · running")) ?? ""),
|
|
1478
1969
|
"step glyph should advance",
|
|
1479
1970
|
);
|
|
1480
1971
|
} finally {
|