pi-crew 0.9.68 → 0.10.2
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 +222 -0
- package/NOTICE.md +21 -0
- package/README.md +44 -2
- package/agents/analyst.md +1 -1
- package/agents/cold-verifier.md +3 -1
- package/agents/critic.md +1 -1
- package/agents/executor.md +1 -1
- package/agents/explorer.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/verifier.md +1 -1
- package/agents/writer.md +1 -1
- package/dist/index.mjs +68113 -60774
- package/docs/README.md +2 -0
- package/docs/actions-reference.md +31 -0
- package/docs/commands-reference.md +17 -6
- package/docs/resource-formats.md +13 -0
- package/package.json +4 -2
- package/schema.json +503 -91
- package/scripts/resource-sampler.mjs +36 -2
- package/skills/requirements-to-task-packet/SKILL.md +26 -0
- package/skills/widget-rendering/SKILL.md +7 -7
- package/src/agents/agent-config.ts +2 -1
- package/src/agents/discover-agents.ts +23 -14
- package/src/config/config-merge.ts +183 -0
- package/src/config/config-validation.ts +687 -0
- package/src/config/config.ts +22 -864
- package/src/config/defaults.ts +43 -2
- package/src/config/drift-detector.ts +1 -1
- package/src/config/env-vars.ts +691 -0
- package/src/config/role-tools.ts +11 -9
- package/src/config/sanitize-project-config.ts +172 -0
- package/src/config/types.ts +49 -1
- package/src/extension/async-notifier.ts +25 -2
- package/src/extension/crew-cleanup.ts +13 -0
- package/src/extension/crew-vibes/config.ts +2 -1
- package/src/extension/crew-vibes/footer.ts +19 -0
- package/src/extension/crew-vibes/index.ts +11 -1
- package/src/extension/plan-orchestrate.ts +132 -0
- package/src/extension/register.ts +8 -0
- package/src/extension/registration/command-registration.ts +1 -0
- package/src/extension/registration/commands/dashboard.ts +158 -0
- package/src/extension/registration/commands/index.ts +35 -0
- package/src/extension/registration/commands/manage.ts +303 -0
- package/src/extension/registration/commands/run.ts +228 -0
- package/src/extension/registration/commands/shared.ts +639 -0
- package/src/extension/registration/commands/status.ts +60 -0
- package/src/extension/registration/commands.ts +13 -1224
- package/src/extension/registration/foreground-run-controller.ts +10 -2
- package/src/extension/registration/lifecycle-handlers.ts +178 -17
- package/src/extension/registration/runtime-cleanup.ts +23 -5
- package/src/extension/registration/subagent-tools.ts +218 -9
- package/src/extension/registration/team-tool.ts +5 -1
- package/src/extension/registration/ui.ts +5 -4
- package/src/extension/rpc-hmac.ts +5 -3
- package/src/extension/team-tool/api/heartbeat.ts +47 -10
- package/src/extension/team-tool/api/plan-approval.ts +9 -0
- package/src/extension/team-tool/api/task-claims.ts +109 -40
- package/src/extension/team-tool/cancel.ts +84 -50
- package/src/extension/team-tool/dispatch/index.ts +1 -0
- package/src/extension/team-tool/dispatch/run.ts +4 -1
- package/src/extension/team-tool/doctor.ts +103 -1
- package/src/extension/team-tool/orchestrate.ts +66 -1
- package/src/extension/team-tool/plans.ts +192 -0
- package/src/extension/team-tool/respond.ts +197 -65
- package/src/extension/team-tool/run-deadline.ts +35 -3
- package/src/extension/team-tool/run-intent.ts +63 -0
- package/src/extension/team-tool/run.ts +74 -20
- package/src/extension/team-tool/status.ts +84 -26
- package/src/extension/team-tool.ts +11 -2
- package/src/hooks/registry.ts +1 -6
- package/src/i18n.ts +9 -0
- package/src/prompt/prompt-runtime.ts +521 -2
- package/src/prompt/worker-events-channel.ts +173 -0
- package/src/runtime/README.md +8 -8
- package/src/runtime/async-runner.ts +7 -3
- package/src/runtime/background-runner.ts +42 -14
- package/src/runtime/broker/broker-issuer.ts +9 -2
- package/src/runtime/broker/crew-broker-tokens.ts +43 -6
- package/src/runtime/broker/crew-broker.ts +838 -10
- package/src/runtime/broker/wait-status-cache.ts +157 -0
- package/src/runtime/budget-enforcement.ts +281 -0
- package/src/runtime/child-pi/child-pi-constants.ts +8 -0
- package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
- package/src/runtime/child-pi/child-pi-streams.ts +21 -1
- package/src/runtime/child-pi/child-pi-timers.ts +324 -0
- package/src/runtime/child-pi/child-pi.ts +97 -201
- package/src/runtime/child-pi/mock-fixtures.ts +16 -2
- package/src/runtime/crew-agent-records.ts +259 -14
- package/src/runtime/delegate-spawn.ts +148 -0
- package/src/runtime/detached-run-results.ts +90 -0
- package/src/runtime/deterministic-ast.ts +2 -1
- package/src/runtime/dispatch-batch.ts +945 -0
- package/src/runtime/finalize-run.ts +557 -0
- package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
- package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
- package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
- package/src/runtime/group-join.ts +11 -125
- package/src/runtime/live-session/live-session-runtime.ts +26 -1
- package/src/runtime/merge-gate.ts +32 -10
- package/src/runtime/merge-loop.ts +130 -0
- package/src/runtime/model/model-budget-summary.ts +53 -0
- package/src/runtime/model/model-fallback.ts +36 -2
- package/src/runtime/model/pi-args.ts +10 -0
- package/src/runtime/model/provider-extensions.ts +10 -0
- package/src/runtime/orphan-worker-registry.ts +1 -1
- package/src/runtime/output/output-validator.ts +45 -0
- package/src/runtime/parent-guard.ts +3 -1
- package/src/runtime/peer-dep.ts +2 -1
- package/src/runtime/per-write-validator.ts +0 -5
- package/src/runtime/pi-spawn.ts +61 -15
- package/src/runtime/plan-approval.ts +125 -0
- package/src/runtime/plan-replan.ts +151 -0
- package/src/runtime/process-status.ts +16 -1
- package/src/runtime/recovery/checkpoint.ts +0 -18
- package/src/runtime/recovery/crash-recovery.ts +111 -46
- package/src/runtime/run-tracker.ts +77 -10
- package/src/runtime/scheduler-context.ts +98 -0
- package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
- package/src/runtime/scheduling/global-worker-cap.ts +2 -1
- package/src/runtime/scheduling/nested-slots.ts +70 -0
- package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
- package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
- package/src/runtime/settings-store.ts +219 -0
- package/src/runtime/spawn-policy.ts +217 -0
- package/src/runtime/stale-reconciler.ts +87 -6
- package/src/runtime/subagent-manager.ts +25 -1
- package/src/runtime/task-output-context.ts +230 -9
- package/src/runtime/task-packet.ts +23 -1
- package/src/runtime/task-runner/child-executor.ts +106 -7
- package/src/runtime/task-runner/post-execution.ts +125 -1
- package/src/runtime/task-runner/pre-execution.ts +39 -1
- package/src/runtime/task-runner/prompt-builder.ts +51 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
- package/src/runtime/task-runner/spec-evidence.ts +403 -0
- package/src/runtime/task-runner/state-helpers.ts +26 -24
- package/src/runtime/task-runner.ts +11 -0
- package/src/runtime/team-runner.ts +132 -1673
- package/src/runtime/verification/spec-sandbox.ts +255 -0
- package/src/runtime/verification/verification-gates.ts +3 -2
- package/src/runtime/verification/verification-worktree.ts +2 -1
- package/src/runtime/workflow-phase-advance.ts +100 -0
- package/src/runtime/workspace-tree.ts +9 -0
- package/src/schema/config-schema.ts +66 -26
- package/src/schema/sensitive-config-paths.ts +64 -0
- package/src/schema/team-tool-schema.ts +13 -3
- package/src/state/README.md +4 -10
- package/src/state/atomic-write.ts +20 -3
- package/src/state/contracts.ts +38 -0
- package/src/state/coordination/mailbox.ts +12 -2
- package/src/state/event-log/cursor.ts +223 -0
- package/src/state/event-log/event-log-rotation.ts +12 -4
- package/src/state/event-log/event-log.ts +152 -369
- package/src/state/event-log/sequence-cache.ts +373 -0
- package/src/state/event-log/worker-atomic-writer.ts +2 -1
- package/src/state/stores/active-run-registry.ts +3 -2
- package/src/state/stores/manifest-io.ts +237 -0
- package/src/state/stores/ownership-map.ts +162 -0
- package/src/state/stores/plan-store.ts +241 -0
- package/src/state/stores/run-cache.ts +0 -90
- package/src/state/stores/spec-store.ts +189 -0
- package/src/state/stores/state-store.ts +139 -232
- package/src/state/types.ts +199 -0
- package/src/ui/dashboard-panes/plan-pane.ts +136 -0
- package/src/ui/dashboard-panes/progress-pane.ts +6 -0
- package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
- package/src/ui/dock-footer.ts +49 -0
- package/src/ui/heartbeat-aggregator.ts +9 -1
- package/src/ui/inline-panel/agent-pane.ts +375 -0
- package/src/ui/inline-panel/agent-transcript.ts +338 -0
- package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
- package/src/ui/inline-panel/crew-editor.ts +192 -0
- package/src/ui/inline-panel/index.ts +290 -0
- package/src/ui/inline-panel/panel-rows.ts +37 -0
- package/src/ui/inline-panel/panel-selection.ts +157 -0
- package/src/ui/inline-panel/panel-store.ts +111 -0
- package/src/ui/inline-panel/view-session-store.ts +36 -0
- package/src/ui/keybinding-map.ts +54 -13
- package/src/ui/pi-ui-compat.ts +9 -0
- package/src/ui/powerbar-publisher.ts +52 -1
- package/src/ui/run-dashboard.ts +31 -5
- package/src/ui/run-snapshot-cache.ts +57 -30
- package/src/ui/snapshot-types.ts +6 -1
- package/src/ui/widget/index.ts +176 -22
- package/src/ui/widget/task-list.ts +198 -0
- package/src/ui/widget/widget-formatters.ts +240 -4
- package/src/ui/widget/widget-renderer.ts +243 -38
- package/src/ui/widget/widget-types.ts +11 -0
- package/src/utils/child-process-shield.ts +106 -0
- package/src/utils/file-coalescer.ts +0 -4
- package/src/utils/fs-errno.ts +66 -0
- package/src/utils/fs-watch.ts +1 -1
- package/src/utils/internal-error.ts +3 -1
- package/src/utils/paths.ts +11 -3
- package/src/utils/redaction.ts +7 -0
- package/src/utils/safe-abort.ts +45 -0
- package/src/utils/task-name-generator.ts +1 -8
- package/src/workflows/discover-workflows.ts +20 -2
- package/src/workflows/validate-workflow.ts +7 -1
- package/src/workflows/workflow-config.ts +17 -0
- package/src/workflows/workflow-serializer.ts +3 -0
- package/src/worktree/worktree-manager.ts +22 -0
- package/workflows/default.workflow.md +36 -26
- package/workflows/strict-fast-fix.workflow.md +26 -0
- package/src/agents/agent-search.ts +0 -98
- package/src/benchmark/benchmark-runner.ts +0 -313
- package/src/benchmark/feedback-loop.ts +0 -73
- package/src/config/resilient-parser.ts +0 -117
- package/src/extension/crew-vibes/cat-frames.ts +0 -18
- package/src/extension/result-watcher.ts +0 -139
- package/src/observability/exporters/prometheus-exporter.ts +0 -54
- package/src/observability/metric-retention.ts +0 -64
- package/src/runtime/compaction/compaction-summary.ts +0 -278
- package/src/runtime/errors/crew-errors.ts +0 -162
- package/src/runtime/live-session/intercom-bridge.ts +0 -187
- package/src/runtime/loop-gates.ts +0 -128
- package/src/runtime/metric-parser.ts +0 -36
- package/src/runtime/output/stream-preview.ts +0 -184
- package/src/runtime/output/tool-progress.ts +0 -278
- package/src/runtime/phase-tracker.ts +0 -385
- package/src/runtime/pipeline-runner.ts +0 -523
- package/src/runtime/process/process-lifecycle.ts +0 -491
- package/src/runtime/recovery/retry-runner.ts +0 -330
- package/src/runtime/run-drift.ts +0 -219
- package/src/runtime/task-quality.ts +0 -199
- package/src/runtime/task-runner/run-projection.ts +0 -128
- package/src/runtime/verification/post-checks.ts +0 -142
- package/src/state/coordination/schedule.ts +0 -166
- package/src/state/event-log/jsonl-writer.ts +0 -115
- package/src/state/hook-instinct-bridge.ts +0 -94
- package/src/state/hook-integrations.ts +0 -51
- package/src/state/session-state-map.ts +0 -51
- package/src/state/stores/blob-store.ts +0 -308
- package/src/state/stores/instinct-store.ts +0 -275
- package/src/state/stores/observation-store.ts +0 -176
- package/src/state/tiered-eval.ts +0 -480
- package/src/state/types-eval.ts +0 -58
- package/src/tools/safe-bash-extension.ts +0 -54
- package/src/tools/safe-bash.ts +0 -505
- package/src/ui/agent-management-overlay.ts +0 -160
- package/src/ui/crew-footer.ts +0 -102
- package/src/ui/crew-select-list.ts +0 -114
- package/src/ui/dashboard-panes/capability-pane.ts +0 -77
- package/src/ui/transcript-entries.ts +0 -256
- package/src/utils/conflict-detect.ts +0 -721
- package/src/utils/fingerprint.ts +0 -180
- package/src/utils/gh-protocol.ts +0 -556
- package/src/utils/project-detector.ts +0 -160
- package/src/utils/sse-parser.ts +0 -131
- package/src/workflows/cost-estimator.ts +0 -34
- package/src/workflows/intermediate-store.ts +0 -166
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* panel-store.ts — process-wide state shared by the inline panel's two halves.
|
|
3
|
+
*
|
|
4
|
+
* The editor wrapper mutates the cursor; the widget renderer and the transcript
|
|
5
|
+
* pane read it. Neither can own the state (the editor is created by pi's
|
|
6
|
+
* factory, the widget by another), so it lives here as a small observable
|
|
7
|
+
* singleton — the same shape pi-subtask keeps in its closure.
|
|
8
|
+
*
|
|
9
|
+
* Row projection is injected rather than computed here: the widget already owns
|
|
10
|
+
* the manifest/snapshot caches that produce the run list, and duplicating that
|
|
11
|
+
* read on every keypress would put disk I/O on the input path.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { PanelRow, PanelSelection, PanelTarget } from "./panel-selection.ts";
|
|
15
|
+
import { isAgentSelection } from "./panel-selection.ts";
|
|
16
|
+
|
|
17
|
+
let selection: PanelSelection = null;
|
|
18
|
+
let viewed: PanelTarget | undefined;
|
|
19
|
+
let rowsProvider: (() => PanelRow[]) | undefined;
|
|
20
|
+
const listeners = new Set<() => void>();
|
|
21
|
+
|
|
22
|
+
/** Install the row projection. Called once during panel wiring. */
|
|
23
|
+
export function setPanelRowsProvider(provider: (() => PanelRow[]) | undefined): void {
|
|
24
|
+
rowsProvider = provider;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Navigable rows, freshly projected on each call.
|
|
29
|
+
*
|
|
30
|
+
* A provider throw must never break input handling — a transient manifest read
|
|
31
|
+
* failure would otherwise make the panel swallow keys — so it degrades to an
|
|
32
|
+
* empty list, which the editor treats as "nothing to navigate".
|
|
33
|
+
*/
|
|
34
|
+
export function panelRows(): PanelRow[] {
|
|
35
|
+
if (!rowsProvider) return [];
|
|
36
|
+
try {
|
|
37
|
+
return rowsProvider();
|
|
38
|
+
} catch {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getPanelSelection(): PanelSelection {
|
|
44
|
+
return selection;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function setPanelSelection(next: PanelSelection): void {
|
|
48
|
+
if (next === selection) return;
|
|
49
|
+
if (isAgentSelection(selection) && isAgentSelection(next) && selection.runId === next.runId && selection.taskId === next.taskId) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
selection = next;
|
|
53
|
+
notifyPanelChange();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getViewedAgent(): PanelTarget | undefined {
|
|
57
|
+
return viewed;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function setViewedAgent(next: PanelTarget | undefined): void {
|
|
61
|
+
if (viewed === next) return;
|
|
62
|
+
if (viewed && next && viewed.runId === next.runId && viewed.taskId === next.taskId) return;
|
|
63
|
+
viewed = next;
|
|
64
|
+
notifyPanelChange();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** True while the panel holds the cursor, i.e. keys are not going to the editor. */
|
|
68
|
+
export function isPanelFocused(): boolean {
|
|
69
|
+
return selection !== null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** What the widget renderer needs, in one read. */
|
|
73
|
+
export function panelDisplayState(): {
|
|
74
|
+
selectedTaskId: string | undefined;
|
|
75
|
+
viewedTaskId: string | undefined;
|
|
76
|
+
focused: boolean;
|
|
77
|
+
} {
|
|
78
|
+
return {
|
|
79
|
+
selectedTaskId: isAgentSelection(selection) ? selection.taskId : undefined,
|
|
80
|
+
viewedTaskId: viewed?.taskId,
|
|
81
|
+
// Cursor-driven: uncapping the row budget while the user is typing
|
|
82
|
+
// into the pane (viewed set, selection null) would jerk the layout on
|
|
83
|
+
// every pane open. The cursor entry is when the full list is needed.
|
|
84
|
+
focused: selection !== null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function subscribePanelChange(listener: () => void): () => void {
|
|
89
|
+
listeners.add(listener);
|
|
90
|
+
return () => {
|
|
91
|
+
listeners.delete(listener);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function notifyPanelChange(): void {
|
|
96
|
+
for (const listener of [...listeners]) {
|
|
97
|
+
try {
|
|
98
|
+
listener();
|
|
99
|
+
} catch {
|
|
100
|
+
// A failing repaint listener must not stop the others.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Session teardown / test isolation. */
|
|
106
|
+
export function resetPanelStore(): void {
|
|
107
|
+
selection = null;
|
|
108
|
+
viewed = undefined;
|
|
109
|
+
rowsProvider = undefined;
|
|
110
|
+
listeners.clear();
|
|
111
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* view-session-store.ts — session-switch guard for run protection.
|
|
3
|
+
*
|
|
4
|
+
* Agent views are in-document panes (see agent-pane.ts / inline-panel's
|
|
5
|
+
* openPane): viewing an agent NEVER switches, resumes, or tears down a
|
|
6
|
+
* session. This store is the one piece of session-switch machinery that
|
|
7
|
+
* remains, and it protects runs from switches the USER makes (/resume, /new,
|
|
8
|
+
* /fork): a switch tears the current session down via session.abort(), which
|
|
9
|
+
* fires the abort signal of any tool call still in flight — a foreground
|
|
10
|
+
* team run created by that tool is linked to the turn's abort, so ANY switch
|
|
11
|
+
* seconds after a run starts used to kill the run's workers ("Child Pi
|
|
12
|
+
* exited with 143" → run cancelled). While the guard is set, the
|
|
13
|
+
* caller-abort propagation is suppressed (run-deadline.ts).
|
|
14
|
+
*
|
|
15
|
+
* Set by the session_before_switch handler (runs before teardown's abort),
|
|
16
|
+
* cleared on the next session_start (the switch landed) and on reconcile.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
let sessionSwitchInFlight = false;
|
|
20
|
+
|
|
21
|
+
export function markSessionSwitchInFlight(): void {
|
|
22
|
+
sessionSwitchInFlight = true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function clearSessionSwitchInFlight(): void {
|
|
26
|
+
sessionSwitchInFlight = false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isSessionSwitchInFlight(): boolean {
|
|
30
|
+
return sessionSwitchInFlight;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Test isolation. */
|
|
34
|
+
export function resetCrewViewSessionState(): void {
|
|
35
|
+
sessionSwitchInFlight = false;
|
|
36
|
+
}
|
package/src/ui/keybinding-map.ts
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import * as fs from "node:fs";
|
|
27
27
|
import * as path from "node:path";
|
|
28
28
|
import { type KeyId, matchesKey } from "@earendil-works/pi-tui";
|
|
29
|
+
import { getCrewEnv } from "../config/env-vars.ts";
|
|
29
30
|
import { keyOf } from "./key-utils.ts";
|
|
30
31
|
|
|
31
32
|
export const DASHBOARD_KEYS = {
|
|
@@ -52,6 +53,7 @@ export const DASHBOARD_KEYS = {
|
|
|
52
53
|
output: ["4"],
|
|
53
54
|
health: ["5"],
|
|
54
55
|
metrics: ["6"],
|
|
56
|
+
plan: ["7"],
|
|
55
57
|
},
|
|
56
58
|
navigation: { up: ["k", "up"], down: ["j", "down"] },
|
|
57
59
|
mailbox: {
|
|
@@ -63,6 +65,7 @@ export const DASHBOARD_KEYS = {
|
|
|
63
65
|
openDetail: ["\r", "\n"],
|
|
64
66
|
},
|
|
65
67
|
health: { recovery: ["R"], killStale: ["K"], diagnosticExport: ["D"] },
|
|
68
|
+
plan: { approve: ["A"], deny: ["n"], diff: ["X"] },
|
|
66
69
|
notification: { dismissAll: ["H"] },
|
|
67
70
|
} as const;
|
|
68
71
|
|
|
@@ -70,7 +73,7 @@ export const DASHBOARD_KEYS = {
|
|
|
70
73
|
* Pane identifiers that can scope a binding. `undefined` means the binding
|
|
71
74
|
* fires in every pane.
|
|
72
75
|
*/
|
|
73
|
-
export type ActivePane = "agents" | "progress" | "mailbox" | "output" | "health" | "metrics";
|
|
76
|
+
export type ActivePane = "agents" | "progress" | "mailbox" | "output" | "health" | "metrics" | "plan";
|
|
74
77
|
|
|
75
78
|
/**
|
|
76
79
|
* A single keybinding: the keys that trigger it, the action it produces, and
|
|
@@ -81,8 +84,8 @@ export type ActivePane = "agents" | "progress" | "mailbox" | "output" | "health"
|
|
|
81
84
|
export interface KeyBinding {
|
|
82
85
|
readonly keys: readonly string[];
|
|
83
86
|
readonly action: DashboardKeyAction;
|
|
84
|
-
/** When set, the binding
|
|
85
|
-
readonly pane?:
|
|
87
|
+
/** When set, the binding fires only in these pane(s). */
|
|
88
|
+
readonly pane?: PaneScope;
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
export type DashboardKeyAction =
|
|
@@ -106,12 +109,16 @@ export type DashboardKeyAction =
|
|
|
106
109
|
| "pane-output"
|
|
107
110
|
| "pane-health"
|
|
108
111
|
| "pane-metrics"
|
|
112
|
+
| "pane-plan"
|
|
113
|
+
| "plan-diff"
|
|
109
114
|
| "up"
|
|
110
115
|
| "down"
|
|
111
116
|
| "mailbox-detail"
|
|
112
117
|
| "health-recovery"
|
|
113
118
|
| "health-kill-stale"
|
|
114
119
|
| "health-diagnostic-export"
|
|
120
|
+
| "plan-approve"
|
|
121
|
+
| "plan-deny"
|
|
115
122
|
| "notifications-dismiss";
|
|
116
123
|
|
|
117
124
|
/**
|
|
@@ -125,14 +132,17 @@ export type DashboardKeyAction =
|
|
|
125
132
|
* 2. `mailbox-detail` (\r, \n) is pane-scoped to mailbox and MUST precede
|
|
126
133
|
* `select` (which also binds \r, \n) so Enter opens the detail instead of
|
|
127
134
|
* triggering select while in the mailbox pane.
|
|
128
|
-
* 3. `health-*` are pane-scoped
|
|
135
|
+
* 3. `health-*` and `plan-*` are pane-scoped (health / progress).
|
|
129
136
|
* 4. `notifications-dismiss` (H) is global.
|
|
130
137
|
* 5. `select`, then the root actions, pane switches, and navigation.
|
|
131
138
|
*
|
|
132
139
|
* NOTE: mailbox action keys A/N/C/P/X (ack/nudge/compose/preview/ackAll) are
|
|
133
|
-
* intentionally NOT
|
|
134
|
-
* reservation but are handled by the mailbox
|
|
135
|
-
* not by the dashboard dispatch.
|
|
140
|
+
* intentionally NOT dispatched for the mailbox pane by this table. They live
|
|
141
|
+
* in `DASHBOARD_KEYS.mailbox` for reservation but are handled by the mailbox
|
|
142
|
+
* overlay's own `handleInput`, not by the dashboard dispatch. The `plan`
|
|
143
|
+
* group reuses uppercase "A" (approve) pane-scoped to "progress" — it never
|
|
144
|
+
* fires while the mailbox pane (or the mailbox-detail overlay) owns input,
|
|
145
|
+
* so `mailbox.ack` behavior is unchanged.
|
|
136
146
|
*/
|
|
137
147
|
const DEFAULT_BINDINGS: readonly KeyBinding[] = [
|
|
138
148
|
{ keys: DASHBOARD_KEYS.close, action: "close" },
|
|
@@ -157,6 +167,22 @@ const DEFAULT_BINDINGS: readonly KeyBinding[] = [
|
|
|
157
167
|
action: "health-diagnostic-export",
|
|
158
168
|
pane: "health",
|
|
159
169
|
},
|
|
170
|
+
{
|
|
171
|
+
keys: DASHBOARD_KEYS.plan.approve,
|
|
172
|
+
action: "plan-approve",
|
|
173
|
+
// WP-7: shared by the progress banner and the Plan pane (pane 7).
|
|
174
|
+
pane: ["progress", "plan"],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
keys: DASHBOARD_KEYS.plan.deny,
|
|
178
|
+
action: "plan-deny",
|
|
179
|
+
pane: ["progress", "plan"],
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
keys: DASHBOARD_KEYS.plan.diff,
|
|
183
|
+
action: "plan-diff",
|
|
184
|
+
pane: "plan",
|
|
185
|
+
},
|
|
160
186
|
{
|
|
161
187
|
keys: DASHBOARD_KEYS.notification.dismissAll,
|
|
162
188
|
action: "notifications-dismiss",
|
|
@@ -179,6 +205,7 @@ const DEFAULT_BINDINGS: readonly KeyBinding[] = [
|
|
|
179
205
|
{ keys: DASHBOARD_KEYS.pane.output, action: "pane-output" },
|
|
180
206
|
{ keys: DASHBOARD_KEYS.pane.health, action: "pane-health" },
|
|
181
207
|
{ keys: DASHBOARD_KEYS.pane.metrics, action: "pane-metrics" },
|
|
208
|
+
{ keys: DASHBOARD_KEYS.pane.plan, action: "pane-plan" },
|
|
182
209
|
{ keys: DASHBOARD_KEYS.navigation.up, action: "up" },
|
|
183
210
|
{ keys: DASHBOARD_KEYS.navigation.down, action: "down" },
|
|
184
211
|
];
|
|
@@ -202,6 +229,7 @@ const KEY_RESERVED = new Set<string>([
|
|
|
202
229
|
...Object.values(DASHBOARD_KEYS.navigation).flat(),
|
|
203
230
|
...Object.values(DASHBOARD_KEYS.mailbox).flat(),
|
|
204
231
|
...Object.values(DASHBOARD_KEYS.health).flat(),
|
|
232
|
+
...Object.values(DASHBOARD_KEYS.plan).flat(),
|
|
205
233
|
...Object.values(DASHBOARD_KEYS.notification).flat(),
|
|
206
234
|
]);
|
|
207
235
|
|
|
@@ -253,9 +281,22 @@ function parseKeybindingOverride(raw: unknown): KeybindingOverride {
|
|
|
253
281
|
* bindings fire at once (so a shared key is genuinely ambiguous). Global
|
|
254
282
|
* (`undefined`) matches anything; two different concrete panes never overlap.
|
|
255
283
|
*/
|
|
256
|
-
|
|
284
|
+
/** Pane scopes — a binding may fire in ONE pane, SEVERAL panes (WP-7: plan
|
|
285
|
+
* approval keys are shared by progress + plan panes), or every pane
|
|
286
|
+
* (undefined). */
|
|
287
|
+
export type PaneScope = ActivePane | readonly ActivePane[];
|
|
288
|
+
|
|
289
|
+
function paneScopeMatches(scope: PaneScope | undefined, pane: ActivePane | undefined): boolean {
|
|
290
|
+
if (scope === undefined) return true;
|
|
291
|
+
if (Array.isArray(scope)) return pane !== undefined && scope.includes(pane);
|
|
292
|
+
return scope === pane;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function paneScopesCompatible(a: PaneScope | undefined, b: PaneScope | undefined): boolean {
|
|
257
296
|
if (a === undefined || b === undefined) return true;
|
|
258
|
-
|
|
297
|
+
const as = Array.isArray(a) ? a : [a];
|
|
298
|
+
const bs = Array.isArray(b) ? b : [b];
|
|
299
|
+
return as.some((x) => bs.includes(x));
|
|
259
300
|
}
|
|
260
301
|
|
|
261
302
|
interface EffectiveBindingsResult {
|
|
@@ -311,7 +352,7 @@ function readConfigKeybindings(cwd: string): KeybindingOverride {
|
|
|
311
352
|
|
|
312
353
|
/** Read the `PI_CREW_KEYBINDINGS` env var (JSON object string). */
|
|
313
354
|
function readEnvKeybindings(): KeybindingOverride {
|
|
314
|
-
const raw =
|
|
355
|
+
const raw = getCrewEnv(KEYBINDINGS_ENV);
|
|
315
356
|
if (!raw) return {};
|
|
316
357
|
try {
|
|
317
358
|
return parseKeybindingOverride(JSON.parse(raw));
|
|
@@ -344,7 +385,7 @@ let _overrideWarnings: readonly string[] = [];
|
|
|
344
385
|
* mtime, cwd); a single `statSync` per call detects on-disk config changes.
|
|
345
386
|
*/
|
|
346
387
|
function getEffectiveBindings(cwd: string = process.cwd()): readonly KeyBinding[] {
|
|
347
|
-
const envRaw =
|
|
388
|
+
const envRaw = getCrewEnv(KEYBINDINGS_ENV);
|
|
348
389
|
const configMtime = configKeybindingsMtime(cwd);
|
|
349
390
|
if (_effectiveCache && _effectiveCache.env === envRaw && _effectiveCache.configMtime === configMtime && _effectiveCache.cwd === cwd) {
|
|
350
391
|
return _effectiveCache.bindings;
|
|
@@ -402,7 +443,7 @@ export function dashboardActionForKey(data: string, activePane?: ActivePane): Da
|
|
|
402
443
|
// Pass 1 — exact string match (case-sensitive). Handles literal ASCII
|
|
403
444
|
// keystrokes ('d', 'D', 'q', 'S', …) and preserves their distinct meanings.
|
|
404
445
|
for (const binding of BINDINGS) {
|
|
405
|
-
if (binding.pane
|
|
446
|
+
if (!paneScopeMatches(binding.pane, activePane)) continue;
|
|
406
447
|
if (binding.keys.includes(data)) return binding.action;
|
|
407
448
|
}
|
|
408
449
|
// Pass 2 — terminal-aware match for escape sequences / canonical KeyIds.
|
|
@@ -411,7 +452,7 @@ export function dashboardActionForKey(data: string, activePane?: ActivePane): Da
|
|
|
411
452
|
// legacy CSI, app-cursor-mode, and Kitty-protocol variants uniformly.
|
|
412
453
|
const key = keyOf(data);
|
|
413
454
|
for (const binding of BINDINGS) {
|
|
414
|
-
if (binding.pane
|
|
455
|
+
if (!paneScopeMatches(binding.pane, activePane)) continue;
|
|
415
456
|
for (const candidate of binding.keys) {
|
|
416
457
|
if (key === candidate) return binding.action;
|
|
417
458
|
if (matchesKey(data, candidate as KeyId)) return binding.action;
|
package/src/ui/pi-ui-compat.ts
CHANGED
|
@@ -46,6 +46,15 @@ export function setExtensionWidget(
|
|
|
46
46
|
ctx.ui.setWidget(key, content as never, widgetOptions as WidgetOptions);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Map the crew widget placement onto pi's `WidgetPlacement`. `"bottom"` is a
|
|
51
|
+
* crew-only placement (rendered inside the crew-vibes footer, below the quota
|
|
52
|
+
* lines); when a raw widget slot is involved it falls back to `belowEditor`.
|
|
53
|
+
*/
|
|
54
|
+
export function toPiWidgetPlacement(placement: "aboveEditor" | "belowEditor" | "bottom"): "aboveEditor" | "belowEditor" {
|
|
55
|
+
return placement === "bottom" ? "belowEditor" : placement;
|
|
56
|
+
}
|
|
57
|
+
|
|
49
58
|
type FooterFactory = (tui: unknown, theme: unknown, footerData: unknown) => unknown;
|
|
50
59
|
|
|
51
60
|
/** Install a custom footer component, or pass `undefined` to restore pi's built-in footer.
|
|
@@ -4,6 +4,7 @@ import { listRecentRuns } from "../extension/run-index.ts";
|
|
|
4
4
|
import { readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
5
5
|
import { listLiveAgents, listLiveAgentsByWorkspace } from "../runtime/live-session/live-agent-manager.ts";
|
|
6
6
|
import type { ManifestCache } from "../runtime/manifest-cache.ts";
|
|
7
|
+
import { isPlanApprovalPending } from "../runtime/plan-approval.ts";
|
|
7
8
|
import { isDisplayActiveRun } from "../runtime/process-status.ts";
|
|
8
9
|
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
9
10
|
import { aggregateUsage } from "../state/usage.ts";
|
|
@@ -87,6 +88,10 @@ export function registerPiCrewPowerbarSegments(events: EventBus, config?: CrewUi
|
|
|
87
88
|
id: "pi-crew-steps",
|
|
88
89
|
label: "pi-crew workflow steps",
|
|
89
90
|
});
|
|
91
|
+
safeEmit(events, "powerbar:register-segment", {
|
|
92
|
+
id: "pi-crew-plan",
|
|
93
|
+
label: "pi-crew plan approval",
|
|
94
|
+
});
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
export function updatePiCrewPowerbar(
|
|
@@ -136,11 +141,40 @@ interface ActiveItem {
|
|
|
136
141
|
* Build the workflow steps segment showing: ✓explore › →plan › ○execute › ○verify
|
|
137
142
|
* with the current/active step highlighted using → arrow.
|
|
138
143
|
*/
|
|
139
|
-
function buildStepsPayload(active: ActiveItem[], allTasks: TeamTaskState[]): PowerbarPayloadShape {
|
|
144
|
+
export function buildStepsPayload(active: ActiveItem[], allTasks: TeamTaskState[]): PowerbarPayloadShape {
|
|
140
145
|
if (!active.length) {
|
|
141
146
|
return { id: "pi-crew-steps" };
|
|
142
147
|
}
|
|
143
148
|
const run = active[0]!.run;
|
|
149
|
+
// WP-7 (R7): plan-carrying runs show PLAN PHASES instead of workflow steps —
|
|
150
|
+
// the plan is the actual execution shape (a re-plan revision switches the
|
|
151
|
+
// segment without waiting for workflow steps that no longer apply).
|
|
152
|
+
const plans = active[0]!.snapshot?.plans;
|
|
153
|
+
if (plans && plans.length > 0) {
|
|
154
|
+
const current = plans[plans.length - 1]!;
|
|
155
|
+
const itemById = new Map(current.items.map((i) => [i.id, i]));
|
|
156
|
+
const statusOf = (ids: string[]): "completed" | "running" | "pending" => {
|
|
157
|
+
const items = ids.map((id) => itemById.get(id)?.status).filter(Boolean) as string[];
|
|
158
|
+
if (items.length && items.every((s) => s === "done")) return "completed";
|
|
159
|
+
if (items.some((s) => s === "active")) return "running";
|
|
160
|
+
return "pending";
|
|
161
|
+
};
|
|
162
|
+
const parts = current.phases.map((phase) => {
|
|
163
|
+
const status = phase.status === "done" ? "completed" : statusOf(phase.itemIds);
|
|
164
|
+
const icon = status === "completed" ? "✓" : status === "running" ? "→" : "○";
|
|
165
|
+
const name = phase.title.length > 10 ? `${phase.title.slice(0, 9)}…` : phase.title;
|
|
166
|
+
return `${icon}${name}`;
|
|
167
|
+
});
|
|
168
|
+
if (parts.length) {
|
|
169
|
+
const hasRunning = parts.some((p) => p.startsWith("→"));
|
|
170
|
+
const allComplete = parts.every((p) => p.startsWith("✓"));
|
|
171
|
+
return {
|
|
172
|
+
id: "pi-crew-steps",
|
|
173
|
+
text: `P${current.version} ${parts.join(" › ")}`,
|
|
174
|
+
color: allComplete ? "success" : hasRunning ? "accent" : "dim",
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
144
178
|
const workflowName = run.workflow ?? "default";
|
|
145
179
|
// Load workflow steps
|
|
146
180
|
const workflows = allWorkflows(discoverWorkflows(run.cwd));
|
|
@@ -206,6 +240,7 @@ class PowerbarPublisher {
|
|
|
206
240
|
#lastActiveKey: string | undefined;
|
|
207
241
|
#lastProgressKey: string | undefined;
|
|
208
242
|
#lastStepsKey: string | undefined;
|
|
243
|
+
#lastPlanKey: string | undefined;
|
|
209
244
|
#latestArgs: PowerbarUpdateArgs | null = null;
|
|
210
245
|
readonly #coalescer: RenderCoalescer;
|
|
211
246
|
|
|
@@ -279,9 +314,11 @@ class PowerbarPublisher {
|
|
|
279
314
|
this.#lastActiveKey = undefined;
|
|
280
315
|
this.#lastProgressKey = undefined;
|
|
281
316
|
this.#lastStepsKey = undefined;
|
|
317
|
+
this.#lastPlanKey = undefined;
|
|
282
318
|
safeEmit(events, "powerbar:update", { id: "pi-crew-active" });
|
|
283
319
|
safeEmit(events, "powerbar:update", { id: "pi-crew-progress" });
|
|
284
320
|
safeEmit(events, "powerbar:update", { id: "pi-crew-steps" });
|
|
321
|
+
safeEmit(events, "powerbar:update", { id: "pi-crew-plan" });
|
|
285
322
|
return;
|
|
286
323
|
}
|
|
287
324
|
const agents = active.flatMap((item) => item.agents);
|
|
@@ -373,6 +410,12 @@ class PowerbarPublisher {
|
|
|
373
410
|
} as const;
|
|
374
411
|
// Build step progress: "explorer > planner > executor > verifier" with current step highlighted
|
|
375
412
|
const stepsPayload = buildStepsPayload(active, tasks);
|
|
413
|
+
// WP-3 (H4): plan-approval segment — `plan:pending` while ANY active run is
|
|
414
|
+
// parked awaiting plan approval, clear payload otherwise. `item.run` is
|
|
415
|
+
// always the freshest manifest available (snapshot.manifest when cached).
|
|
416
|
+
const planPayload: PowerbarPayloadShape = active.some((item) => isPlanApprovalPending(item.run))
|
|
417
|
+
? { id: "pi-crew-plan", text: "plan:pending", color: "warning" }
|
|
418
|
+
: { id: "pi-crew-plan" };
|
|
376
419
|
// 1.8: dedup per segment using a key over every visible field. Previously
|
|
377
420
|
// the dedup string only carried text/suffix/running, so changes to `bar`
|
|
378
421
|
// (progress %) or `color` could be swallowed and stale UI emitted again
|
|
@@ -380,6 +423,7 @@ class PowerbarPublisher {
|
|
|
380
423
|
const activeKey = powerbarKey(activePayload);
|
|
381
424
|
const progressKey = powerbarKey(progressPayload);
|
|
382
425
|
const stepsKey = powerbarKey(stepsPayload);
|
|
426
|
+
const planKey = powerbarKey(planPayload);
|
|
383
427
|
if (activeKey !== this.#lastActiveKey) {
|
|
384
428
|
this.#lastActiveKey = activeKey;
|
|
385
429
|
safeEmit(events, "powerbar:update", activePayload);
|
|
@@ -392,6 +436,10 @@ class PowerbarPublisher {
|
|
|
392
436
|
this.#lastStepsKey = stepsKey;
|
|
393
437
|
safeEmit(events, "powerbar:update", stepsPayload);
|
|
394
438
|
}
|
|
439
|
+
if (planKey !== this.#lastPlanKey) {
|
|
440
|
+
this.#lastPlanKey = planKey;
|
|
441
|
+
safeEmit(events, "powerbar:update", planPayload);
|
|
442
|
+
}
|
|
395
443
|
// Never call setStatusFallback - crew-widget manages "pi-crew" status with its own widget format
|
|
396
444
|
// Powerbar only emits events; it does not set status directly
|
|
397
445
|
}
|
|
@@ -431,9 +479,11 @@ class PowerbarPublisher {
|
|
|
431
479
|
this.#lastActiveKey = undefined;
|
|
432
480
|
this.#lastProgressKey = undefined;
|
|
433
481
|
this.#lastStepsKey = undefined;
|
|
482
|
+
this.#lastPlanKey = undefined;
|
|
434
483
|
safeEmit(events, "powerbar:update", { id: "pi-crew-active" });
|
|
435
484
|
safeEmit(events, "powerbar:update", { id: "pi-crew-progress" });
|
|
436
485
|
safeEmit(events, "powerbar:update", { id: "pi-crew-steps" });
|
|
486
|
+
safeEmit(events, "powerbar:update", { id: "pi-crew-plan" });
|
|
437
487
|
}
|
|
438
488
|
|
|
439
489
|
/** Reset dedup state on session lifecycle events. */
|
|
@@ -441,6 +491,7 @@ class PowerbarPublisher {
|
|
|
441
491
|
this.#lastActiveKey = undefined;
|
|
442
492
|
this.#lastProgressKey = undefined;
|
|
443
493
|
this.#lastStepsKey = undefined;
|
|
494
|
+
this.#lastPlanKey = undefined;
|
|
444
495
|
}
|
|
445
496
|
|
|
446
497
|
dispose(): void {
|
package/src/ui/run-dashboard.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { MetricRegistry } from "../observability/metric-registry.ts";
|
|
|
3
3
|
import { readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
4
4
|
import type { CrewAgentRecord } from "../runtime/crew-agent-runtime.ts";
|
|
5
5
|
import { getLiveAgentContextPercent } from "../runtime/live-session/live-agent-manager.ts";
|
|
6
|
+
import { isPlanApprovalPending } from "../runtime/plan-approval.ts";
|
|
6
7
|
import { isDisplayActiveRun, isLikelyOrphanedActiveRun } from "../runtime/process-status.ts";
|
|
7
8
|
import type { TeamRunManifest, TeamTaskState, UsageState } from "../state/types.ts";
|
|
8
9
|
import { aggregateUsage } from "../state/usage.ts";
|
|
@@ -16,6 +17,7 @@ import { summarizeTerminalReason } from "./dashboard-panes/cancellation-pane.ts"
|
|
|
16
17
|
import { renderHealthPane } from "./dashboard-panes/health-pane.ts";
|
|
17
18
|
import { renderMailboxPane } from "./dashboard-panes/mailbox-pane.ts";
|
|
18
19
|
import { renderMetricsPane } from "./dashboard-panes/metrics-pane.ts";
|
|
20
|
+
import { renderPlanPane } from "./dashboard-panes/plan-pane.ts";
|
|
19
21
|
import { renderProgressPane } from "./dashboard-panes/progress-pane.ts";
|
|
20
22
|
import { renderTranscriptPane } from "./dashboard-panes/transcript-pane.ts";
|
|
21
23
|
import { DynamicCrewBorder } from "./dynamic-border.ts";
|
|
@@ -75,7 +77,7 @@ export interface RunDashboardOptions {
|
|
|
75
77
|
* looking at. Resetting to "agents" on every `new RunDashboard(...)` was a
|
|
76
78
|
* UX regression.
|
|
77
79
|
*/
|
|
78
|
-
let lastActivePane: "agents" | "progress" | "mailbox" | "output" | "health" | "metrics" = "agents";
|
|
80
|
+
let lastActivePane: "agents" | "progress" | "mailbox" | "output" | "health" | "metrics" | "plan" = "agents";
|
|
79
81
|
|
|
80
82
|
export type RunDashboardAction =
|
|
81
83
|
| "status"
|
|
@@ -94,6 +96,8 @@ export type RunDashboardAction =
|
|
|
94
96
|
| "health-recovery"
|
|
95
97
|
| "health-kill-stale"
|
|
96
98
|
| "health-diagnostic-export"
|
|
99
|
+
| "plan-approve"
|
|
100
|
+
| "plan-deny"
|
|
97
101
|
| "notifications-dismiss";
|
|
98
102
|
export interface RunDashboardSelection {
|
|
99
103
|
runId: string;
|
|
@@ -404,7 +408,9 @@ export class RunDashboard implements DashboardComponent {
|
|
|
404
408
|
private runScrollOffset = 0;
|
|
405
409
|
private showFullProgress = false;
|
|
406
410
|
private showHelp = false;
|
|
407
|
-
private activePane: "agents" | "progress" | "mailbox" | "output" | "health" | "metrics" = lastActivePane;
|
|
411
|
+
private activePane: "agents" | "progress" | "mailbox" | "output" | "health" | "metrics" | "plan" = lastActivePane;
|
|
412
|
+
/** WP-7 (R7): pane-scoped revision-diff toggle (X). */
|
|
413
|
+
private planDiff = false;
|
|
408
414
|
private runs: TeamRunManifest[];
|
|
409
415
|
private readonly done: (selection: RunDashboardSelection | undefined) => void;
|
|
410
416
|
private readonly theme: CrewTheme;
|
|
@@ -638,7 +644,7 @@ export class RunDashboard implements DashboardComponent {
|
|
|
638
644
|
lines.push(
|
|
639
645
|
border("╭", "╮"),
|
|
640
646
|
row(
|
|
641
|
-
`${fg("accent", "▐")} ${this.theme.bold("pi-crew")} · ${this.runs.length} runs ${fg("dim", "1-
|
|
647
|
+
`${fg("accent", "▐")} ${this.theme.bold("pi-crew")} · ${this.runs.length} runs ${fg("dim", "1-7 pane · ↑↓ · Enter · ? help · Esc")}`,
|
|
642
648
|
),
|
|
643
649
|
sep(),
|
|
644
650
|
);
|
|
@@ -732,7 +738,9 @@ export class RunDashboard implements DashboardComponent {
|
|
|
732
738
|
registry: this.options.registry,
|
|
733
739
|
}),
|
|
734
740
|
)
|
|
735
|
-
:
|
|
741
|
+
: this.activePane === "plan"
|
|
742
|
+
? safeRenderPane("plan", () => renderPlanPane(snap, { diff: this.planDiff }))
|
|
743
|
+
: safeRenderPane("transcript", () => renderTranscriptPane(snap))
|
|
736
744
|
: [...readAgentPreview(r, 4, this.options), ...readProgressPreview(r, 2, this.options.snapshotCache)];
|
|
737
745
|
const filteredPane = paneLines.filter((l) => l && !l.includes("(none)") && l.trim() !== "");
|
|
738
746
|
if (filteredPane.length > 0) {
|
|
@@ -832,6 +840,19 @@ export class RunDashboard implements DashboardComponent {
|
|
|
832
840
|
this.done(selectedRunId ? { runId: selectedRunId, action: "status" } : undefined);
|
|
833
841
|
return;
|
|
834
842
|
}
|
|
843
|
+
// WP-3 (H4-subset): plan approval keys. Deliberately a DEDICATED branch
|
|
844
|
+
// (not the generic union block below) because plan actions must gate on
|
|
845
|
+
// the selected run actually being parked on a pending approval — a
|
|
846
|
+
// stray keystroke on a non-pending run is a silent no-op (dashboard
|
|
847
|
+
// stays open) instead of closing it via done(undefined).
|
|
848
|
+
if (action === "plan-approve" || action === "plan-deny") {
|
|
849
|
+
const run = selectedRunFromGrouped(this.runs, this.selected, this.options.snapshotCache);
|
|
850
|
+
const manifest = run ? (snapshotFor(run, this.options.snapshotCache)?.manifest ?? run) : undefined;
|
|
851
|
+
if (run && manifest && isPlanApprovalPending(manifest)) {
|
|
852
|
+
this.done({ runId: run.runId, action });
|
|
853
|
+
}
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
835
856
|
if (
|
|
836
857
|
action === "summary" ||
|
|
837
858
|
action === "artifacts" ||
|
|
@@ -875,7 +896,12 @@ export class RunDashboard implements DashboardComponent {
|
|
|
875
896
|
else if (action === "pane-output") this.activePane = "output";
|
|
876
897
|
else if (action === "pane-health") this.activePane = "health";
|
|
877
898
|
else if (action === "pane-metrics") this.activePane = "metrics";
|
|
878
|
-
else if (action === "
|
|
899
|
+
else if (action === "pane-plan") this.activePane = "plan";
|
|
900
|
+
else if (action === "plan-diff") {
|
|
901
|
+
this.planDiff = !this.planDiff;
|
|
902
|
+
this.invalidate();
|
|
903
|
+
return;
|
|
904
|
+
} else if (action === "up") this.selected = Math.max(0, this.selected - 1);
|
|
879
905
|
else if (action === "down") {
|
|
880
906
|
const selectableCount = groupedRuns(this.runs, this.options.snapshotCache).filter((row) => row.run).length;
|
|
881
907
|
this.selected = Math.min(Math.max(0, selectableCount - 1), this.selected + 1);
|