@selesai/code 0.2.1 → 0.3.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/dist/config.d.ts +4 -15
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -45
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +8 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/diagnostics.d.ts +1 -0
- package/dist/core/diagnostics.d.ts.map +1 -1
- package/dist/core/diagnostics.js.map +1 -1
- package/dist/core/model-registry.d.ts +4 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +101 -51
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/package-manager.d.ts +10 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +93 -8
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-manager.test.d.ts +2 -0
- package/dist/core/package-manager.test.d.ts.map +1 -0
- package/dist/core/package-manager.test.js +20 -0
- package/dist/core/package-manager.test.js.map +1 -0
- package/dist/core/resource-loader.d.ts +3 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +10 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/defaults/models.json +64 -113
- package/dist/extensions/caveman/caveman-instructions.cjs +39 -0
- package/dist/extensions/caveman/index.js +116 -0
- package/dist/extensions/caveman/package.json +8 -0
- package/dist/extensions/caveman/test/extension.test.js +185 -0
- package/dist/extensions/caveman/test/helpers.test.js +67 -0
- package/dist/extensions/copy-turn.ts +2 -2
- package/dist/extensions/pi-intercom/broker/broker.ts +2 -3
- package/dist/extensions/pi-intercom/broker/paths.ts +10 -5
- package/dist/extensions/pi-intercom/broker/spawn.ts +2 -3
- package/dist/extensions/pi-intercom/config.ts +2 -2
- package/dist/extensions/pi-intercom/index.ts +2 -2
- package/dist/extensions/pi-intercom/package.json +2 -2
- package/dist/extensions/pi-intercom/ui/compose.ts +3 -3
- package/dist/extensions/pi-intercom/ui/inline-message.ts +3 -3
- package/dist/extensions/pi-intercom/ui/session-list.ts +3 -3
- package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +2 -2
- package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +2 -1
- package/dist/extensions/pi-powerline-footer/index.ts +40 -47
- package/dist/extensions/pi-powerline-footer/package.json +2 -2
- package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +10 -10
- package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +1 -1
- package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +2 -2
- package/dist/extensions/pi-powerline-footer/theme.ts +6 -6
- package/dist/extensions/pi-powerline-footer/tps.ts +1 -1
- package/dist/extensions/pi-powerline-footer/types.ts +7 -7
- package/dist/extensions/pi-powerline-footer/welcome.ts +23 -16
- package/dist/extensions/pi-powerline-footer/working-vibes.ts +52 -59
- package/dist/extensions/pi-rewind-hook/index.ts +1 -1
- package/dist/extensions/pi-rewind-hook/package.json +1 -1
- package/dist/extensions/pi-subagents/README.md +224 -52
- package/dist/extensions/pi-subagents/agents/architect.md +188 -0
- package/dist/extensions/pi-subagents/agents/builder.md +120 -0
- package/dist/extensions/pi-subagents/agents/commentator.md +131 -0
- package/dist/extensions/pi-subagents/agents/explorer.md +51 -0
- package/dist/extensions/pi-subagents/agents/recapper.md +21 -0
- package/dist/extensions/pi-subagents/install.mjs +93 -0
- package/dist/extensions/pi-subagents/package.json +4 -5
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +76 -13
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +179 -2
- package/dist/extensions/pi-subagents/src/agents/agent-memory.ts +254 -0
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +11 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +193 -19
- package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +27 -2
- package/dist/extensions/pi-subagents/src/extension/config.ts +27 -4
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +1 -7
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -2
- package/dist/extensions/pi-subagents/src/extension/index.ts +73 -71
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +369 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +54 -10
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +200 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +21 -253
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +519 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +195 -105
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +88 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +67 -10
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +34 -4
- package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +166 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +156 -1
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +515 -0
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +161 -44
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +1 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +3 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +167 -6
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +514 -0
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +28 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +873 -204
- package/dist/extensions/pi-subagents/src/runs/background/wait.ts +353 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +123 -27
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +174 -70
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +569 -81
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +45 -22
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +172 -21
- package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +128 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +89 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +50 -1
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +35 -4
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +63 -45
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +125 -5
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +74 -0
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +52 -0
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +28 -5
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +16 -1
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +15 -2
- package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +212 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +133 -22
- package/dist/extensions/pi-subagents/src/shared/settings.ts +3 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +197 -4
- package/dist/extensions/pi-subagents/src/shared/utils.ts +108 -17
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +330 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +135 -4
- package/dist/extensions/pi-subagents/src/tui/render.ts +32 -12
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2772 -0
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +1107 -0
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +463 -0
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +274 -0
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1535 -0
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +242 -0
- package/dist/extensions/pi-subagents/test/integration/doctor-executor.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +252 -0
- package/dist/extensions/pi-subagents/test/integration/foreground-result-size.test.ts +188 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +1306 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +1033 -0
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +397 -0
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +583 -0
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +691 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +892 -0
- package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1824 -0
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1284 -0
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +127 -0
- package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +299 -0
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +37 -0
- package/dist/extensions/pi-subagents/test/support/helpers.ts +189 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +335 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +129 -0
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +184 -0
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +285 -0
- package/dist/extensions/pi-subagents/test/support/register-loader.mjs +15 -0
- package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +108 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +421 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +343 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +1199 -0
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +556 -0
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +420 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +579 -0
- package/dist/extensions/pi-subagents/test/unit/agent-scope.test.ts +20 -0
- package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +24 -0
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +221 -0
- package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +98 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +285 -0
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +112 -0
- package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +206 -0
- package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +159 -0
- package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +229 -0
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +225 -0
- package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +101 -0
- package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +228 -0
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +152 -0
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +72 -0
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +327 -0
- package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +161 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +159 -0
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +217 -0
- package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +78 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +415 -0
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +126 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +271 -0
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +173 -0
- package/dist/extensions/pi-subagents/test/unit/jsonl-writer.test.ts +119 -0
- package/dist/extensions/pi-subagents/test/unit/live-async-resume.test.ts +170 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +346 -0
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +87 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +145 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +162 -0
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +470 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +329 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +319 -0
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +70 -0
- package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +247 -0
- package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +113 -0
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +803 -0
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +238 -0
- package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +118 -0
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +299 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +408 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +134 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +257 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +226 -0
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +294 -0
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +147 -0
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +997 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +478 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +562 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +438 -0
- package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +54 -0
- package/dist/extensions/pi-subagents/test/unit/slash-chain-groups.test.ts +464 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +255 -0
- package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +19 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +245 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +517 -0
- package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +72 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +237 -0
- package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +60 -0
- package/dist/extensions/pi-subagents/test/unit/ts-loader.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +202 -0
- package/dist/extensions/pi-subagents/test/unit/types-fork-preamble.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/wait.test.ts +398 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +134 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +479 -0
- package/dist/extensions/pi-web-agent/package.json +2 -2
- package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +2 -2
- package/dist/extensions/pi-web-agent/src/changelog-notice.ts +5 -2
- package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +1 -1
- package/dist/extensions/pi-web-agent/src/extension.ts +1 -1
- package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +6 -4
- package/dist/extensions/question/helpers.ts +3 -3
- package/dist/extensions/question/index.ts +2 -2
- package/dist/extensions/question/navigation.ts +2 -2
- package/dist/extensions/question/package.json +2 -2
- package/dist/extensions/question/question-list.ts +2 -2
- package/dist/extensions/question/tests/question-list.test.ts +2 -2
- package/dist/extensions/question/tui-adapter.ts +2 -2
- package/dist/extensions/question/types.ts +2 -2
- package/dist/extensions/question/ui-protocol.ts +2 -2
- package/dist/extensions/rtk.ts +2 -2
- package/dist/extensions/tokenin-onboarding.ts +2 -2
- package/dist/extensions/undo.ts +1 -1
- package/dist/extensions/web-agent-onboarding.ts +3 -3
- package/dist/extensions/workflow/adapter.ts +16 -8
- package/dist/extensions/workflow/extension.ts +1 -1
- package/dist/extensions/workflow/package.json +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +2 -0
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +19 -5
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/skills/caveman/SKILL.md +49 -0
- package/dist/skills/handoff-text/SKILL.md +13 -0
- package/dist/utils/thinking-tags.d.ts +7 -0
- package/dist/utils/thinking-tags.d.ts.map +1 -0
- package/dist/utils/thinking-tags.js +63 -0
- package/dist/utils/thinking-tags.js.map +1 -0
- package/docs/docs.json +4 -0
- package/docs/extensions.md +2 -0
- package/docs/settings.md +15 -0
- package/docs/shared-host-extensions.md +109 -0
- package/package.json +2 -2
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
|
|
4
|
+
const { buildWidgetLines, clearLegacyResultAnimationTimer, renderWidget } = await import("../../src/tui/render.ts") as {
|
|
5
|
+
buildWidgetLines: (jobs: Array<Record<string, unknown>>, theme: { fg(name: string, text: string): string; bold(text: string): string }, width?: number, expanded?: boolean) => string[];
|
|
6
|
+
clearLegacyResultAnimationTimer: (context: { state: { subagentResultAnimationTimer?: ReturnType<typeof setInterval> } }) => void;
|
|
7
|
+
renderWidget: (ctx: Record<string, unknown>, jobs: Array<Record<string, unknown>>) => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const theme = {
|
|
11
|
+
fg: (_name: string, text: string) => text,
|
|
12
|
+
bold: (text: string) => text,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const runningGlyphPattern = "[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏●]";
|
|
16
|
+
|
|
17
|
+
function escapeRegExp(value: string): string {
|
|
18
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function outputPathPattern(posixPath: string): RegExp {
|
|
22
|
+
return new RegExp(`output: ${posixPath.split("/").map(escapeRegExp).join("[\\\\/]")}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function firstGrapheme(text: string): string {
|
|
26
|
+
return Array.from(text.trimStart())[0] ?? "";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function firstRunningGlyph(text: string): string {
|
|
30
|
+
return text.match(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏●]/)?.[0] ?? "";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function createUiContext() {
|
|
34
|
+
const widgets: unknown[] = [];
|
|
35
|
+
let renderRequests = 0;
|
|
36
|
+
const ctx = {
|
|
37
|
+
hasUI: true,
|
|
38
|
+
ui: {
|
|
39
|
+
theme,
|
|
40
|
+
setWidget: (_key: string, value: unknown) => {
|
|
41
|
+
widgets.push(value);
|
|
42
|
+
},
|
|
43
|
+
requestRender: () => {
|
|
44
|
+
renderRequests += 1;
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
ctx,
|
|
50
|
+
widgets,
|
|
51
|
+
get renderRequests() {
|
|
52
|
+
return renderRequests;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function renderWidgetLines(widget: unknown, width = 180): string[] {
|
|
58
|
+
return (widget as (_tui: unknown, widgetTheme: typeof theme) => { render(width: number): string[] })(undefined, theme).render(width);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function restoreDescriptor(target: object, key: string, descriptor: PropertyDescriptor | undefined): void {
|
|
62
|
+
if (descriptor) {
|
|
63
|
+
Object.defineProperty(target, key, descriptor);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
Reflect.deleteProperty(target, key);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function withStdoutSize<T>(rows: number, columns: number, fn: () => T): T {
|
|
70
|
+
const stdout = process.stdout as NodeJS.WriteStream & { rows?: number; columns?: number };
|
|
71
|
+
const rowsDescriptor = Object.getOwnPropertyDescriptor(stdout, "rows");
|
|
72
|
+
const columnsDescriptor = Object.getOwnPropertyDescriptor(stdout, "columns");
|
|
73
|
+
Object.defineProperty(stdout, "rows", { configurable: true, value: rows });
|
|
74
|
+
Object.defineProperty(stdout, "columns", { configurable: true, value: columns });
|
|
75
|
+
try {
|
|
76
|
+
return fn();
|
|
77
|
+
} finally {
|
|
78
|
+
restoreDescriptor(stdout, "rows", rowsDescriptor);
|
|
79
|
+
restoreDescriptor(stdout, "columns", columnsDescriptor);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function resetWidgetLayout(): void {
|
|
84
|
+
renderWidget(createUiContext().ctx as never, []);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
describe("subagent async widget rendering", () => {
|
|
88
|
+
it("orders running jobs before queued summaries and completions", () => {
|
|
89
|
+
const lines = buildWidgetLines([
|
|
90
|
+
{ asyncId: "done-1", asyncDir: "/tmp/done", status: "complete", agents: ["reviewer"], startedAt: 0, updatedAt: 1000 },
|
|
91
|
+
{ asyncId: "queued-1", asyncDir: "/tmp/queued", status: "queued", agents: ["planner"], startedAt: 0, updatedAt: 1000 },
|
|
92
|
+
{ asyncId: "run-1", asyncDir: "/tmp/run", status: "running", agents: ["scout"], currentStep: 0, stepsTotal: 2, startedAt: Date.now() - 1000, updatedAt: Date.now(), currentTool: "read", currentToolStartedAt: Date.now() - 500 },
|
|
93
|
+
], theme, 120);
|
|
94
|
+
|
|
95
|
+
const text = lines.join("\n");
|
|
96
|
+
assert.match(text, new RegExp(`^${runningGlyphPattern} Async agents · background`));
|
|
97
|
+
assert.ok(text.indexOf("scout") < text.indexOf("queued"), "running row should precede queued summary");
|
|
98
|
+
assert.ok(text.indexOf("queued") < text.indexOf("reviewer"), "queued summary should precede completions");
|
|
99
|
+
assert.match(text, /⎿ read/);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("uses parallel running/done wording for async jobs with parallel groups", () => {
|
|
103
|
+
const lines = buildWidgetLines([
|
|
104
|
+
{ asyncId: "run-1", asyncDir: "/tmp/1", status: "running", mode: "parallel", agents: ["scout", "reviewer", "worker"], hasParallelGroups: true, activeParallelGroup: true, runningSteps: 3, completedSteps: 0, stepsTotal: 3 },
|
|
105
|
+
], theme, 120);
|
|
106
|
+
|
|
107
|
+
const text = lines.join("\n");
|
|
108
|
+
assert.match(text, /parallel · 3 agents running · 0\/3 done/);
|
|
109
|
+
assert.match(text, /⎿ thinking…/);
|
|
110
|
+
assert.doesNotMatch(text, /parallel · scout, reviewer, worker/);
|
|
111
|
+
assert.doesNotMatch(text, /step 1\/3/);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("collapses repeated async parallel agent names", () => {
|
|
115
|
+
const lines = buildWidgetLines([
|
|
116
|
+
{ asyncId: "run-1", asyncDir: "/tmp/1", status: "running", mode: "parallel", agents: ["reviewer", "reviewer", "reviewer"], activeParallelGroup: true, runningSteps: 3, completedSteps: 0, stepsTotal: 3 },
|
|
117
|
+
], theme, 120);
|
|
118
|
+
|
|
119
|
+
const text = lines.join("\n");
|
|
120
|
+
assert.match(text, /parallel · 3 agents running/);
|
|
121
|
+
assert.doesNotMatch(text, /parallel · reviewer ×3/);
|
|
122
|
+
assert.doesNotMatch(text, /reviewer → reviewer → reviewer/);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("renders a compact component widget for three active parallel agents without core truncation", () => {
|
|
126
|
+
const now = Date.now();
|
|
127
|
+
const ui = createUiContext();
|
|
128
|
+
renderWidget(ui.ctx as never, [{
|
|
129
|
+
asyncId: "run-1",
|
|
130
|
+
asyncDir: "/tmp/1",
|
|
131
|
+
status: "running",
|
|
132
|
+
mode: "parallel",
|
|
133
|
+
agents: ["reviewer", "reviewer", "reviewer"],
|
|
134
|
+
activeParallelGroup: true,
|
|
135
|
+
runningSteps: 3,
|
|
136
|
+
completedSteps: 0,
|
|
137
|
+
stepsTotal: 3,
|
|
138
|
+
updatedAt: now,
|
|
139
|
+
steps: [
|
|
140
|
+
{ index: 0, agent: "reviewer", status: "running", lastActivityAt: now, turnCount: 5, toolCount: 18, tokens: { input: 30_000, output: 10_000, cache: 4_000, total: 44_000 } },
|
|
141
|
+
{ index: 1, agent: "reviewer", status: "running", lastActivityAt: now - 2000, turnCount: 4, toolCount: 13, tokens: { input: 16_000, output: 4_000, cache: 2_000, total: 22_000 } },
|
|
142
|
+
{ index: 2, agent: "reviewer", status: "running", currentTool: "grep", currentToolStartedAt: now - 1000, turnCount: 3, toolCount: 11, tokens: { input: 14_000, output: 3_000, cache: 2_000, total: 19_000 } },
|
|
143
|
+
],
|
|
144
|
+
}]);
|
|
145
|
+
const widget = ui.widgets.at(-1);
|
|
146
|
+
assert.equal(typeof widget, "function", "renderWidget should install a component widget, not a capped string-array widget");
|
|
147
|
+
const lines = (widget as (_tui: unknown, widgetTheme: typeof theme) => { render(width: number): string[] })(undefined, theme).render(180).map((line) => line.trimEnd());
|
|
148
|
+
const text = lines.join("\n");
|
|
149
|
+
assert.match(text, /async subagent parallel \(3\) · background/);
|
|
150
|
+
assert.match(text, /Agent 1\/3: reviewer · running · active now · 5 turns · 18 tool uses · 44k token/);
|
|
151
|
+
assert.match(text, /Agent 2\/3: reviewer · running · active 2s ago · 4 turns · 13 tool uses · 22k token/);
|
|
152
|
+
assert.match(text, /Agent 3\/3: reviewer · running · grep \| 1\.0s · 3 turns · 11 tool uses · 19k token/);
|
|
153
|
+
assert.match(text, /Press configured-expand-key for live detail/);
|
|
154
|
+
assert.doesNotMatch(text, /widget truncated/);
|
|
155
|
+
assert.ok(lines.length <= 10, "collapsed component should stay under Pi's string-widget cap even though it bypasses it");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("locks crowded collapsed widget height for the current terminal session", () => {
|
|
159
|
+
resetWidgetLayout();
|
|
160
|
+
withStdoutSize(30, 120, () => {
|
|
161
|
+
const now = 20_000;
|
|
162
|
+
const crowdedJobs = Array.from({ length: 3 }, (_, jobIndex) => ({
|
|
163
|
+
asyncId: `run-${jobIndex + 1}`,
|
|
164
|
+
asyncDir: `/tmp/run-${jobIndex + 1}`,
|
|
165
|
+
status: "running",
|
|
166
|
+
mode: "parallel",
|
|
167
|
+
agents: ["scout", "reviewer"],
|
|
168
|
+
activeParallelGroup: true,
|
|
169
|
+
runningSteps: 2,
|
|
170
|
+
completedSteps: 0,
|
|
171
|
+
stepsTotal: 2,
|
|
172
|
+
updatedAt: now + jobIndex,
|
|
173
|
+
steps: [
|
|
174
|
+
{ index: 0, agent: "scout", status: "running", currentTool: "read", currentToolStartedAt: now - 1000 },
|
|
175
|
+
{ index: 1, agent: "reviewer", status: "running", currentTool: "grep", currentToolStartedAt: now - 2000 },
|
|
176
|
+
],
|
|
177
|
+
}));
|
|
178
|
+
const ui = createUiContext();
|
|
179
|
+
|
|
180
|
+
renderWidget(ui.ctx as never, crowdedJobs);
|
|
181
|
+
const crowdedLines = renderWidgetLines(ui.widgets.at(-1));
|
|
182
|
+
assert.equal(crowdedLines.length, 10, "30 terminal rows should keep the compact widget cap while locking height");
|
|
183
|
+
assert.match(crowdedLines.join("\n"), /Async agents · 3 agents running/);
|
|
184
|
+
|
|
185
|
+
renderWidget(ui.ctx as never, [{
|
|
186
|
+
...crowdedJobs[0]!,
|
|
187
|
+
status: "complete",
|
|
188
|
+
runningSteps: 0,
|
|
189
|
+
completedSteps: 2,
|
|
190
|
+
steps: [
|
|
191
|
+
{ index: 0, agent: "scout", status: "complete" },
|
|
192
|
+
{ index: 1, agent: "reviewer", status: "complete" },
|
|
193
|
+
],
|
|
194
|
+
}]);
|
|
195
|
+
const settledLines = renderWidgetLines(ui.widgets.at(-1));
|
|
196
|
+
assert.equal(settledLines.length, 10, "collapsed widget keeps its locked row count until cleared or resized");
|
|
197
|
+
assert.match(settledLines.join("\n"), /parallel · done/);
|
|
198
|
+
|
|
199
|
+
renderWidget(ui.ctx as never, []);
|
|
200
|
+
renderWidget(ui.ctx as never, [{ asyncId: "small", asyncDir: "/tmp/small", status: "running", agents: ["worker"], currentTool: "read" }]);
|
|
201
|
+
const resetLines = renderWidgetLines(ui.widgets.at(-1));
|
|
202
|
+
assert.ok(resetLines.length < 10, "clearing the widget starts a fresh layout session");
|
|
203
|
+
});
|
|
204
|
+
resetWidgetLayout();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("keeps medium terminal progressive fallback within the compact cap", () => {
|
|
208
|
+
resetWidgetLayout();
|
|
209
|
+
withStdoutSize(50, 120, () => {
|
|
210
|
+
const ui = createUiContext();
|
|
211
|
+
const jobs = [{
|
|
212
|
+
asyncId: "run-wide",
|
|
213
|
+
asyncDir: "/tmp/run-wide",
|
|
214
|
+
status: "running",
|
|
215
|
+
mode: "parallel",
|
|
216
|
+
agents: Array.from({ length: 40 }, (_, index) => `agent-${index}`),
|
|
217
|
+
activeParallelGroup: true,
|
|
218
|
+
runningSteps: 40,
|
|
219
|
+
completedSteps: 0,
|
|
220
|
+
stepsTotal: 40,
|
|
221
|
+
steps: Array.from({ length: 40 }, (_, index) => ({ index, agent: `agent-${index}`, status: "running", currentTool: "read" })),
|
|
222
|
+
}];
|
|
223
|
+
|
|
224
|
+
renderWidget(ui.ctx as never, jobs);
|
|
225
|
+
const lines = renderWidgetLines(ui.widgets.at(-1));
|
|
226
|
+
assert.equal(lines.length, 14);
|
|
227
|
+
assert.match(lines.join("\n"), /parallel · running/);
|
|
228
|
+
});
|
|
229
|
+
resetWidgetLayout();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("keeps constrained progressive slots focused on active jobs", () => {
|
|
233
|
+
resetWidgetLayout();
|
|
234
|
+
withStdoutSize(22, 120, () => {
|
|
235
|
+
const ui = createUiContext();
|
|
236
|
+
const jobs = [
|
|
237
|
+
{ asyncId: "run-1", asyncDir: "/tmp/run-1", status: "running", mode: "single", agents: ["first"], currentTool: "read" },
|
|
238
|
+
{ asyncId: "run-2", asyncDir: "/tmp/run-2", status: "running", mode: "single", agents: ["second"], currentTool: "grep" },
|
|
239
|
+
{ asyncId: "run-3", asyncDir: "/tmp/run-3", status: "running", mode: "single", agents: ["third"], currentTool: "edit" },
|
|
240
|
+
];
|
|
241
|
+
renderWidget(ui.ctx as never, jobs);
|
|
242
|
+
const firstText = renderWidgetLines(ui.widgets.at(-1)).join("\n");
|
|
243
|
+
assert.match(firstText, /first/);
|
|
244
|
+
assert.match(firstText, /\+2 more/);
|
|
245
|
+
|
|
246
|
+
renderWidget(ui.ctx as never, [
|
|
247
|
+
{ ...jobs[0]!, status: "complete", currentTool: undefined },
|
|
248
|
+
jobs[1]!,
|
|
249
|
+
jobs[2]!,
|
|
250
|
+
]);
|
|
251
|
+
const updatedText = renderWidgetLines(ui.widgets.at(-1)).join("\n");
|
|
252
|
+
assert.match(updatedText, /second/);
|
|
253
|
+
assert.doesNotMatch(updatedText, /first · done/);
|
|
254
|
+
assert.match(updatedText, /\+2 more/);
|
|
255
|
+
});
|
|
256
|
+
resetWidgetLayout();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("uses a single collapsed widget line when the terminal has almost no spare rows", () => {
|
|
260
|
+
resetWidgetLayout();
|
|
261
|
+
withStdoutSize(20, 120, () => {
|
|
262
|
+
const ui = createUiContext();
|
|
263
|
+
renderWidget(ui.ctx as never, [{
|
|
264
|
+
asyncId: "run-tiny",
|
|
265
|
+
asyncDir: "/tmp/run-tiny",
|
|
266
|
+
status: "running",
|
|
267
|
+
agents: ["worker"],
|
|
268
|
+
currentTool: "read",
|
|
269
|
+
}]);
|
|
270
|
+
|
|
271
|
+
const lines = renderWidgetLines(ui.widgets.at(-1));
|
|
272
|
+
assert.equal(lines.length, 1);
|
|
273
|
+
assert.match(lines[0] ?? "", /subagents \(1\/1 running\)/);
|
|
274
|
+
});
|
|
275
|
+
resetWidgetLayout();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("keeps expanded async widgets on the full-detail path", () => {
|
|
279
|
+
resetWidgetLayout();
|
|
280
|
+
withStdoutSize(20, 120, () => {
|
|
281
|
+
const ui = createUiContext();
|
|
282
|
+
ui.ctx.ui.getToolsExpanded = () => true;
|
|
283
|
+
renderWidget(ui.ctx as never, [{
|
|
284
|
+
asyncId: "run-expanded",
|
|
285
|
+
asyncDir: "/tmp/run-expanded",
|
|
286
|
+
status: "running",
|
|
287
|
+
mode: "parallel",
|
|
288
|
+
agents: ["reviewer"],
|
|
289
|
+
activeParallelGroup: true,
|
|
290
|
+
runningSteps: 1,
|
|
291
|
+
completedSteps: 0,
|
|
292
|
+
stepsTotal: 1,
|
|
293
|
+
steps: [{ index: 0, agent: "reviewer", status: "running", currentTool: "read" }],
|
|
294
|
+
}]);
|
|
295
|
+
|
|
296
|
+
const text = renderWidgetLines(ui.widgets.at(-1)).join("\n");
|
|
297
|
+
assert.match(text, /async subagent parallel · background/);
|
|
298
|
+
assert.match(text, /Agent 1\/1: reviewer · running/);
|
|
299
|
+
assert.doesNotMatch(text, /subagents \(1\/1 running\)/);
|
|
300
|
+
});
|
|
301
|
+
resetWidgetLayout();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("shows per-agent detail for active async parallel widget rows", () => {
|
|
305
|
+
const now = Date.now();
|
|
306
|
+
const lines = buildWidgetLines([
|
|
307
|
+
{
|
|
308
|
+
asyncId: "run-1",
|
|
309
|
+
asyncDir: "/tmp/1",
|
|
310
|
+
status: "running",
|
|
311
|
+
mode: "parallel",
|
|
312
|
+
agents: ["reviewer", "reviewer", "reviewer"],
|
|
313
|
+
activeParallelGroup: true,
|
|
314
|
+
runningSteps: 2,
|
|
315
|
+
completedSteps: 1,
|
|
316
|
+
stepsTotal: 3,
|
|
317
|
+
updatedAt: now,
|
|
318
|
+
steps: [
|
|
319
|
+
{ agent: "reviewer", status: "running", lastActivityAt: now, toolCount: 2 },
|
|
320
|
+
{ agent: "reviewer", status: "running", currentTool: "read", currentToolStartedAt: now - 2000 },
|
|
321
|
+
{ agent: "reviewer", status: "complete", tokens: { input: 1000, output: 500, cache: 0, total: 1500 } },
|
|
322
|
+
],
|
|
323
|
+
},
|
|
324
|
+
], theme, 160);
|
|
325
|
+
|
|
326
|
+
const text = lines.join("\n");
|
|
327
|
+
assert.match(text, /async subagent parallel \(3\) · background/);
|
|
328
|
+
assert.match(text, /parallel · 2 agents running · 1\/3 done/);
|
|
329
|
+
assert.match(text, /Agent 1\/3: reviewer · running · 2 tool uses/);
|
|
330
|
+
assert.match(text, /⎿ active now/);
|
|
331
|
+
assert.match(text, /Agent 2\/3: reviewer · running\n\s+⎿ read \| 2\.0s/);
|
|
332
|
+
assert.match(text, /Press configured-expand-key for live detail/);
|
|
333
|
+
assert.match(text, /Agent 3\/3: reviewer · complete · 1\.5k token/);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("shows model and thinking for active async widget rows", () => {
|
|
337
|
+
const lines = buildWidgetLines([
|
|
338
|
+
{
|
|
339
|
+
asyncId: "run-1",
|
|
340
|
+
asyncDir: "/tmp/1",
|
|
341
|
+
status: "running",
|
|
342
|
+
mode: "parallel",
|
|
343
|
+
agents: ["reviewer", "scout"],
|
|
344
|
+
activeParallelGroup: true,
|
|
345
|
+
runningSteps: 2,
|
|
346
|
+
completedSteps: 0,
|
|
347
|
+
stepsTotal: 2,
|
|
348
|
+
steps: [
|
|
349
|
+
{ agent: "reviewer", status: "running", model: "openai-codex/gpt-5.5:high" },
|
|
350
|
+
{ agent: "scout", status: "running", model: "anthropic/claude-haiku-4-5", thinking: "low" },
|
|
351
|
+
],
|
|
352
|
+
},
|
|
353
|
+
], theme, 180);
|
|
354
|
+
|
|
355
|
+
const text = lines.join("\n");
|
|
356
|
+
assert.match(text, /Agent 1\/2: reviewer · running \(gpt-5\.5 · thinking high\)/);
|
|
357
|
+
assert.match(text, /Agent 2\/2: scout · running \(claude-haiku-4-5 · thinking low\)/);
|
|
358
|
+
assert.doesNotMatch(text, /openai-codex\/gpt-5\.5/);
|
|
359
|
+
assert.doesNotMatch(text, /gpt-5\.5:high/);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("keeps async row status visible before long model badges on narrow widgets", () => {
|
|
363
|
+
const lines = buildWidgetLines([
|
|
364
|
+
{
|
|
365
|
+
asyncId: "run-1",
|
|
366
|
+
asyncDir: "/tmp/1",
|
|
367
|
+
status: "running",
|
|
368
|
+
mode: "parallel",
|
|
369
|
+
agents: ["reviewer"],
|
|
370
|
+
activeParallelGroup: true,
|
|
371
|
+
runningSteps: 1,
|
|
372
|
+
completedSteps: 0,
|
|
373
|
+
stepsTotal: 1,
|
|
374
|
+
steps: [
|
|
375
|
+
{ agent: "reviewer", status: "running", model: "anthropic/claude-opus-4-5-20260501-super-long-model-name:high" },
|
|
376
|
+
],
|
|
377
|
+
},
|
|
378
|
+
], theme, 68);
|
|
379
|
+
|
|
380
|
+
const row = lines.find((line) => line.includes("Agent 1/1")) ?? "";
|
|
381
|
+
assert.match(row, /Agent 1\/1: reviewer · running/);
|
|
382
|
+
assert.doesNotMatch(row, /Agent 1\/1: reviewer \(/);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("shows inline live detail for expanded async parallel widget rows", () => {
|
|
386
|
+
const now = Date.now();
|
|
387
|
+
const job = {
|
|
388
|
+
asyncId: "run-1",
|
|
389
|
+
asyncDir: "/tmp/1",
|
|
390
|
+
status: "running",
|
|
391
|
+
mode: "parallel",
|
|
392
|
+
agents: ["reviewer"],
|
|
393
|
+
activeParallelGroup: true,
|
|
394
|
+
runningSteps: 1,
|
|
395
|
+
completedSteps: 0,
|
|
396
|
+
stepsTotal: 1,
|
|
397
|
+
updatedAt: now,
|
|
398
|
+
steps: [
|
|
399
|
+
{
|
|
400
|
+
index: 0,
|
|
401
|
+
agent: "reviewer",
|
|
402
|
+
status: "running",
|
|
403
|
+
currentTool: "read",
|
|
404
|
+
currentToolArgs: "src/tui/render.ts",
|
|
405
|
+
currentToolStartedAt: now - 2000,
|
|
406
|
+
recentTools: [{ tool: "grep", args: "async widget", endMs: now - 3000 }],
|
|
407
|
+
recentOutput: ["found renderWidget", "checking expanded state"],
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const collapsedText = buildWidgetLines([job], theme, 180).join("\n");
|
|
413
|
+
assert.match(collapsedText, /Press configured-expand-key for live detail/);
|
|
414
|
+
assert.doesNotMatch(collapsedText, /found renderWidget/);
|
|
415
|
+
|
|
416
|
+
const expandedText = buildWidgetLines([job], theme, 180, true).join("\n");
|
|
417
|
+
assert.doesNotMatch(expandedText, /Press configured-expand-key for live detail/);
|
|
418
|
+
assert.match(expandedText, /⎿ read: src\/tui\/render\.ts \| 2\.0s/);
|
|
419
|
+
assert.match(expandedText, outputPathPattern("/tmp/1/output-0.log"));
|
|
420
|
+
assert.match(expandedText, /grep: async widget/);
|
|
421
|
+
assert.match(expandedText, /found renderWidget/);
|
|
422
|
+
assert.match(expandedText, /checking expanded state/);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it("shows step detail and configured live detail key hint for running single async jobs with steps", () => {
|
|
426
|
+
const now = Date.now();
|
|
427
|
+
const job = {
|
|
428
|
+
asyncId: "single-run",
|
|
429
|
+
asyncDir: "/tmp/single-run",
|
|
430
|
+
status: "running",
|
|
431
|
+
mode: "single",
|
|
432
|
+
agents: ["worker"],
|
|
433
|
+
stepsTotal: 1,
|
|
434
|
+
updatedAt: now,
|
|
435
|
+
steps: [
|
|
436
|
+
{
|
|
437
|
+
index: 0,
|
|
438
|
+
agent: "worker",
|
|
439
|
+
status: "running",
|
|
440
|
+
currentTool: "read",
|
|
441
|
+
currentToolArgs: "src/tui/render.ts",
|
|
442
|
+
currentToolStartedAt: now - 2000,
|
|
443
|
+
recentOutput: ["reading render widget"],
|
|
444
|
+
},
|
|
445
|
+
],
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const collapsedText = buildWidgetLines([job], theme, 180).join("\n");
|
|
449
|
+
assert.match(collapsedText, /async subagent worker · background/);
|
|
450
|
+
assert.match(collapsedText, /Step 1\/1: worker · running/);
|
|
451
|
+
assert.match(collapsedText, /⎿ read: src\/tui\/render\.ts \| 2\.0s/);
|
|
452
|
+
assert.match(collapsedText, /Press configured-expand-key for live detail/);
|
|
453
|
+
assert.match(collapsedText, outputPathPattern("/tmp/single-run/output-0.log"));
|
|
454
|
+
assert.doesNotMatch(collapsedText, /reading render widget/);
|
|
455
|
+
|
|
456
|
+
const expandedText = buildWidgetLines([job], theme, 180, true).join("\n");
|
|
457
|
+
assert.doesNotMatch(expandedText, /Press configured-expand-key for live detail/);
|
|
458
|
+
assert.match(expandedText, /reading render widget/);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it("keeps generic activity fallback for single async jobs without steps", () => {
|
|
462
|
+
const now = Date.now();
|
|
463
|
+
const text = buildWidgetLines([
|
|
464
|
+
{
|
|
465
|
+
asyncId: "single-no-steps",
|
|
466
|
+
asyncDir: "/tmp/single-no-steps",
|
|
467
|
+
status: "running",
|
|
468
|
+
mode: "single",
|
|
469
|
+
agents: ["worker"],
|
|
470
|
+
currentTool: "read",
|
|
471
|
+
currentToolStartedAt: now - 1000,
|
|
472
|
+
updatedAt: now,
|
|
473
|
+
},
|
|
474
|
+
], theme, 180).join("\n");
|
|
475
|
+
|
|
476
|
+
assert.match(text, /⎿ read 1\.0s/);
|
|
477
|
+
assert.doesNotMatch(text, /Step 1\/1/);
|
|
478
|
+
assert.doesNotMatch(text, /Press configured-expand-key for live detail/);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("includes logical chain context for active async chain parallel groups", () => {
|
|
482
|
+
const lines = buildWidgetLines([
|
|
483
|
+
{
|
|
484
|
+
asyncId: "run-chain",
|
|
485
|
+
asyncDir: "/tmp/chain",
|
|
486
|
+
status: "running",
|
|
487
|
+
mode: "chain",
|
|
488
|
+
agents: ["reviewer", "auditor"],
|
|
489
|
+
activeParallelGroup: true,
|
|
490
|
+
currentStep: 1,
|
|
491
|
+
chainStepCount: 3,
|
|
492
|
+
parallelGroups: [{ start: 1, count: 2, stepIndex: 1 }],
|
|
493
|
+
runningSteps: 1,
|
|
494
|
+
completedSteps: 1,
|
|
495
|
+
stepsTotal: 2,
|
|
496
|
+
},
|
|
497
|
+
], theme, 160);
|
|
498
|
+
|
|
499
|
+
const text = lines.join("\n");
|
|
500
|
+
assert.match(text, /step 2\/3 · parallel group: 1 agent running · 1\/2 done/);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("uses logical chain steps after an async chain parallel group finishes", () => {
|
|
504
|
+
const lines = buildWidgetLines([
|
|
505
|
+
{
|
|
506
|
+
asyncId: "run-chain",
|
|
507
|
+
asyncDir: "/tmp/chain",
|
|
508
|
+
status: "running",
|
|
509
|
+
mode: "chain",
|
|
510
|
+
agents: ["scout", "reviewer", "auditor", "writer"],
|
|
511
|
+
activeParallelGroup: false,
|
|
512
|
+
currentStep: 3,
|
|
513
|
+
chainStepCount: 2,
|
|
514
|
+
parallelGroups: [{ start: 0, count: 3, stepIndex: 0 }],
|
|
515
|
+
stepsTotal: 4,
|
|
516
|
+
steps: [
|
|
517
|
+
{ index: 0, agent: "scout", status: "complete" },
|
|
518
|
+
{ index: 1, agent: "reviewer", status: "complete" },
|
|
519
|
+
{ index: 2, agent: "auditor", status: "complete" },
|
|
520
|
+
{ index: 3, agent: "writer", status: "running", toolCount: 1 },
|
|
521
|
+
],
|
|
522
|
+
},
|
|
523
|
+
], theme, 180);
|
|
524
|
+
|
|
525
|
+
const text = lines.join("\n");
|
|
526
|
+
assert.match(text, /async subagent chain \(2\)/);
|
|
527
|
+
assert.match(text, /chain · step 2\/2/);
|
|
528
|
+
assert.match(text, /Step 1\/2: parallel group · 3\/3 done/);
|
|
529
|
+
assert.match(text, /Step 2\/2: writer · running · 1 tool use/);
|
|
530
|
+
assert.match(text, /Press configured-expand-key for live detail/);
|
|
531
|
+
assert.match(text, outputPathPattern("/tmp/chain/output-3.log"));
|
|
532
|
+
assert.doesNotMatch(text, /step 4\/4/);
|
|
533
|
+
assert.doesNotMatch(text, /Step 4\/4/);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it("omits zero-running labels for pending active async parallel groups", () => {
|
|
537
|
+
const lines = buildWidgetLines([
|
|
538
|
+
{
|
|
539
|
+
asyncId: "parallel-pending",
|
|
540
|
+
asyncDir: "/tmp/parallel-pending",
|
|
541
|
+
status: "running",
|
|
542
|
+
mode: "parallel",
|
|
543
|
+
agents: ["scout", "reviewer", "worker"],
|
|
544
|
+
activeParallelGroup: true,
|
|
545
|
+
runningSteps: 0,
|
|
546
|
+
completedSteps: 0,
|
|
547
|
+
stepsTotal: 3,
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
asyncId: "chain-pending",
|
|
551
|
+
asyncDir: "/tmp/chain-pending",
|
|
552
|
+
status: "running",
|
|
553
|
+
mode: "chain",
|
|
554
|
+
agents: ["reviewer", "auditor"],
|
|
555
|
+
activeParallelGroup: true,
|
|
556
|
+
currentStep: 0,
|
|
557
|
+
chainStepCount: 2,
|
|
558
|
+
parallelGroups: [{ start: 0, count: 2, stepIndex: 0 }],
|
|
559
|
+
runningSteps: 0,
|
|
560
|
+
completedSteps: 0,
|
|
561
|
+
stepsTotal: 2,
|
|
562
|
+
},
|
|
563
|
+
], theme, 180);
|
|
564
|
+
|
|
565
|
+
const text = lines.join("\n");
|
|
566
|
+
assert.match(text, /parallel · 0\/3 done/);
|
|
567
|
+
assert.match(text, /chain · step 1\/2 · parallel group: 0\/2 done/);
|
|
568
|
+
assert.doesNotMatch(text, /0 agents running/);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it("shows explicit overflow counts for hidden work", () => {
|
|
572
|
+
const lines = buildWidgetLines([
|
|
573
|
+
{ asyncId: "run-1", asyncDir: "/tmp/1", status: "running", agents: ["a1"] },
|
|
574
|
+
{ asyncId: "run-2", asyncDir: "/tmp/2", status: "running", agents: ["a2"] },
|
|
575
|
+
{ asyncId: "run-3", asyncDir: "/tmp/3", status: "running", agents: ["a3"] },
|
|
576
|
+
{ asyncId: "run-4", asyncDir: "/tmp/4", status: "running", agents: ["a4"] },
|
|
577
|
+
{ asyncId: "run-5", asyncDir: "/tmp/5", status: "running", agents: ["a5"] },
|
|
578
|
+
], theme, 120);
|
|
579
|
+
|
|
580
|
+
assert.match(lines.join("\n"), /\+1 more \(1 running\)/);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("counts hidden queued work even when a visible running agent name contains queued", () => {
|
|
584
|
+
const lines = buildWidgetLines([
|
|
585
|
+
{ asyncId: "run-1", asyncDir: "/tmp/1", status: "running", agents: ["queued-scanner"] },
|
|
586
|
+
{ asyncId: "run-2", asyncDir: "/tmp/2", status: "running", agents: ["a2"] },
|
|
587
|
+
{ asyncId: "run-3", asyncDir: "/tmp/3", status: "running", agents: ["a3"] },
|
|
588
|
+
{ asyncId: "run-4", asyncDir: "/tmp/4", status: "running", agents: ["a4"] },
|
|
589
|
+
{ asyncId: "queued-1", asyncDir: "/tmp/q", status: "queued", agents: ["planner"] },
|
|
590
|
+
], theme, 120);
|
|
591
|
+
|
|
592
|
+
assert.match(lines.join("\n"), /\+1 more \(1 queued\)/);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it("advances running widget glyphs when progress seed changes", () => {
|
|
596
|
+
const first = buildWidgetLines([
|
|
597
|
+
{ asyncId: "run-progress", asyncDir: "/tmp/run", status: "running", agents: ["worker"], updatedAt: 11 },
|
|
598
|
+
{ asyncId: "run-other", asyncDir: "/tmp/other", status: "running", agents: ["scout"], updatedAt: 0 },
|
|
599
|
+
], theme, 120);
|
|
600
|
+
const second = buildWidgetLines([
|
|
601
|
+
{ asyncId: "run-progress", asyncDir: "/tmp/run", status: "running", agents: ["worker"], updatedAt: 12 },
|
|
602
|
+
{ asyncId: "run-other", asyncDir: "/tmp/other", status: "running", agents: ["scout"], updatedAt: 0 },
|
|
603
|
+
], theme, 120);
|
|
604
|
+
|
|
605
|
+
assert.notEqual(firstGrapheme(first[0] ?? ""), firstGrapheme(second[0] ?? ""), "header glyph should advance from changed progress");
|
|
606
|
+
assert.notEqual(firstRunningGlyph(first[1] ?? ""), firstRunningGlyph(second[1] ?? ""), "job glyph should advance from changed progress");
|
|
607
|
+
|
|
608
|
+
const firstStep = buildWidgetLines([{
|
|
609
|
+
asyncId: "run-step-progress",
|
|
610
|
+
asyncDir: "/tmp/run-step",
|
|
611
|
+
status: "running",
|
|
612
|
+
agents: ["worker"],
|
|
613
|
+
stepsTotal: 1,
|
|
614
|
+
updatedAt: 20,
|
|
615
|
+
steps: [{ agent: "worker", status: "running", currentToolStartedAt: 10 }],
|
|
616
|
+
}], theme, 120);
|
|
617
|
+
const secondStep = buildWidgetLines([{
|
|
618
|
+
asyncId: "run-step-progress",
|
|
619
|
+
asyncDir: "/tmp/run-step",
|
|
620
|
+
status: "running",
|
|
621
|
+
agents: ["worker"],
|
|
622
|
+
stepsTotal: 1,
|
|
623
|
+
updatedAt: 20,
|
|
624
|
+
steps: [{ agent: "worker", status: "running", currentToolStartedAt: 11 }],
|
|
625
|
+
}], theme, 120);
|
|
626
|
+
assert.notEqual(
|
|
627
|
+
firstRunningGlyph(firstStep.find((line) => line.includes("Step 1/1")) ?? ""),
|
|
628
|
+
firstRunningGlyph(secondStep.find((line) => line.includes("Step 1/1")) ?? ""),
|
|
629
|
+
"step glyph should advance from changed step progress",
|
|
630
|
+
);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it("keeps running widget output stable when progress seed is unchanged", async () => {
|
|
634
|
+
const job = {
|
|
635
|
+
asyncId: "run-stable",
|
|
636
|
+
asyncDir: "/tmp/run",
|
|
637
|
+
status: "running",
|
|
638
|
+
agents: ["worker"],
|
|
639
|
+
startedAt: 1_000,
|
|
640
|
+
updatedAt: 3_000,
|
|
641
|
+
currentTool: "read",
|
|
642
|
+
currentToolStartedAt: 2_000,
|
|
643
|
+
lastActivityAt: 2_500,
|
|
644
|
+
};
|
|
645
|
+
const first = buildWidgetLines([job], theme, 120);
|
|
646
|
+
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
647
|
+
const second = buildWidgetLines([job], theme, 120);
|
|
648
|
+
|
|
649
|
+
assert.deepEqual(second, first);
|
|
650
|
+
assert.equal(firstGrapheme(first[1] ?? ""), firstGrapheme(second[1] ?? ""));
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it("does not animate queued-only widgets", async () => {
|
|
654
|
+
const ui = createUiContext();
|
|
655
|
+
renderWidget(ui.ctx as never, [{ asyncId: "queued-only", asyncDir: "/tmp/queued", status: "queued", agents: ["planner"] }]);
|
|
656
|
+
const initialWidgetCount = ui.widgets.length;
|
|
657
|
+
await new Promise((resolve) => setTimeout(resolve, 190));
|
|
658
|
+
assert.equal(ui.widgets.length, initialWidgetCount, "static queued widget should not refresh at animation cadence");
|
|
659
|
+
assert.equal(ui.renderRequests, 0);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it("clears legacy result row animation timers", async () => {
|
|
663
|
+
let ticks = 0;
|
|
664
|
+
const context = {
|
|
665
|
+
state: { subagentResultAnimationTimer: setInterval(() => { ticks += 1; }, 10) },
|
|
666
|
+
};
|
|
667
|
+
try {
|
|
668
|
+
clearLegacyResultAnimationTimer(context);
|
|
669
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
670
|
+
assert.equal(context.state.subagentResultAnimationTimer, undefined);
|
|
671
|
+
assert.equal(ticks, 0, "legacy timer should be cleared before it can tick");
|
|
672
|
+
} finally {
|
|
673
|
+
if (context.state.subagentResultAnimationTimer) clearInterval(context.state.subagentResultAnimationTimer);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it("does not refresh running widgets at animation cadence", async () => {
|
|
678
|
+
const ui = createUiContext();
|
|
679
|
+
renderWidget(ui.ctx as never, [{ asyncId: "run-static", asyncDir: "/tmp/run", status: "running", agents: ["scout"] }]);
|
|
680
|
+
const initialWidgetCount = ui.widgets.length;
|
|
681
|
+
await new Promise((resolve) => setTimeout(resolve, 190));
|
|
682
|
+
assert.equal(ui.widgets.length, initialWidgetCount, "running widget should wait for status updates instead of animation ticks");
|
|
683
|
+
assert.equal(ui.renderRequests, 0);
|
|
684
|
+
|
|
685
|
+
renderWidget(ui.ctx as never, []);
|
|
686
|
+
const afterClearCount = ui.widgets.length;
|
|
687
|
+
await new Promise((resolve) => setTimeout(resolve, 190));
|
|
688
|
+
assert.equal(ui.widgets.length, afterClearCount, "cleared widget should stay quiet");
|
|
689
|
+
assert.equal(ui.widgets.at(-1), undefined);
|
|
690
|
+
});
|
|
691
|
+
});
|