@pellux/goodvibes-tui 0.27.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 +124 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/context-usage.ts +53 -0
- package/src/core/conversation.ts +4 -6
- package/src/core/session-recovery.ts +2 -0
- package/src/core/turn-event-wiring.ts +1 -0
- package/src/input/commands/cost-runtime.ts +49 -0
- package/src/input/commands/eval.ts +4 -3
- package/src/input/commands/operator-runtime.ts +5 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-feed-routes.ts +60 -26
- package/src/input/handler-feed.ts +17 -8
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-shortcuts.ts +51 -0
- package/src/input/handler-ui-state.ts +4 -0
- package/src/input/handler.ts +43 -6
- package/src/input/keybindings.ts +53 -8
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +117 -1
- package/src/main.ts +5 -2
- package/src/panels/agent-inspector-panel.ts +139 -45
- package/src/panels/agent-inspector-shared.ts +147 -3
- package/src/panels/approval-panel.ts +204 -102
- package/src/panels/automation-control-panel.ts +370 -103
- package/src/panels/base-panel.ts +26 -2
- 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 +253 -75
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +159 -56
- package/src/panels/confirm-state.ts +8 -1
- package/src/panels/control-plane-panel.ts +354 -95
- package/src/panels/cost-tracker-panel.ts +243 -57
- package/src/panels/debug-panel.ts +347 -159
- package/src/panels/diff-panel.ts +257 -82
- package/src/panels/docs-panel.ts +166 -64
- package/src/panels/eval-panel.ts +336 -109
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +238 -159
- package/src/panels/file-preview-panel.ts +141 -59
- package/src/panels/git-panel.ts +360 -191
- package/src/panels/hooks-panel.ts +181 -19
- package/src/panels/incident-review-panel.ts +252 -43
- package/src/panels/index.ts +0 -4
- package/src/panels/intelligence-panel.ts +310 -103
- package/src/panels/knowledge-graph-panel.ts +480 -58
- package/src/panels/local-auth-panel.ts +251 -33
- package/src/panels/marketplace-panel.ts +219 -33
- package/src/panels/memory-panel.ts +83 -81
- package/src/panels/ops-control-panel.ts +211 -32
- package/src/panels/ops-strategy-panel.ts +131 -45
- package/src/panels/orchestration-panel.ts +259 -67
- package/src/panels/panel-list-panel.ts +184 -136
- package/src/panels/panel-manager.ts +120 -13
- package/src/panels/plan-dashboard-panel.ts +507 -74
- package/src/panels/plugins-panel.ts +227 -34
- package/src/panels/policy-panel.ts +264 -55
- package/src/panels/polish-core.ts +162 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +67 -149
- package/src/panels/project-planning-answer-actions.ts +134 -0
- package/src/panels/project-planning-panel.ts +84 -113
- package/src/panels/provider-health-panel.ts +438 -480
- 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 +136 -38
- package/src/panels/remote-panel.ts +123 -38
- package/src/panels/routes-panel.ts +87 -24
- package/src/panels/sandbox-panel.ts +232 -65
- package/src/panels/scrollable-list-panel.ts +143 -145
- package/src/panels/security-panel.ts +204 -52
- package/src/panels/services-panel.ts +174 -76
- package/src/panels/session-browser-panel.ts +101 -6
- package/src/panels/session-maintenance.ts +4 -122
- package/src/panels/settings-sync-panel.ts +346 -68
- package/src/panels/skills-panel.ts +157 -89
- package/src/panels/subscription-panel.ts +105 -34
- package/src/panels/symbol-outline-panel.ts +287 -154
- package/src/panels/system-messages-panel.ts +172 -38
- package/src/panels/tasks-panel.ts +348 -134
- package/src/panels/thinking-panel.ts +66 -32
- package/src/panels/token-budget-panel.ts +233 -46
- package/src/panels/tool-inspector-panel.ts +170 -54
- package/src/panels/types.ts +65 -4
- package/src/panels/work-plan-panel.ts +393 -39
- package/src/panels/worktree-panel.ts +239 -61
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +146 -150
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- 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 +30 -11
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/overlay-box.ts +16 -10
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/process-indicator.ts +3 -1
- package/src/renderer/progress.ts +8 -6
- 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/tab-strip.ts +148 -34
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/ui-factory.ts +45 -36
- 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/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- 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 -666
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/panels/context-visualizer-panel.ts +0 -214
- package/src/panels/forensics-panel.ts +0 -364
- package/src/panels/panel-picker.ts +0 -106
- package/src/panels/provider-account-snapshot.ts +0 -259
- package/src/panels/provider-accounts-panel.ts +0 -218
- package/src/panels/provider-stats-panel.ts +0 -366
- package/src/panels/schedule-panel.ts +0 -342
- package/src/panels/watchers-panel.ts +0 -193
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
import type { Line } from '../types/grid.ts';
|
|
10
10
|
import { createEmptyLine } from '../types/grid.ts';
|
|
11
|
+
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
11
12
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
12
13
|
import type { UiOrchestrationSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
|
|
13
|
-
import type { OrchestrationGraphRecord } from '@/runtime/index.ts';
|
|
14
|
+
import type { OrchestrationGraphRecord, OrchestrationNodeRecord } from '@/runtime/index.ts';
|
|
15
|
+
import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
14
16
|
import {
|
|
15
17
|
buildEmptyState,
|
|
16
|
-
|
|
18
|
+
buildKeyboardHints,
|
|
17
19
|
buildKeyValueLine,
|
|
18
20
|
buildPanelLine,
|
|
19
21
|
buildPanelWorkspace,
|
|
@@ -24,34 +26,69 @@ import {
|
|
|
24
26
|
type PanelWorkspaceSection,
|
|
25
27
|
} from './polish.ts';
|
|
26
28
|
|
|
29
|
+
// Domain accents only; base chrome (header/headerBg/info/good/warn/bad/
|
|
30
|
+
// selectBg) comes from DEFAULT_PANEL_PALETTE.
|
|
27
31
|
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
28
|
-
|
|
29
|
-
headerBg: '#1e293b',
|
|
30
|
-
running: '#22c55e',
|
|
31
|
-
ready: '#38bdf8',
|
|
32
|
-
blocked: '#f59e0b',
|
|
33
|
-
failed: '#ef4444',
|
|
34
|
-
completed: '#a78bfa',
|
|
35
|
-
selectBg: '#0f172a',
|
|
32
|
+
completed: '#a78bfa', // completed-graph badge, distinct from running/good
|
|
36
33
|
} as const);
|
|
37
34
|
|
|
38
35
|
function statusColor(status: string): string {
|
|
39
36
|
switch (status) {
|
|
40
|
-
case 'ready': return C.
|
|
41
|
-
case 'running': return C.
|
|
42
|
-
case 'blocked': return C.
|
|
43
|
-
case 'failed': return C.
|
|
37
|
+
case 'ready': return C.info;
|
|
38
|
+
case 'running': return C.good;
|
|
39
|
+
case 'blocked': return C.warn;
|
|
40
|
+
case 'failed': return C.bad;
|
|
44
41
|
case 'completed': return C.completed;
|
|
45
42
|
default: return C.dim;
|
|
46
43
|
}
|
|
47
44
|
}
|
|
48
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
|
+
|
|
49
71
|
export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRecord> {
|
|
50
72
|
private readonly readModel?: UiReadModel<UiOrchestrationSnapshot>;
|
|
51
73
|
private readonly unsub: (() => void) | null;
|
|
52
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
|
+
|
|
53
90
|
public constructor(readModel?: UiReadModel<UiOrchestrationSnapshot>) {
|
|
54
|
-
super('orchestration', 'Orchestration', '
|
|
91
|
+
super('orchestration', 'Orchestration', '◒', 'runtime-ops');
|
|
55
92
|
this.readModel = readModel;
|
|
56
93
|
this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
|
|
57
94
|
}
|
|
@@ -69,6 +106,42 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
69
106
|
return [...this.readModel.getSnapshot().graphs].sort((a, b) => b.createdAt - a.createdAt);
|
|
70
107
|
}
|
|
71
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
|
+
|
|
72
145
|
protected renderItem(
|
|
73
146
|
graph: OrchestrationGraphRecord,
|
|
74
147
|
index: number,
|
|
@@ -76,12 +149,17 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
76
149
|
width: number,
|
|
77
150
|
): Line {
|
|
78
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);
|
|
79
156
|
return buildPanelLine(width, [
|
|
80
|
-
[
|
|
157
|
+
[marker, selected ? C.value : C.dim, bg],
|
|
81
158
|
[graph.status.padEnd(10), statusColor(graph.status), bg],
|
|
82
159
|
[` ${graph.mode.padEnd(17)}`, C.value, bg],
|
|
83
160
|
[` ${graph.id.slice(0, 8)} `, C.dim, bg],
|
|
84
|
-
[
|
|
161
|
+
[`${nodeCount.padStart(4)} `, C.dim, bg],
|
|
162
|
+
[truncateDisplay(graph.title, titleBudget), C.value, bg],
|
|
85
163
|
]);
|
|
86
164
|
}
|
|
87
165
|
|
|
@@ -94,9 +172,79 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
94
172
|
}
|
|
95
173
|
|
|
96
174
|
// ---------------------------------------------------------------------------
|
|
97
|
-
// Input —
|
|
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.
|
|
98
178
|
// ---------------------------------------------------------------------------
|
|
99
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
|
+
|
|
100
248
|
// ---------------------------------------------------------------------------
|
|
101
249
|
// Render — multi-section layout (posture + scrollable graphs + detail + nodes)
|
|
102
250
|
// ---------------------------------------------------------------------------
|
|
@@ -114,7 +262,7 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
114
262
|
width,
|
|
115
263
|
' Runtime store not wired into this panel yet.',
|
|
116
264
|
'The orchestration workspace needs the live runtime store before it can show graphs, nodes, and recursion guard events.',
|
|
117
|
-
[
|
|
265
|
+
[],
|
|
118
266
|
C,
|
|
119
267
|
),
|
|
120
268
|
}],
|
|
@@ -124,17 +272,19 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
124
272
|
return workspace;
|
|
125
273
|
}
|
|
126
274
|
|
|
275
|
+
this._captureGuardTrips();
|
|
276
|
+
|
|
127
277
|
const snapshot = this.readModel.getSnapshot();
|
|
128
278
|
const graphs = this.getItems();
|
|
279
|
+
this._syncSelectionFromId(graphs);
|
|
129
280
|
const postureLines = [
|
|
130
281
|
buildKeyValueLine(width, [
|
|
131
282
|
{ label: 'graphs', value: String(snapshot.totalGraphs), valueColor: snapshot.totalGraphs > 0 ? C.value : C.dim },
|
|
132
|
-
{ label: 'active', value: String(snapshot.activeGraphIds.length), valueColor: snapshot.activeGraphIds.length > 0 ? C.
|
|
283
|
+
{ label: 'active', value: String(snapshot.activeGraphIds.length), valueColor: snapshot.activeGraphIds.length > 0 ? C.good : C.dim },
|
|
133
284
|
{ label: 'completed', value: String(snapshot.totalCompletedGraphs), valueColor: snapshot.totalCompletedGraphs > 0 ? C.completed : C.dim },
|
|
134
|
-
{ label: 'failed', value: String(snapshot.totalFailedGraphs), valueColor: snapshot.totalFailedGraphs > 0 ? C.
|
|
135
|
-
{ label: 'guards', value: String(snapshot.recursionGuardTrips), valueColor: snapshot.recursionGuardTrips > 0 ? C.
|
|
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 },
|
|
136
287
|
], C),
|
|
137
|
-
buildGuidanceLine(width, '/orchestration', 'inspect recursive execution posture, graph health, and node contract flow', C),
|
|
138
288
|
];
|
|
139
289
|
if (graphs.length === 0) {
|
|
140
290
|
this.needsRender = false;
|
|
@@ -164,15 +314,23 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
164
314
|
}
|
|
165
315
|
|
|
166
316
|
this.clampSelection();
|
|
167
|
-
const selected =
|
|
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';
|
|
168
322
|
const detailLines: Line[] = [
|
|
169
323
|
buildPanelLine(width, [
|
|
170
324
|
[' Title: ', C.label],
|
|
171
|
-
[selected.title, C.value],
|
|
325
|
+
[truncateDisplay(selected.title, Math.max(0, width - 11)), C.value],
|
|
326
|
+
]),
|
|
327
|
+
buildPanelLine(width, [
|
|
172
328
|
[' Status: ', C.label],
|
|
173
329
|
[selected.status, statusColor(selected.status)],
|
|
174
330
|
[' Mode: ', C.label],
|
|
175
331
|
[selected.mode, C.value],
|
|
332
|
+
[' Live: ', C.label],
|
|
333
|
+
[isActive ? 'yes' : 'no', isActive ? C.good : C.dim],
|
|
176
334
|
]),
|
|
177
335
|
buildPanelLine(width, [
|
|
178
336
|
[' Nodes: ', C.label],
|
|
@@ -181,65 +339,65 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
181
339
|
[selected.startedAt ? new Date(selected.startedAt).toLocaleTimeString() : 'n/a', C.dim],
|
|
182
340
|
[' Ended: ', C.label],
|
|
183
341
|
[selected.endedAt ? new Date(selected.endedAt).toLocaleTimeString() : 'n/a', C.dim],
|
|
342
|
+
[' Duration: ', C.label],
|
|
343
|
+
[elapsed, C.dim],
|
|
184
344
|
]),
|
|
185
345
|
];
|
|
186
|
-
if (selected.lastRecursionGuard) {
|
|
187
|
-
detailLines.push(buildPanelLine(width, [
|
|
188
|
-
[' Recursion guard: ', C.label],
|
|
189
|
-
[`depth ${selected.lastRecursionGuard.depth} active ${selected.lastRecursionGuard.activeAgents} ${selected.lastRecursionGuard.reason}`, C.blocked],
|
|
190
|
-
]));
|
|
191
|
-
}
|
|
192
346
|
|
|
193
|
-
const nodes = selected
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (focusNode?.contract) {
|
|
198
|
-
const toolCount = focusNode.contract.allowedTools?.length ?? 0;
|
|
199
|
-
const evidenceCount = focusNode.contract.requiredEvidence?.length ?? 0;
|
|
200
|
-
const scopeCount = focusNode.contract.writeScope?.length ?? 0;
|
|
201
|
-
detailLines.push(buildPanelLine(width, [
|
|
202
|
-
[' Contract: ', C.label],
|
|
203
|
-
[`tools ${toolCount}`, C.value],
|
|
204
|
-
[' evidence ', C.label],
|
|
205
|
-
[String(evidenceCount), C.value],
|
|
206
|
-
[' write scope ', C.label],
|
|
207
|
-
[String(scopeCount), C.value],
|
|
208
|
-
]));
|
|
209
|
-
detailLines.push(buildPanelLine(width, [
|
|
210
|
-
[' Flow: ', C.label],
|
|
211
|
-
[focusNode.contract.executionProtocol ?? 'direct', C.value],
|
|
212
|
-
[' Review: ', C.label],
|
|
213
|
-
[focusNode.contract.reviewMode ?? 'none', C.value],
|
|
214
|
-
[' Lane: ', C.label],
|
|
215
|
-
[focusNode.contract.communicationLane ?? 'default', C.value],
|
|
216
|
-
[' Inherits: ', C.label],
|
|
217
|
-
[focusNode.contract.inheritsParentConstraints ? 'yes' : 'no', C.value],
|
|
218
|
-
]));
|
|
219
|
-
}
|
|
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));
|
|
220
351
|
|
|
221
352
|
const nodeLines: Line[] = nodes.length === 0
|
|
222
353
|
? [buildPanelLine(width, [[' No nodes recorded yet.', C.dim]])]
|
|
223
|
-
: nodes.map((node) => {
|
|
354
|
+
: nodes.map((node, idx) => {
|
|
355
|
+
const isNodeCursor = this._focus === 'node' && idx === nodeCursor;
|
|
356
|
+
const bg = isNodeCursor ? C.selectBg : undefined;
|
|
357
|
+
const marker = isNodeCursor ? '▸ ' : ' ';
|
|
224
358
|
const depends = node.dependencyNodeIds.length > 0 ? ` deps:${node.dependencyNodeIds.length}` : '';
|
|
225
359
|
return buildPanelLine(width, [
|
|
226
|
-
[
|
|
227
|
-
[node.status.padEnd(10), statusColor(node.status)],
|
|
228
|
-
[` ${node.role.padEnd(10)}`, C.value],
|
|
229
|
-
[` ${node.id.slice(0, 8)} `, C.dim],
|
|
230
|
-
[`${node.title}${depends}
|
|
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],
|
|
231
365
|
]);
|
|
232
366
|
});
|
|
233
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
|
+
|
|
234
375
|
const scrollableLines: Line[] = graphs.map((graph, index) =>
|
|
235
376
|
this.renderItem(graph, index, index === this.selectedIndex, width),
|
|
236
377
|
);
|
|
237
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
|
+
|
|
238
391
|
const postureSection: PanelWorkspaceSection = { title: 'Orchestration posture', lines: postureLines };
|
|
239
392
|
const selectedGraphSection: PanelWorkspaceSection = { title: 'Selected Graph', lines: detailLines };
|
|
240
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
|
+
}
|
|
241
398
|
const graphsSection = resolveScrollablePanelSection(width, height, {
|
|
242
399
|
intro,
|
|
400
|
+
footerLines,
|
|
243
401
|
palette: C,
|
|
244
402
|
beforeSections: [postureSection],
|
|
245
403
|
section: {
|
|
@@ -250,24 +408,58 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
250
408
|
minRows: 4,
|
|
251
409
|
appendWindowSummary: { dimColor: C.dim },
|
|
252
410
|
},
|
|
253
|
-
afterSections
|
|
411
|
+
afterSections,
|
|
254
412
|
});
|
|
255
413
|
this.scrollStart = graphsSection.scrollOffset;
|
|
256
414
|
|
|
257
415
|
const sections: PanelWorkspaceSection[] = [
|
|
258
416
|
postureSection,
|
|
259
417
|
graphsSection.section,
|
|
260
|
-
|
|
261
|
-
nodesSection,
|
|
418
|
+
...afterSections,
|
|
262
419
|
];
|
|
263
420
|
this.needsRender = false;
|
|
264
421
|
const lines = buildPanelWorkspace(width, height, {
|
|
265
422
|
title: 'Orchestration Control Room',
|
|
266
423
|
intro,
|
|
267
424
|
sections,
|
|
425
|
+
footerLines,
|
|
268
426
|
palette: C,
|
|
269
427
|
});
|
|
270
428
|
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
271
429
|
return lines.slice(0, height);
|
|
272
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
|
+
}
|
|
273
465
|
}
|