@pellux/goodvibes-tui 1.1.0 → 1.7.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 +85 -21
- package/README.md +20 -11
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/conversation-rendering.ts +19 -0
- package/src/core/conversation.ts +18 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/system-message-router.ts +37 -51
- package/src/core/turn-cancellation.ts +20 -0
- package/src/core/turn-event-wiring.ts +64 -3
- package/src/export/cost-utils.ts +36 -0
- package/src/input/command-registry.ts +38 -1
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +192 -0
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +6 -1
- package/src/input/commands/operator-panel-runtime.ts +40 -24
- package/src/input/commands/planning-runtime.ts +26 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +3 -3
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +15 -7
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +338 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +161 -0
- package/src/input/config-modal.ts +377 -0
- package/src/input/feed-context-factory.ts +7 -8
- package/src/input/handler-command-route.ts +27 -17
- package/src/input/handler-feed-routes.ts +61 -23
- package/src/input/handler-feed.ts +47 -23
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +65 -0
- package/src/input/handler-modal-stack.ts +3 -5
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-picker-routes.ts +11 -94
- package/src/input/handler-shortcuts.ts +24 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +6 -28
- package/src/input/keybindings.ts +22 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +58 -0
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/settings-modal-data.ts +95 -0
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/main.ts +24 -27
- package/src/panels/agent-inspector-shared.ts +2 -1
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +15 -61
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +9 -43
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +36 -10
- package/src/panels/diff-panel.ts +12 -43
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-panel.ts +800 -0
- package/src/panels/fleet-read-model.ts +477 -0
- package/src/panels/fleet-steer.ts +92 -0
- package/src/panels/fleet-stop.ts +125 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/index.ts +7 -31
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +151 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +36 -0
- package/src/panels/modals/pairing-modal.ts +144 -0
- package/src/panels/modals/planning-modal.ts +282 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +149 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +108 -5
- package/src/panels/project-planning-answer-actions.ts +4 -2
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +13 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +60 -4
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/config-modal.ts +81 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +10 -2
- package/src/renderer/git-status.ts +40 -0
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/model-workspace.ts +44 -1
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/turn-injection.ts +114 -0
- package/src/runtime/bootstrap-command-context.ts +21 -1
- package/src/runtime/bootstrap-command-parts.ts +34 -7
- package/src/runtime/bootstrap-core.ts +14 -4
- package/src/runtime/bootstrap-shell.ts +29 -16
- package/src/runtime/bootstrap.ts +11 -17
- package/src/runtime/code-index-services.ts +112 -0
- package/src/runtime/orchestrator-core-services.ts +49 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/services.ts +91 -43
- package/src/runtime/ui-services.ts +8 -0
- package/src/runtime/workstream-services.ts +195 -0
- package/src/shell/blocking-input.ts +22 -1
- package/src/shell/ui-openers.ts +129 -17
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -238
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -615
- package/src/panels/docs-panel.ts +0 -384
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -673
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -515
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -566
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -731
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -310
- package/src/panels/provider-health-views.ts +0 -567
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -496
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -540
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -466
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -671
- package/src/renderer/qr-renderer.ts +0 -120
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OrchestrationPanel — displays task graphs, node contracts, recursion guards,
|
|
3
|
-
* and WRFC-visible orchestration state.
|
|
4
|
-
*
|
|
5
|
-
* Migrated (Wave B2): extends ScrollableListPanel<OrchestrationGraphRecord>.
|
|
6
|
-
* Navigation (up/down/j/k) is handled by the base class.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { Line } from '../types/grid.ts';
|
|
10
|
-
import { createEmptyLine } from '../types/grid.ts';
|
|
11
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
12
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
13
|
-
import type { UiOrchestrationSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
|
|
14
|
-
import type { OrchestrationGraphRecord, OrchestrationNodeRecord } from '@/runtime/index.ts';
|
|
15
|
-
import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
16
|
-
import {
|
|
17
|
-
buildEmptyState,
|
|
18
|
-
buildKeyboardHints,
|
|
19
|
-
buildKeyValueLine,
|
|
20
|
-
buildPanelLine,
|
|
21
|
-
buildPanelWorkspace,
|
|
22
|
-
resolveScrollablePanelSection,
|
|
23
|
-
DEFAULT_PANEL_PALETTE,
|
|
24
|
-
extendPalette,
|
|
25
|
-
type PanelPalette,
|
|
26
|
-
type PanelWorkspaceSection,
|
|
27
|
-
} from './polish.ts';
|
|
28
|
-
|
|
29
|
-
// Domain accents only; base chrome (header/headerBg/info/good/warn/bad/
|
|
30
|
-
// selectBg) comes from DEFAULT_PANEL_PALETTE.
|
|
31
|
-
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
32
|
-
completed: '#a78bfa', // completed-graph badge, distinct from running/good
|
|
33
|
-
} as const);
|
|
34
|
-
|
|
35
|
-
function statusColor(status: string): string {
|
|
36
|
-
switch (status) {
|
|
37
|
-
case 'ready': return C.info;
|
|
38
|
-
case 'running': return C.good;
|
|
39
|
-
case 'blocked': return C.warn;
|
|
40
|
-
case 'failed': return C.bad;
|
|
41
|
-
case 'completed': return C.completed;
|
|
42
|
-
default: return C.dim;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** One captured recursion-guard trip — built locally from `lastRecursionGuard` deltas (WO-131: the domain state only keeps the most recent trip per graph plus a running counter, no history list). */
|
|
47
|
-
interface GuardTripRecord {
|
|
48
|
-
readonly graphId: string;
|
|
49
|
-
readonly graphTitle: string;
|
|
50
|
-
readonly depth: number;
|
|
51
|
-
readonly activeAgents: number;
|
|
52
|
-
readonly reason: string;
|
|
53
|
-
readonly nodeId?: string | undefined;
|
|
54
|
-
readonly triggeredAt: number;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const MAX_GUARD_TRIP_HISTORY = 50;
|
|
58
|
-
|
|
59
|
-
/** The cross-panel jump Enter dispatches for a focused node, consumed by the panel-integration router. */
|
|
60
|
-
export interface OrchestrationNodeJump {
|
|
61
|
-
readonly kind: 'agent-jump' | 'task-jump';
|
|
62
|
-
readonly id: string;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function nodesOf(graph: OrchestrationGraphRecord): OrchestrationNodeRecord[] {
|
|
66
|
-
return graph.nodeOrder
|
|
67
|
-
.map((nodeId) => graph.nodes.get(nodeId))
|
|
68
|
-
.filter((node): node is OrchestrationNodeRecord => Boolean(node));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRecord> {
|
|
72
|
-
private readonly readModel?: UiReadModel<UiOrchestrationSnapshot>;
|
|
73
|
-
private readonly unsub: (() => void) | null;
|
|
74
|
-
|
|
75
|
-
/** Stabilizes selection across re-sorts (new graphs prepend by createdAt) — keyed by graph id, not raw index. */
|
|
76
|
-
private _selectedGraphId: string | null = null;
|
|
77
|
-
|
|
78
|
-
/** Tab toggles which list up/down drives. */
|
|
79
|
-
private _focus: 'graph' | 'node' = 'graph';
|
|
80
|
-
/** Cursor within the selected graph's node list; persists across focus toggles. */
|
|
81
|
-
private _nodeCursor = 0;
|
|
82
|
-
|
|
83
|
-
/** Recursion-guard trip history accumulated from `lastRecursionGuard` deltas across all graphs, newest first. */
|
|
84
|
-
private _guardTripHistory: GuardTripRecord[] = [];
|
|
85
|
-
private readonly _seenGuardTripAt = new Map<string, number>();
|
|
86
|
-
|
|
87
|
-
/** Set by handleInput('enter') while node-focused; consumed by the panel-integration router immediately after. */
|
|
88
|
-
private _pendingNodeJump: OrchestrationNodeJump | null = null;
|
|
89
|
-
|
|
90
|
-
public constructor(readModel?: UiReadModel<UiOrchestrationSnapshot>) {
|
|
91
|
-
super('orchestration', 'Orchestration', '◒', 'runtime-ops');
|
|
92
|
-
this.readModel = readModel;
|
|
93
|
-
this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
public override onDestroy(): void {
|
|
97
|
-
this.unsub?.();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
// ScrollableListPanel contract
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
|
|
104
|
-
protected getItems(): readonly OrchestrationGraphRecord[] {
|
|
105
|
-
if (!this.readModel) return [];
|
|
106
|
-
return [...this.readModel.getSnapshot().graphs].sort((a, b) => b.createdAt - a.createdAt);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Keeps `selectedIndex` pointed at the same graph id after a re-sort (e.g. a new graph prepends). */
|
|
110
|
-
private _syncSelectionFromId(graphs: readonly OrchestrationGraphRecord[]): void {
|
|
111
|
-
if (this._selectedGraphId) {
|
|
112
|
-
const idx = graphs.findIndex((g) => g.id === this._selectedGraphId);
|
|
113
|
-
if (idx >= 0) {
|
|
114
|
-
this.selectedIndex = idx;
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
this.selectedIndex = graphs.length === 0 ? 0 : Math.min(this.selectedIndex, graphs.length - 1);
|
|
119
|
-
this._selectedGraphId = graphs.at(this.selectedIndex)?.id ?? null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** Appends any newly-observed recursion-guard trips (across all graphs) to the local history list. */
|
|
123
|
-
private _captureGuardTrips(): void {
|
|
124
|
-
if (!this.readModel) return;
|
|
125
|
-
for (const graph of this.readModel.getSnapshot().graphs) {
|
|
126
|
-
const trip = graph.lastRecursionGuard;
|
|
127
|
-
if (!trip) continue;
|
|
128
|
-
if (this._seenGuardTripAt.get(graph.id) === trip.triggeredAt) continue;
|
|
129
|
-
this._seenGuardTripAt.set(graph.id, trip.triggeredAt);
|
|
130
|
-
this._guardTripHistory.unshift({
|
|
131
|
-
graphId: graph.id,
|
|
132
|
-
graphTitle: graph.title,
|
|
133
|
-
depth: trip.depth,
|
|
134
|
-
activeAgents: trip.activeAgents,
|
|
135
|
-
reason: trip.reason,
|
|
136
|
-
nodeId: trip.nodeId,
|
|
137
|
-
triggeredAt: trip.triggeredAt,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
if (this._guardTripHistory.length > MAX_GUARD_TRIP_HISTORY) {
|
|
141
|
-
this._guardTripHistory.length = MAX_GUARD_TRIP_HISTORY;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
protected renderItem(
|
|
146
|
-
graph: OrchestrationGraphRecord,
|
|
147
|
-
index: number,
|
|
148
|
-
selected: boolean,
|
|
149
|
-
width: number,
|
|
150
|
-
): Line {
|
|
151
|
-
const bg = selected ? C.selectBg : undefined;
|
|
152
|
-
const marker = selected ? '▸ ' : ' ';
|
|
153
|
-
const nodeCount = `${graph.nodeOrder.length}n`;
|
|
154
|
-
// Reserve room for marker(2) + status(10) + mode(17) + id(8) + nodes(~5) + gaps.
|
|
155
|
-
const titleBudget = Math.max(0, width - 47);
|
|
156
|
-
return buildPanelLine(width, [
|
|
157
|
-
[marker, selected ? C.value : C.dim, bg],
|
|
158
|
-
[graph.status.padEnd(10), statusColor(graph.status), bg],
|
|
159
|
-
[` ${graph.mode.padEnd(17)}`, C.value, bg],
|
|
160
|
-
[` ${graph.id.slice(0, 8)} `, C.dim, bg],
|
|
161
|
-
[`${nodeCount.padStart(4)} `, C.dim, bg],
|
|
162
|
-
[truncateDisplay(graph.title, titleBudget), C.value, bg],
|
|
163
|
-
]);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
protected override getPalette(): PanelPalette {
|
|
167
|
-
return C;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
protected override getEmptyStateMessage(): string {
|
|
171
|
-
return ' No orchestration graphs recorded yet.';
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
|
-
// Input — Tab switches graph-list/node-list focus; up/down drives whichever
|
|
176
|
-
// list has focus; Enter on a focused node jumps to the Inspector (agent
|
|
177
|
-
// nodes) or Tasks (task-backed nodes) via the panel-integration router.
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
|
|
180
|
-
public override handleInput(key: string): boolean {
|
|
181
|
-
if (this.lastError !== null) this.clearError();
|
|
182
|
-
|
|
183
|
-
const graphs = this.getItems();
|
|
184
|
-
this._syncSelectionFromId(graphs);
|
|
185
|
-
const selectedGraph = this.getSelectedItem();
|
|
186
|
-
const nodes = selectedGraph ? nodesOf(selectedGraph) : [];
|
|
187
|
-
|
|
188
|
-
if (key === 'tab') {
|
|
189
|
-
if (nodes.length === 0) return false;
|
|
190
|
-
this._focus = this._focus === 'graph' ? 'node' : 'graph';
|
|
191
|
-
this._nodeCursor = Math.min(this._nodeCursor, Math.max(0, nodes.length - 1));
|
|
192
|
-
this.markDirty();
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (this._focus === 'node' && nodes.length > 0) {
|
|
197
|
-
switch (key) {
|
|
198
|
-
case 'up':
|
|
199
|
-
case 'k':
|
|
200
|
-
this._nodeCursor = Math.max(0, this._nodeCursor - 1);
|
|
201
|
-
this.markDirty();
|
|
202
|
-
return true;
|
|
203
|
-
case 'down':
|
|
204
|
-
case 'j':
|
|
205
|
-
this._nodeCursor = Math.min(nodes.length - 1, this._nodeCursor + 1);
|
|
206
|
-
this.markDirty();
|
|
207
|
-
return true;
|
|
208
|
-
case 'enter':
|
|
209
|
-
case 'return': {
|
|
210
|
-
const node = nodes[this._nodeCursor];
|
|
211
|
-
if (node?.agentId) {
|
|
212
|
-
this._pendingNodeJump = { kind: 'agent-jump', id: node.agentId };
|
|
213
|
-
return true;
|
|
214
|
-
}
|
|
215
|
-
if (node?.taskId) {
|
|
216
|
-
this._pendingNodeJump = { kind: 'task-jump', id: node.taskId };
|
|
217
|
-
return true;
|
|
218
|
-
}
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
default:
|
|
222
|
-
return false;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const consumed = super.handleInput(key);
|
|
227
|
-
if (consumed) {
|
|
228
|
-
const refreshed = this.getItems();
|
|
229
|
-
this._selectedGraphId = refreshed.at(this.selectedIndex)?.id ?? this._selectedGraphId;
|
|
230
|
-
// A different graph is selected — the node cursor no longer refers to a
|
|
231
|
-
// meaningful row until Tab re-engages node focus for the new graph.
|
|
232
|
-
this._focus = 'graph';
|
|
233
|
-
this._nodeCursor = 0;
|
|
234
|
-
}
|
|
235
|
-
return consumed;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Consumed by the panel-integration router immediately after handleInput
|
|
240
|
-
* returns true for the same Enter keystroke while node-focused.
|
|
241
|
-
*/
|
|
242
|
-
public consumePendingNodeJump(): OrchestrationNodeJump | null {
|
|
243
|
-
const pending = this._pendingNodeJump;
|
|
244
|
-
this._pendingNodeJump = null;
|
|
245
|
-
return pending;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// ---------------------------------------------------------------------------
|
|
249
|
-
// Render — multi-section layout (posture + scrollable graphs + detail + nodes)
|
|
250
|
-
// ---------------------------------------------------------------------------
|
|
251
|
-
|
|
252
|
-
public render(width: number, height: number): Line[] {
|
|
253
|
-
const intro = 'Task graphs, node contracts, recursion guards, and WRFC-visible orchestration state.';
|
|
254
|
-
|
|
255
|
-
if (!this.readModel) {
|
|
256
|
-
this.needsRender = false;
|
|
257
|
-
const workspace = buildPanelWorkspace(width, height, {
|
|
258
|
-
title: 'Orchestration Control Room',
|
|
259
|
-
intro,
|
|
260
|
-
sections: [{
|
|
261
|
-
lines: buildEmptyState(
|
|
262
|
-
width,
|
|
263
|
-
' Runtime store not wired into this panel yet.',
|
|
264
|
-
'The orchestration workspace needs the live runtime store before it can show graphs, nodes, and recursion guard events.',
|
|
265
|
-
[],
|
|
266
|
-
C,
|
|
267
|
-
),
|
|
268
|
-
}],
|
|
269
|
-
palette: C,
|
|
270
|
-
});
|
|
271
|
-
while (workspace.length < height) workspace.push(createEmptyLine(width));
|
|
272
|
-
return workspace;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
this._captureGuardTrips();
|
|
276
|
-
|
|
277
|
-
const snapshot = this.readModel.getSnapshot();
|
|
278
|
-
const graphs = this.getItems();
|
|
279
|
-
this._syncSelectionFromId(graphs);
|
|
280
|
-
const postureLines = [
|
|
281
|
-
buildKeyValueLine(width, [
|
|
282
|
-
{ label: 'graphs', value: String(snapshot.totalGraphs), valueColor: snapshot.totalGraphs > 0 ? C.value : C.dim },
|
|
283
|
-
{ label: 'active', value: String(snapshot.activeGraphIds.length), valueColor: snapshot.activeGraphIds.length > 0 ? C.good : C.dim },
|
|
284
|
-
{ label: 'completed', value: String(snapshot.totalCompletedGraphs), valueColor: snapshot.totalCompletedGraphs > 0 ? C.completed : C.dim },
|
|
285
|
-
{ label: 'failed', value: String(snapshot.totalFailedGraphs), valueColor: snapshot.totalFailedGraphs > 0 ? C.bad : C.dim },
|
|
286
|
-
{ label: 'guards', value: String(snapshot.recursionGuardTrips), valueColor: snapshot.recursionGuardTrips > 0 ? C.warn : C.dim },
|
|
287
|
-
], C),
|
|
288
|
-
];
|
|
289
|
-
if (graphs.length === 0) {
|
|
290
|
-
this.needsRender = false;
|
|
291
|
-
const workspace = buildPanelWorkspace(width, height, {
|
|
292
|
-
title: 'Orchestration Control Room',
|
|
293
|
-
intro,
|
|
294
|
-
sections: [{
|
|
295
|
-
title: 'Orchestration posture',
|
|
296
|
-
lines: [
|
|
297
|
-
...postureLines,
|
|
298
|
-
...buildEmptyState(
|
|
299
|
-
width,
|
|
300
|
-
this.getEmptyStateMessage(),
|
|
301
|
-
'Graphs, nodes, child contracts, and recursion guard trips will appear here as orchestration starts.',
|
|
302
|
-
[
|
|
303
|
-
{ command: '/tasks', summary: 'create or inspect task flows that feed orchestration graphs' },
|
|
304
|
-
{ command: '/communication', summary: 'review structured agent communication alongside graph execution' },
|
|
305
|
-
],
|
|
306
|
-
C,
|
|
307
|
-
),
|
|
308
|
-
],
|
|
309
|
-
}],
|
|
310
|
-
palette: C,
|
|
311
|
-
});
|
|
312
|
-
while (workspace.length < height) workspace.push(createEmptyLine(width));
|
|
313
|
-
return workspace;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
this.clampSelection();
|
|
317
|
-
const selected = this.getSelectedItem()!;
|
|
318
|
-
const isActive = snapshot.activeGraphIds.includes(selected.id);
|
|
319
|
-
const elapsed = selected.startedAt
|
|
320
|
-
? formatElapsed(Math.max(0, (selected.endedAt ?? Date.now()) - selected.startedAt))
|
|
321
|
-
: 'n/a';
|
|
322
|
-
const detailLines: Line[] = [
|
|
323
|
-
buildPanelLine(width, [
|
|
324
|
-
[' Title: ', C.label],
|
|
325
|
-
[truncateDisplay(selected.title, Math.max(0, width - 11)), C.value],
|
|
326
|
-
]),
|
|
327
|
-
buildPanelLine(width, [
|
|
328
|
-
[' Status: ', C.label],
|
|
329
|
-
[selected.status, statusColor(selected.status)],
|
|
330
|
-
[' Mode: ', C.label],
|
|
331
|
-
[selected.mode, C.value],
|
|
332
|
-
[' Live: ', C.label],
|
|
333
|
-
[isActive ? 'yes' : 'no', isActive ? C.good : C.dim],
|
|
334
|
-
]),
|
|
335
|
-
buildPanelLine(width, [
|
|
336
|
-
[' Nodes: ', C.label],
|
|
337
|
-
[String(selected.nodeOrder.length), C.value],
|
|
338
|
-
[' Started: ', C.label],
|
|
339
|
-
[selected.startedAt ? new Date(selected.startedAt).toLocaleTimeString() : 'n/a', C.dim],
|
|
340
|
-
[' Ended: ', C.label],
|
|
341
|
-
[selected.endedAt ? new Date(selected.endedAt).toLocaleTimeString() : 'n/a', C.dim],
|
|
342
|
-
[' Duration: ', C.label],
|
|
343
|
-
[elapsed, C.dim],
|
|
344
|
-
]),
|
|
345
|
-
];
|
|
346
|
-
|
|
347
|
-
const nodes = nodesOf(selected);
|
|
348
|
-
const nodeCursor = Math.min(this._nodeCursor, Math.max(0, nodes.length - 1));
|
|
349
|
-
const focusNode = nodes[nodeCursor];
|
|
350
|
-
detailLines.push(...this._buildNodeContractLines(width, focusNode));
|
|
351
|
-
|
|
352
|
-
const nodeLines: Line[] = nodes.length === 0
|
|
353
|
-
? [buildPanelLine(width, [[' No nodes recorded yet.', C.dim]])]
|
|
354
|
-
: nodes.map((node, idx) => {
|
|
355
|
-
const isNodeCursor = this._focus === 'node' && idx === nodeCursor;
|
|
356
|
-
const bg = isNodeCursor ? C.selectBg : undefined;
|
|
357
|
-
const marker = isNodeCursor ? '▸ ' : ' ';
|
|
358
|
-
const depends = node.dependencyNodeIds.length > 0 ? ` deps:${node.dependencyNodeIds.length}` : '';
|
|
359
|
-
return buildPanelLine(width, [
|
|
360
|
-
[marker, isNodeCursor ? C.value : C.label, bg],
|
|
361
|
-
[node.status.padEnd(10), statusColor(node.status), bg],
|
|
362
|
-
[` ${node.role.padEnd(10)}`, C.value, bg],
|
|
363
|
-
[` ${node.id.slice(0, 8)} `, C.dim, bg],
|
|
364
|
-
[truncateDisplay(`${node.title}${depends}`, Math.max(0, width - 36)), C.value, bg],
|
|
365
|
-
]);
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
const guardTripLines: Line[] = this._guardTripHistory.slice(0, 5).map((trip) => buildPanelLine(width, [
|
|
369
|
-
[` ${new Date(trip.triggeredAt).toLocaleTimeString()} `, C.dim],
|
|
370
|
-
[truncateDisplay(trip.graphTitle, Math.max(0, width - 60)), C.value],
|
|
371
|
-
[` depth ${trip.depth} active ${trip.activeAgents}`, C.warn],
|
|
372
|
-
[` ${truncateDisplay(trip.reason, Math.max(0, width - 50))}`, C.dim],
|
|
373
|
-
]));
|
|
374
|
-
|
|
375
|
-
const scrollableLines: Line[] = graphs.map((graph, index) =>
|
|
376
|
-
this.renderItem(graph, index, index === this.selectedIndex, width),
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
// Context-aware footer: surface position + only the keys that work in the
|
|
380
|
-
// currently-focused list (graph list vs node list).
|
|
381
|
-
const footerLines: Line[] = [
|
|
382
|
-
buildKeyboardHints(width, [
|
|
383
|
-
{ keys: `${this.selectedIndex + 1}/${graphs.length}`, label: 'graph' },
|
|
384
|
-
...(nodes.length > 0 ? [{ keys: 'Tab', label: this._focus === 'node' ? 'graph focus' : 'node focus' }] : []),
|
|
385
|
-
...(this._focus === 'node'
|
|
386
|
-
? [{ keys: '↑/↓', label: 'node' }, { keys: 'Enter', label: 'jump to node detail' }]
|
|
387
|
-
: [{ keys: '↑/↓', label: 'select' }, { keys: 'g/G', label: 'top/bottom' }, { keys: 'PgUp/PgDn', label: 'page' }]),
|
|
388
|
-
], C),
|
|
389
|
-
];
|
|
390
|
-
|
|
391
|
-
const postureSection: PanelWorkspaceSection = { title: 'Orchestration posture', lines: postureLines };
|
|
392
|
-
const selectedGraphSection: PanelWorkspaceSection = { title: 'Selected Graph', lines: detailLines };
|
|
393
|
-
const nodesSection: PanelWorkspaceSection = { title: 'Nodes', lines: nodeLines };
|
|
394
|
-
const afterSections: PanelWorkspaceSection[] = [selectedGraphSection, nodesSection];
|
|
395
|
-
if (guardTripLines.length > 0) {
|
|
396
|
-
afterSections.push({ title: 'Guard trips (this session)', lines: guardTripLines });
|
|
397
|
-
}
|
|
398
|
-
const graphsSection = resolveScrollablePanelSection(width, height, {
|
|
399
|
-
intro,
|
|
400
|
-
footerLines,
|
|
401
|
-
palette: C,
|
|
402
|
-
beforeSections: [postureSection],
|
|
403
|
-
section: {
|
|
404
|
-
title: 'Graphs',
|
|
405
|
-
scrollableLines,
|
|
406
|
-
selectedIndex: this.selectedIndex,
|
|
407
|
-
scrollOffset: this.scrollStart,
|
|
408
|
-
minRows: 4,
|
|
409
|
-
appendWindowSummary: { dimColor: C.dim },
|
|
410
|
-
},
|
|
411
|
-
afterSections,
|
|
412
|
-
});
|
|
413
|
-
this.scrollStart = graphsSection.scrollOffset;
|
|
414
|
-
|
|
415
|
-
const sections: PanelWorkspaceSection[] = [
|
|
416
|
-
postureSection,
|
|
417
|
-
graphsSection.section,
|
|
418
|
-
...afterSections,
|
|
419
|
-
];
|
|
420
|
-
this.needsRender = false;
|
|
421
|
-
const lines = buildPanelWorkspace(width, height, {
|
|
422
|
-
title: 'Orchestration Control Room',
|
|
423
|
-
intro,
|
|
424
|
-
sections,
|
|
425
|
-
footerLines,
|
|
426
|
-
palette: C,
|
|
427
|
-
});
|
|
428
|
-
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
429
|
-
return lines.slice(0, height);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/** Full node contract — allowedTools/requiredEvidence/writeScope/executionProtocol/reviewMode/communicationLane, plus inheritance. */
|
|
433
|
-
private _buildNodeContractLines(width: number, node: OrchestrationNodeRecord | undefined): Line[] {
|
|
434
|
-
if (!node) return [];
|
|
435
|
-
const contract = node.contract;
|
|
436
|
-
if (!contract) {
|
|
437
|
-
return [buildPanelLine(width, [[' Contract: ', C.label], ['none recorded', C.dim]])];
|
|
438
|
-
}
|
|
439
|
-
const list = (arr: string[] | undefined) => arr && arr.length > 0 ? arr.join(', ') : 'none';
|
|
440
|
-
return [
|
|
441
|
-
buildPanelLine(width, [
|
|
442
|
-
[' Allowed tools: ', C.label],
|
|
443
|
-
[truncateDisplay(list(contract.allowedTools), Math.max(0, width - 18)), C.value],
|
|
444
|
-
]),
|
|
445
|
-
buildPanelLine(width, [
|
|
446
|
-
[' Required evidence: ', C.label],
|
|
447
|
-
[truncateDisplay(list(contract.requiredEvidence), Math.max(0, width - 22)), C.value],
|
|
448
|
-
]),
|
|
449
|
-
buildPanelLine(width, [
|
|
450
|
-
[' Write scope: ', C.label],
|
|
451
|
-
[truncateDisplay(list(contract.writeScope), Math.max(0, width - 16)), C.value],
|
|
452
|
-
]),
|
|
453
|
-
buildPanelLine(width, [
|
|
454
|
-
[' Flow: ', C.label],
|
|
455
|
-
[contract.executionProtocol ?? 'direct', C.value],
|
|
456
|
-
[' Review: ', C.label],
|
|
457
|
-
[contract.reviewMode ?? 'none', C.value],
|
|
458
|
-
[' Lane: ', C.label],
|
|
459
|
-
[contract.communicationLane ?? 'default', C.value],
|
|
460
|
-
[' Inherits: ', C.label],
|
|
461
|
-
[contract.inheritsParentConstraints ? 'yes' : 'no', C.value],
|
|
462
|
-
]),
|
|
463
|
-
];
|
|
464
|
-
}
|
|
465
|
-
}
|