@pellux/goodvibes-tui 0.28.0 → 0.29.0
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 +101 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/input/commands/cost-runtime.ts +49 -0
- package/src/input/commands/operator-runtime.ts +5 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-feed-routes.ts +12 -35
- package/src/input/handler-feed.ts +4 -8
- package/src/input/handler-shortcuts.ts +51 -0
- package/src/input/handler.ts +43 -6
- package/src/input/keybindings.ts +48 -8
- package/src/input/panel-integration-actions.ts +107 -1
- package/src/main.ts +5 -1
- package/src/panels/agent-inspector-panel.ts +125 -36
- package/src/panels/agent-inspector-shared.ts +144 -0
- package/src/panels/approval-panel.ts +67 -16
- package/src/panels/automation-control-panel.ts +368 -124
- package/src/panels/builtin/agent.ts +28 -37
- package/src/panels/builtin/development.ts +40 -32
- package/src/panels/builtin/knowledge.ts +15 -6
- package/src/panels/builtin/operations.ts +178 -117
- package/src/panels/builtin/session.ts +35 -9
- package/src/panels/builtin/shared.ts +98 -6
- package/src/panels/cockpit-panel.ts +232 -73
- package/src/panels/communication-panel.ts +58 -20
- package/src/panels/confirm-state.ts +8 -1
- package/src/panels/control-plane-panel.ts +351 -118
- package/src/panels/cost-tracker-panel.ts +165 -7
- package/src/panels/debug-panel.ts +312 -159
- package/src/panels/diff-panel.ts +203 -57
- package/src/panels/docs-panel.ts +152 -66
- package/src/panels/eval-panel.ts +220 -42
- package/src/panels/file-explorer-panel.ts +202 -122
- package/src/panels/file-preview-panel.ts +132 -57
- package/src/panels/git-panel.ts +309 -128
- package/src/panels/hooks-panel.ts +150 -22
- package/src/panels/incident-review-panel.ts +223 -35
- package/src/panels/index.ts +0 -4
- package/src/panels/intelligence-panel.ts +212 -86
- package/src/panels/knowledge-graph-panel.ts +461 -101
- package/src/panels/local-auth-panel.ts +240 -28
- package/src/panels/marketplace-panel.ts +193 -26
- package/src/panels/memory-panel.ts +78 -77
- package/src/panels/ops-control-panel.ts +146 -29
- package/src/panels/ops-strategy-panel.ts +72 -4
- package/src/panels/orchestration-panel.ts +231 -69
- package/src/panels/panel-list-panel.ts +154 -131
- package/src/panels/panel-manager.ts +97 -9
- package/src/panels/plan-dashboard-panel.ts +333 -17
- package/src/panels/plugins-panel.ts +184 -29
- package/src/panels/policy-panel.ts +210 -38
- package/src/panels/polish-core.ts +7 -2
- package/src/panels/polish.ts +23 -4
- package/src/panels/project-planning-answer-actions.ts +134 -0
- package/src/panels/project-planning-panel.ts +62 -113
- package/src/panels/provider-health-panel.ts +434 -518
- package/src/panels/provider-health-routes.ts +203 -0
- package/src/panels/provider-health-tracker.ts +194 -6
- package/src/panels/provider-health-views.ts +560 -0
- package/src/panels/qr-panel.ts +116 -30
- package/src/panels/remote-panel.ts +114 -36
- package/src/panels/routes-panel.ts +63 -22
- package/src/panels/sandbox-panel.ts +174 -42
- package/src/panels/scrollable-list-panel.ts +19 -135
- package/src/panels/security-panel.ts +133 -33
- package/src/panels/services-panel.ts +116 -64
- package/src/panels/session-browser-panel.ts +73 -2
- package/src/panels/session-maintenance.ts +4 -122
- package/src/panels/settings-sync-panel.ts +335 -72
- package/src/panels/skills-panel.ts +157 -89
- package/src/panels/subscription-panel.ts +86 -33
- package/src/panels/symbol-outline-panel.ts +248 -108
- package/src/panels/system-messages-panel.ts +114 -13
- package/src/panels/tasks-panel.ts +326 -139
- package/src/panels/thinking-panel.ts +43 -10
- package/src/panels/token-budget-panel.ts +194 -18
- package/src/panels/tool-inspector-panel.ts +144 -34
- package/src/panels/types.ts +40 -4
- package/src/panels/work-plan-panel.ts +280 -17
- package/src/panels/worktree-panel.ts +175 -42
- package/src/panels/wrfc-panel.ts +116 -24
- package/src/renderer/conversation-overlays.ts +25 -11
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +22 -16
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +21 -7
- package/src/renderer/overlay-box.ts +16 -10
- package/src/renderer/process-indicator.ts +3 -1
- package/src/renderer/progress.ts +5 -4
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +41 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/ui-factory.ts +41 -30
- package/src/renderer/ui-primitives.ts +23 -2
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +2 -2
- package/src/utils/splash-lines.ts +44 -3
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +13 -0
- package/src/panels/agent-logs-panel.ts +0 -635
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/panels/context-visualizer-panel.ts +0 -238
- package/src/panels/forensics-panel.ts +0 -378
- package/src/panels/provider-account-snapshot.ts +0 -259
- package/src/panels/provider-accounts-panel.ts +0 -255
- package/src/panels/provider-stats-panel.ts +0 -391
- package/src/panels/schedule-panel.ts +0 -365
- package/src/panels/watchers-panel.ts +0 -213
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import type { ExecutionPlan, PlanItem, PlanItemStatus } from '@pellux/goodvibes-sdk/platform/core';
|
|
2
|
+
import type { ExecutionPlan, ExecutionPlanManager, PlanItem, PlanItemStatus } from '@pellux/goodvibes-sdk/platform/core';
|
|
3
3
|
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
4
4
|
import { BasePanel } from './base-panel.ts';
|
|
5
|
-
import
|
|
5
|
+
import { AgentInspectorPanel } from './agent-inspector-panel.ts';
|
|
6
|
+
import type { PanelIntegrationContext } from './types.ts';
|
|
7
|
+
import type { WorkflowEvent } from '@/runtime/index.ts';
|
|
8
|
+
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
6
9
|
import {
|
|
7
10
|
buildEmptyState,
|
|
8
11
|
buildKeyboardHints,
|
|
@@ -52,6 +55,21 @@ const STATUS_FG: Record<PlanItemStatus, string> = {
|
|
|
52
55
|
skipped: C.skipped,
|
|
53
56
|
};
|
|
54
57
|
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Deps
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The subset of ExecutionPlanManager the dashboard needs: the live active
|
|
64
|
+
* plan (getActive) plus history/switching support (list/getSummary/
|
|
65
|
+
* toMarkdown — the same trio /plan list|show consume, planning-runtime.ts:
|
|
66
|
+
* 140-165) so the panel can browse and view any plan on disk, not just the
|
|
67
|
+
* currently active one.
|
|
68
|
+
*/
|
|
69
|
+
export interface PlanDashboardPanelDeps {
|
|
70
|
+
readonly planManager: Pick<ExecutionPlanManager, 'getActive' | 'list' | 'getSummary' | 'toMarkdown'>;
|
|
71
|
+
}
|
|
72
|
+
|
|
55
73
|
// ---------------------------------------------------------------------------
|
|
56
74
|
// PlanDashboardPanel
|
|
57
75
|
// ---------------------------------------------------------------------------
|
|
@@ -65,11 +83,37 @@ export class PlanDashboardPanel extends BasePanel {
|
|
|
65
83
|
// Item flat-list parallel to navigable rows (set during render) for detail.
|
|
66
84
|
private navItems: PlanItem[] = [];
|
|
67
85
|
private blockedItemIds = new Set<string>();
|
|
68
|
-
private readonly planManager: PlanDashboardQuery;
|
|
69
86
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
/**
|
|
88
|
+
* The plan item under the cursor. This panel owns its own selection state
|
|
89
|
+
* (`selectedIndex` navigates the `this.navItems` flat list directly), so
|
|
90
|
+
* every selected-row read routes through this one accessor — indexing
|
|
91
|
+
* `this.navItems` by the cursor directly is banned by the
|
|
92
|
+
* no-raw-selectedindex-read architecture rule.
|
|
93
|
+
*/
|
|
94
|
+
private selectedNavItem(): PlanItem | undefined {
|
|
95
|
+
return this.navItems.at(this.selectedIndex);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Plan history / switching (planManager.list()/getSummary()/toMarkdown()).
|
|
99
|
+
// `viewingPlanId` is null while following the live active plan; once set,
|
|
100
|
+
// the dashboard renders that specific plan from disk instead.
|
|
101
|
+
private viewingPlanId: string | null = null;
|
|
102
|
+
private historyMode = false;
|
|
103
|
+
private historyPlans: ExecutionPlan[] = [];
|
|
104
|
+
private historySelectedIndex = 0;
|
|
105
|
+
private historyScrollOffset = 0;
|
|
106
|
+
|
|
107
|
+
// Live-refresh subscriptions (WrfcPanel pattern: subscribe ui.events.workflows
|
|
108
|
+
// so the dashboard updates during a run without requiring a keypress).
|
|
109
|
+
private unsubscribers: Array<() => void> = [];
|
|
110
|
+
|
|
111
|
+
constructor(
|
|
112
|
+
private readonly workflowEvents: UiEventFeed<WorkflowEvent>,
|
|
113
|
+
private readonly deps: PlanDashboardPanelDeps,
|
|
114
|
+
) {
|
|
115
|
+
super('plan', 'Plan', '▤', 'agent');
|
|
116
|
+
this.subscribeToEvents();
|
|
73
117
|
}
|
|
74
118
|
|
|
75
119
|
override onActivate(): void {
|
|
@@ -78,11 +122,52 @@ export class PlanDashboardPanel extends BasePanel {
|
|
|
78
122
|
this.markDirty();
|
|
79
123
|
}
|
|
80
124
|
|
|
125
|
+
override onDestroy(): void {
|
|
126
|
+
for (const unsub of this.unsubscribers) unsub();
|
|
127
|
+
this.unsubscribers = [];
|
|
128
|
+
super.onDestroy();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// --------------------------------------------------------------------------
|
|
132
|
+
// Live refresh — mirrors WrfcPanel: any workflow event may have changed the
|
|
133
|
+
// active plan's item statuses (WRFC updates plan items via
|
|
134
|
+
// wrfcController.setPlanManager, main.ts:120), so re-render without
|
|
135
|
+
// requiring a keypress.
|
|
136
|
+
// --------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
private subscribeToEvents(): void {
|
|
139
|
+
const refresh = () => this.markDirty();
|
|
140
|
+
this.unsubscribers.push(
|
|
141
|
+
this.workflowEvents.on('WORKFLOW_CHAIN_CREATED', refresh),
|
|
142
|
+
this.workflowEvents.on('WORKFLOW_STATE_CHANGED', refresh),
|
|
143
|
+
this.workflowEvents.on('WORKFLOW_REVIEW_COMPLETED', refresh),
|
|
144
|
+
this.workflowEvents.on('WORKFLOW_FIX_ATTEMPTED', refresh),
|
|
145
|
+
this.workflowEvents.on('WORKFLOW_GATE_RESULT', refresh),
|
|
146
|
+
this.workflowEvents.on('WORKFLOW_CHAIN_PASSED', refresh),
|
|
147
|
+
this.workflowEvents.on('WORKFLOW_CHAIN_FAILED', refresh),
|
|
148
|
+
this.workflowEvents.on('WORKFLOW_AUTO_COMMITTED', refresh),
|
|
149
|
+
this.workflowEvents.on('WORKFLOW_CASCADE_ABORTED', refresh),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
81
153
|
// --------------------------------------------------------------------------
|
|
82
154
|
// Input
|
|
83
155
|
// --------------------------------------------------------------------------
|
|
84
156
|
|
|
85
157
|
handleInput(key: string): boolean {
|
|
158
|
+
if (this.historyMode) {
|
|
159
|
+
return this.handleHistoryInput(key);
|
|
160
|
+
}
|
|
161
|
+
if (key === 'h') {
|
|
162
|
+
this.enterHistoryMode();
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
if (key === 'a' && this.viewingPlanId !== null) {
|
|
166
|
+
this.viewingPlanId = null;
|
|
167
|
+
this.selectedIndex = 0;
|
|
168
|
+
this.markDirty();
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
86
171
|
if (key === 'up' || key === 'k') {
|
|
87
172
|
if (this.selectedIndex > 0) {
|
|
88
173
|
this.selectedIndex--;
|
|
@@ -108,17 +193,94 @@ export class PlanDashboardPanel extends BasePanel {
|
|
|
108
193
|
this.markDirty();
|
|
109
194
|
}
|
|
110
195
|
return true;
|
|
196
|
+
} else if (key === 'enter' || key === 'return') {
|
|
197
|
+
// Consumed here (so the integration router fires next) only when the
|
|
198
|
+
// selected item actually has an agent to jump to; see
|
|
199
|
+
// handlePanelIntegrationAction below.
|
|
200
|
+
const item = this.selectedNavItem();
|
|
201
|
+
if (item?.agentId) return true;
|
|
111
202
|
}
|
|
112
203
|
return false;
|
|
113
204
|
}
|
|
114
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Cross-panel jump: Enter on a plan item with an assigned agent opens the
|
|
208
|
+
* Inspector focused on that agent (inspectAgent, WO-110). The inspector
|
|
209
|
+
* already renders a WRFC badge for the inspected agent, so this also
|
|
210
|
+
* cross-links the owning WRFC chain without the dashboard needing to know
|
|
211
|
+
* about WrfcController directly.
|
|
212
|
+
*/
|
|
213
|
+
handlePanelIntegrationAction(key: string, ctx: PanelIntegrationContext): boolean {
|
|
214
|
+
if (this.historyMode) return false;
|
|
215
|
+
if (key !== 'enter' && key !== 'return') return false;
|
|
216
|
+
const item = this.selectedNavItem();
|
|
217
|
+
if (!item?.agentId) return false;
|
|
218
|
+
const inspector = ctx.panelManager.open('inspector');
|
|
219
|
+
if (inspector instanceof AgentInspectorPanel) {
|
|
220
|
+
inspector.inspectAgent(item.agentId);
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// --------------------------------------------------------------------------
|
|
226
|
+
// Plan history / switching
|
|
227
|
+
// --------------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
private enterHistoryMode(): void {
|
|
230
|
+
this.historyPlans = [...this.deps.planManager.list()].sort(
|
|
231
|
+
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),
|
|
232
|
+
);
|
|
233
|
+
this.historySelectedIndex = 0;
|
|
234
|
+
this.historyScrollOffset = 0;
|
|
235
|
+
this.historyMode = true;
|
|
236
|
+
this.markDirty();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private handleHistoryInput(key: string): boolean {
|
|
240
|
+
if (key === 'up' || key === 'k') {
|
|
241
|
+
if (this.historySelectedIndex > 0) {
|
|
242
|
+
this.historySelectedIndex--;
|
|
243
|
+
this.markDirty();
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
if (key === 'down' || key === 'j') {
|
|
248
|
+
if (this.historySelectedIndex < this.historyPlans.length - 1) {
|
|
249
|
+
this.historySelectedIndex++;
|
|
250
|
+
this.markDirty();
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
if (key === 'enter' || key === 'return') {
|
|
255
|
+
const plan = this.historyPlans[this.historySelectedIndex];
|
|
256
|
+
if (plan) {
|
|
257
|
+
this.viewingPlanId = plan.id;
|
|
258
|
+
this.selectedIndex = 0;
|
|
259
|
+
}
|
|
260
|
+
this.historyMode = false;
|
|
261
|
+
this.markDirty();
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
if (key === 'escape' || key === 'h') {
|
|
265
|
+
this.historyMode = false;
|
|
266
|
+
this.markDirty();
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
// Modal browsing: absorb everything else (mirrors ConfirmState's "others
|
|
270
|
+
// absorbed" contract) so stray keys cannot leak into item navigation.
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
115
274
|
// --------------------------------------------------------------------------
|
|
116
275
|
// Render
|
|
117
276
|
// --------------------------------------------------------------------------
|
|
118
277
|
|
|
119
278
|
render(width: number, height: number): Line[] {
|
|
120
279
|
return this.trackedRender(() => {
|
|
121
|
-
|
|
280
|
+
if (this.historyMode) {
|
|
281
|
+
return this.renderHistory(width, height);
|
|
282
|
+
}
|
|
283
|
+
const plan = this.resolveViewedPlan();
|
|
122
284
|
if (!plan) {
|
|
123
285
|
return buildPanelWorkspace(width, height, {
|
|
124
286
|
title: ' Plan Dashboard',
|
|
@@ -128,14 +290,16 @@ export class PlanDashboardPanel extends BasePanel {
|
|
|
128
290
|
lines: buildEmptyState(
|
|
129
291
|
width,
|
|
130
292
|
' No active execution plan',
|
|
131
|
-
'
|
|
293
|
+
'Execution plans are produced by WRFC-reviewed tasks (gather-plan-apply execution). Start one from Teamwork and its phases, progress, blocked steps, and next action will surface here.',
|
|
132
294
|
[
|
|
133
|
-
{ command: '/
|
|
295
|
+
{ command: '/teamwork create-mode local-engineer <title>', summary: 'start a WRFC-reviewed engineer task — the only producer of a real execution plan' },
|
|
296
|
+
{ command: '/teamwork modes', summary: 'see every reviewMode: wrfc mode that can seed a plan' },
|
|
134
297
|
],
|
|
135
298
|
C,
|
|
136
299
|
),
|
|
137
300
|
},
|
|
138
301
|
],
|
|
302
|
+
footerLines: this.historyFooterHintLines(width),
|
|
139
303
|
palette: C,
|
|
140
304
|
});
|
|
141
305
|
}
|
|
@@ -143,11 +307,149 @@ export class PlanDashboardPanel extends BasePanel {
|
|
|
143
307
|
});
|
|
144
308
|
}
|
|
145
309
|
|
|
310
|
+
/** Resolve which plan to render: the pinned history selection, or the live active plan. */
|
|
311
|
+
private resolveViewedPlan(): ExecutionPlan | null {
|
|
312
|
+
if (this.viewingPlanId !== null) {
|
|
313
|
+
const found = this.deps.planManager.list().find((p) => p.id === this.viewingPlanId);
|
|
314
|
+
if (found) return found;
|
|
315
|
+
// The pinned plan is gone (e.g. deleted from disk) — fall back to live.
|
|
316
|
+
this.viewingPlanId = null;
|
|
317
|
+
}
|
|
318
|
+
return this.deps.planManager.getActive();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Keyboard hint shown even on the empty state so 'h' (history) stays discoverable. */
|
|
322
|
+
private historyFooterHintLines(width: number): Line[] {
|
|
323
|
+
return [
|
|
324
|
+
buildKeyboardHints(width, [
|
|
325
|
+
{ keys: 'h', label: 'plan history' },
|
|
326
|
+
], C),
|
|
327
|
+
];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// --------------------------------------------------------------------------
|
|
331
|
+
// Plan history browser
|
|
332
|
+
// --------------------------------------------------------------------------
|
|
333
|
+
|
|
334
|
+
private renderHistory(width: number, height: number): Line[] {
|
|
335
|
+
if (this.historyPlans.length === 0) {
|
|
336
|
+
return buildPanelWorkspace(width, height, {
|
|
337
|
+
title: ' Plan History',
|
|
338
|
+
intro: 'Every execution plan ever created for this project.',
|
|
339
|
+
sections: [
|
|
340
|
+
{
|
|
341
|
+
lines: buildEmptyState(
|
|
342
|
+
width,
|
|
343
|
+
' No plans on disk',
|
|
344
|
+
'Plans appear here once a WRFC-reviewed task (gather-plan-apply execution) creates one.',
|
|
345
|
+
[],
|
|
346
|
+
C,
|
|
347
|
+
),
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
footerLines: [buildKeyboardHints(width, [{ keys: 'Esc / h', label: 'back' }], C)],
|
|
351
|
+
palette: C,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const activePlan = this.deps.planManager.getActive();
|
|
356
|
+
const rows: Line[] = [];
|
|
357
|
+
let selectedLineIndex = 0;
|
|
358
|
+
for (let i = 0; i < this.historyPlans.length; i++) {
|
|
359
|
+
const plan = this.historyPlans[i]!;
|
|
360
|
+
const isSelected = i === this.historySelectedIndex;
|
|
361
|
+
if (isSelected) selectedLineIndex = rows.length;
|
|
362
|
+
rows.push(this.renderHistoryRow(plan, activePlan?.id === plan.id, isSelected, width));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const highlighted = this.historyPlans[this.historySelectedIndex];
|
|
366
|
+
const detailSection: PanelWorkspaceSection | null = highlighted ? {
|
|
367
|
+
title: 'Plan Detail',
|
|
368
|
+
lines: this.buildHistoryDetailLines(highlighted, width),
|
|
369
|
+
} : null;
|
|
370
|
+
|
|
371
|
+
const footerLines: Line[] = [
|
|
372
|
+
buildKeyboardHints(width, [
|
|
373
|
+
{ keys: '↑/↓', label: 'browse' },
|
|
374
|
+
{ keys: 'Enter', label: 'view plan' },
|
|
375
|
+
{ keys: 'Esc / h', label: 'back' },
|
|
376
|
+
], C),
|
|
377
|
+
];
|
|
378
|
+
|
|
379
|
+
const intro = `${this.historyPlans.length} plan${this.historyPlans.length === 1 ? '' : 's'} on disk`;
|
|
380
|
+
const historySection = resolveScrollablePanelSection(width, height, {
|
|
381
|
+
intro,
|
|
382
|
+
footerLines,
|
|
383
|
+
palette: C,
|
|
384
|
+
afterSections: detailSection ? [detailSection] : [],
|
|
385
|
+
section: {
|
|
386
|
+
title: 'Plan History',
|
|
387
|
+
scrollableLines: rows,
|
|
388
|
+
selectedIndex: selectedLineIndex,
|
|
389
|
+
scrollOffset: this.historyScrollOffset,
|
|
390
|
+
minRows: 6,
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
this.historyScrollOffset = historySection.scrollOffset;
|
|
394
|
+
|
|
395
|
+
return buildPanelWorkspace(width, height, {
|
|
396
|
+
title: ' Plan History',
|
|
397
|
+
intro,
|
|
398
|
+
sections: [
|
|
399
|
+
historySection.section,
|
|
400
|
+
...(detailSection ? [detailSection] : []),
|
|
401
|
+
],
|
|
402
|
+
footerLines,
|
|
403
|
+
palette: C,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private renderHistoryRow(plan: ExecutionPlan, isActive: boolean, isSelected: boolean, width: number): Line {
|
|
408
|
+
const marker = isActive ? '▶' : ' ';
|
|
409
|
+
const idShort = plan.id.slice(0, 8);
|
|
410
|
+
const statusTag = `[${plan.status.padEnd(8)}]`;
|
|
411
|
+
const prefix = ` ${marker} ${idShort} ${statusTag} `;
|
|
412
|
+
const label = truncateDisplay(plan.title, Math.max(6, width - prefix.length - 2));
|
|
413
|
+
return buildSelectablePanelLine(width, [
|
|
414
|
+
{ text: ` ${marker} `, fg: isActive ? C.good : C.dim, bold: isActive },
|
|
415
|
+
{ text: `${idShort} `, fg: C.dim },
|
|
416
|
+
{ text: `${statusTag} `, fg: this.statusColor(plan.status) },
|
|
417
|
+
{ text: label, fg: C.value },
|
|
418
|
+
], {
|
|
419
|
+
selected: isSelected,
|
|
420
|
+
selectedBg: C.selectBg,
|
|
421
|
+
leadingMarker: '▸',
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
private buildHistoryDetailLines(plan: ExecutionPlan, width: number): Line[] {
|
|
426
|
+
const lines: Line[] = [
|
|
427
|
+
buildPanelLine(width, [
|
|
428
|
+
[' Summary ', C.label],
|
|
429
|
+
[truncateDisplay(this.deps.planManager.getSummary(plan), Math.max(8, width - 12)), C.value],
|
|
430
|
+
]),
|
|
431
|
+
];
|
|
432
|
+
const markdown = this.deps.planManager.toMarkdown(plan)
|
|
433
|
+
.split('\n')
|
|
434
|
+
.filter((line) => line.trim().length > 0)
|
|
435
|
+
.slice(0, 4);
|
|
436
|
+
for (const line of markdown) {
|
|
437
|
+
lines.push(buildPanelLine(width, [
|
|
438
|
+
[' ', C.label],
|
|
439
|
+
[truncateDisplay(line, Math.max(8, width - 3)), C.dim],
|
|
440
|
+
]));
|
|
441
|
+
}
|
|
442
|
+
return lines;
|
|
443
|
+
}
|
|
444
|
+
|
|
146
445
|
// --------------------------------------------------------------------------
|
|
147
446
|
// Plan render
|
|
148
447
|
// --------------------------------------------------------------------------
|
|
149
448
|
|
|
150
449
|
private renderPlan(plan: ExecutionPlan, width: number, height: number): Line[] {
|
|
450
|
+
const activePlan = this.deps.planManager.getActive();
|
|
451
|
+
const isHistorical = activePlan?.id !== plan.id;
|
|
452
|
+
|
|
151
453
|
const phaseOrder: string[] = [];
|
|
152
454
|
const byPhase = new Map<string, PlanItem[]>();
|
|
153
455
|
for (const item of plan.items) {
|
|
@@ -247,6 +549,16 @@ export class PlanDashboardPanel extends BasePanel {
|
|
|
247
549
|
],
|
|
248
550
|
};
|
|
249
551
|
|
|
552
|
+
const historyBanner: PanelWorkspaceSection | null = isHistorical ? {
|
|
553
|
+
lines: [
|
|
554
|
+
buildPanelLine(width, [
|
|
555
|
+
[' Viewing history ', C.warn],
|
|
556
|
+
[plan.status.toUpperCase(), this.statusColor(plan.status)],
|
|
557
|
+
[' — press a to return to the live plan', C.dim],
|
|
558
|
+
]),
|
|
559
|
+
],
|
|
560
|
+
} : null;
|
|
561
|
+
|
|
250
562
|
const detailSection = this.buildDetailSection(width);
|
|
251
563
|
const footerLines = this.footerLines(width);
|
|
252
564
|
|
|
@@ -254,7 +566,7 @@ export class PlanDashboardPanel extends BasePanel {
|
|
|
254
566
|
intro: plan.title,
|
|
255
567
|
footerLines,
|
|
256
568
|
palette: C,
|
|
257
|
-
beforeSections: [summary],
|
|
569
|
+
beforeSections: historyBanner ? [historyBanner, summary] : [summary],
|
|
258
570
|
afterSections: detailSection ? [detailSection] : [],
|
|
259
571
|
section: {
|
|
260
572
|
title: 'Execution Plan',
|
|
@@ -271,6 +583,7 @@ export class PlanDashboardPanel extends BasePanel {
|
|
|
271
583
|
title: ` Plan Dashboard`,
|
|
272
584
|
intro: plan.title,
|
|
273
585
|
sections: [
|
|
586
|
+
...(historyBanner ? [historyBanner] : []),
|
|
274
587
|
summary,
|
|
275
588
|
planSection.section,
|
|
276
589
|
...(detailSection ? [detailSection] : []),
|
|
@@ -281,7 +594,7 @@ export class PlanDashboardPanel extends BasePanel {
|
|
|
281
594
|
}
|
|
282
595
|
|
|
283
596
|
private buildDetailSection(width: number): PanelWorkspaceSection | null {
|
|
284
|
-
const item = this.
|
|
597
|
+
const item = this.selectedNavItem();
|
|
285
598
|
if (!item) return null;
|
|
286
599
|
const blocked = this.blockedItemIds.has(item.id);
|
|
287
600
|
const lines: Line[] = [
|
|
@@ -307,13 +620,16 @@ export class PlanDashboardPanel extends BasePanel {
|
|
|
307
620
|
}
|
|
308
621
|
|
|
309
622
|
private footerLines(width: number): Line[] {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
], C),
|
|
623
|
+
const hints: Array<{ keys: string; label: string }> = [
|
|
624
|
+
{ keys: this.totalRows > 0 ? `${this.selectedIndex + 1}/${this.totalRows}` : '0/0', label: 'step' },
|
|
625
|
+
{ keys: '↑/↓', label: 'navigate' },
|
|
626
|
+
{ keys: 'Home/End', label: 'jump' },
|
|
627
|
+
{ keys: 'h', label: 'history' },
|
|
316
628
|
];
|
|
629
|
+
const item = this.selectedNavItem();
|
|
630
|
+
if (item?.agentId) hints.push({ keys: 'Enter', label: 'inspect agent' });
|
|
631
|
+
if (this.viewingPlanId !== null) hints.push({ keys: 'a', label: 'back to active' });
|
|
632
|
+
return [buildKeyboardHints(width, hints, C)];
|
|
317
633
|
}
|
|
318
634
|
|
|
319
635
|
// --------------------------------------------------------------------------
|