@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
|
@@ -11,11 +11,10 @@ import { createEmptyLine } from '../types/grid.ts';
|
|
|
11
11
|
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
12
12
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
13
13
|
import type { UiOrchestrationSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
|
|
14
|
-
import type { OrchestrationGraphRecord } from '@/runtime/index.ts';
|
|
14
|
+
import type { OrchestrationGraphRecord, OrchestrationNodeRecord } from '@/runtime/index.ts';
|
|
15
15
|
import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
16
16
|
import {
|
|
17
17
|
buildEmptyState,
|
|
18
|
-
buildGuidanceLine,
|
|
19
18
|
buildKeyboardHints,
|
|
20
19
|
buildKeyValueLine,
|
|
21
20
|
buildPanelLine,
|
|
@@ -27,34 +26,69 @@ import {
|
|
|
27
26
|
type PanelWorkspaceSection,
|
|
28
27
|
} from './polish.ts';
|
|
29
28
|
|
|
29
|
+
// Domain accents only; base chrome (header/headerBg/info/good/warn/bad/
|
|
30
|
+
// selectBg) comes from DEFAULT_PANEL_PALETTE.
|
|
30
31
|
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
31
|
-
|
|
32
|
-
headerBg: '#1e293b',
|
|
33
|
-
running: '#22c55e',
|
|
34
|
-
ready: '#38bdf8',
|
|
35
|
-
blocked: '#f59e0b',
|
|
36
|
-
failed: '#ef4444',
|
|
37
|
-
completed: '#a78bfa',
|
|
38
|
-
selectBg: '#0f172a',
|
|
32
|
+
completed: '#a78bfa', // completed-graph badge, distinct from running/good
|
|
39
33
|
} as const);
|
|
40
34
|
|
|
41
35
|
function statusColor(status: string): string {
|
|
42
36
|
switch (status) {
|
|
43
|
-
case 'ready': return C.
|
|
44
|
-
case 'running': return C.
|
|
45
|
-
case 'blocked': return C.
|
|
46
|
-
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;
|
|
47
41
|
case 'completed': return C.completed;
|
|
48
42
|
default: return C.dim;
|
|
49
43
|
}
|
|
50
44
|
}
|
|
51
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
|
+
|
|
52
71
|
export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRecord> {
|
|
53
72
|
private readonly readModel?: UiReadModel<UiOrchestrationSnapshot>;
|
|
54
73
|
private readonly unsub: (() => void) | null;
|
|
55
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
|
+
|
|
56
90
|
public constructor(readModel?: UiReadModel<UiOrchestrationSnapshot>) {
|
|
57
|
-
super('orchestration', 'Orchestration', '
|
|
91
|
+
super('orchestration', 'Orchestration', '◒', 'runtime-ops');
|
|
58
92
|
this.readModel = readModel;
|
|
59
93
|
this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
|
|
60
94
|
}
|
|
@@ -72,6 +106,42 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
72
106
|
return [...this.readModel.getSnapshot().graphs].sort((a, b) => b.createdAt - a.createdAt);
|
|
73
107
|
}
|
|
74
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
|
+
|
|
75
145
|
protected renderItem(
|
|
76
146
|
graph: OrchestrationGraphRecord,
|
|
77
147
|
index: number,
|
|
@@ -102,9 +172,79 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
102
172
|
}
|
|
103
173
|
|
|
104
174
|
// ---------------------------------------------------------------------------
|
|
105
|
-
// 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.
|
|
106
178
|
// ---------------------------------------------------------------------------
|
|
107
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
|
+
|
|
108
248
|
// ---------------------------------------------------------------------------
|
|
109
249
|
// Render — multi-section layout (posture + scrollable graphs + detail + nodes)
|
|
110
250
|
// ---------------------------------------------------------------------------
|
|
@@ -122,7 +262,7 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
122
262
|
width,
|
|
123
263
|
' Runtime store not wired into this panel yet.',
|
|
124
264
|
'The orchestration workspace needs the live runtime store before it can show graphs, nodes, and recursion guard events.',
|
|
125
|
-
[
|
|
265
|
+
[],
|
|
126
266
|
C,
|
|
127
267
|
),
|
|
128
268
|
}],
|
|
@@ -132,17 +272,19 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
132
272
|
return workspace;
|
|
133
273
|
}
|
|
134
274
|
|
|
275
|
+
this._captureGuardTrips();
|
|
276
|
+
|
|
135
277
|
const snapshot = this.readModel.getSnapshot();
|
|
136
278
|
const graphs = this.getItems();
|
|
279
|
+
this._syncSelectionFromId(graphs);
|
|
137
280
|
const postureLines = [
|
|
138
281
|
buildKeyValueLine(width, [
|
|
139
282
|
{ label: 'graphs', value: String(snapshot.totalGraphs), valueColor: snapshot.totalGraphs > 0 ? C.value : C.dim },
|
|
140
|
-
{ 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 },
|
|
141
284
|
{ label: 'completed', value: String(snapshot.totalCompletedGraphs), valueColor: snapshot.totalCompletedGraphs > 0 ? C.completed : C.dim },
|
|
142
|
-
{ label: 'failed', value: String(snapshot.totalFailedGraphs), valueColor: snapshot.totalFailedGraphs > 0 ? C.
|
|
143
|
-
{ 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 },
|
|
144
287
|
], C),
|
|
145
|
-
buildGuidanceLine(width, '/orchestration', 'inspect recursive execution posture, graph health, and node contract flow', C),
|
|
146
288
|
];
|
|
147
289
|
if (graphs.length === 0) {
|
|
148
290
|
this.needsRender = false;
|
|
@@ -172,7 +314,7 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
172
314
|
}
|
|
173
315
|
|
|
174
316
|
this.clampSelection();
|
|
175
|
-
const selected =
|
|
317
|
+
const selected = this.getSelectedItem()!;
|
|
176
318
|
const isActive = snapshot.activeGraphIds.includes(selected.id);
|
|
177
319
|
const elapsed = selected.startedAt
|
|
178
320
|
? formatElapsed(Math.max(0, (selected.endedAt ?? Date.now()) - selected.startedAt))
|
|
@@ -188,7 +330,7 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
188
330
|
[' Mode: ', C.label],
|
|
189
331
|
[selected.mode, C.value],
|
|
190
332
|
[' Live: ', C.label],
|
|
191
|
-
[isActive ? 'yes' : 'no', isActive ? C.
|
|
333
|
+
[isActive ? 'yes' : 'no', isActive ? C.good : C.dim],
|
|
192
334
|
]),
|
|
193
335
|
buildPanelLine(width, [
|
|
194
336
|
[' Nodes: ', C.label],
|
|
@@ -201,71 +343,58 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
201
343
|
[elapsed, C.dim],
|
|
202
344
|
]),
|
|
203
345
|
];
|
|
204
|
-
if (selected.lastRecursionGuard) {
|
|
205
|
-
detailLines.push(buildPanelLine(width, [
|
|
206
|
-
[' Recursion guard: ', C.label],
|
|
207
|
-
[`depth ${selected.lastRecursionGuard.depth} active ${selected.lastRecursionGuard.activeAgents} ${selected.lastRecursionGuard.reason}`, C.blocked],
|
|
208
|
-
]));
|
|
209
|
-
}
|
|
210
346
|
|
|
211
|
-
const nodes = selected
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (focusNode?.contract) {
|
|
216
|
-
const toolCount = focusNode.contract.allowedTools?.length ?? 0;
|
|
217
|
-
const evidenceCount = focusNode.contract.requiredEvidence?.length ?? 0;
|
|
218
|
-
const scopeCount = focusNode.contract.writeScope?.length ?? 0;
|
|
219
|
-
detailLines.push(buildPanelLine(width, [
|
|
220
|
-
[' Contract: ', C.label],
|
|
221
|
-
[`tools ${toolCount}`, C.value],
|
|
222
|
-
[' evidence ', C.label],
|
|
223
|
-
[String(evidenceCount), C.value],
|
|
224
|
-
[' write scope ', C.label],
|
|
225
|
-
[String(scopeCount), C.value],
|
|
226
|
-
]));
|
|
227
|
-
detailLines.push(buildPanelLine(width, [
|
|
228
|
-
[' Flow: ', C.label],
|
|
229
|
-
[focusNode.contract.executionProtocol ?? 'direct', C.value],
|
|
230
|
-
[' Review: ', C.label],
|
|
231
|
-
[focusNode.contract.reviewMode ?? 'none', C.value],
|
|
232
|
-
[' Lane: ', C.label],
|
|
233
|
-
[focusNode.contract.communicationLane ?? 'default', C.value],
|
|
234
|
-
[' Inherits: ', C.label],
|
|
235
|
-
[focusNode.contract.inheritsParentConstraints ? 'yes' : 'no', C.value],
|
|
236
|
-
]));
|
|
237
|
-
}
|
|
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));
|
|
238
351
|
|
|
239
352
|
const nodeLines: Line[] = nodes.length === 0
|
|
240
353
|
? [buildPanelLine(width, [[' No nodes recorded yet.', C.dim]])]
|
|
241
|
-
: 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 ? '▸ ' : ' ';
|
|
242
358
|
const depends = node.dependencyNodeIds.length > 0 ? ` deps:${node.dependencyNodeIds.length}` : '';
|
|
243
359
|
return buildPanelLine(width, [
|
|
244
|
-
[
|
|
245
|
-
[node.status.padEnd(10), statusColor(node.status)],
|
|
246
|
-
[` ${node.role.padEnd(10)}`, C.value],
|
|
247
|
-
[` ${node.id.slice(0, 8)} `, C.dim],
|
|
248
|
-
[truncateDisplay(`${node.title}${depends}`, Math.max(0, 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],
|
|
249
365
|
]);
|
|
250
366
|
});
|
|
251
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
|
+
|
|
252
375
|
const scrollableLines: Line[] = graphs.map((graph, index) =>
|
|
253
376
|
this.renderItem(graph, index, index === this.selectedIndex, width),
|
|
254
377
|
);
|
|
255
378
|
|
|
256
|
-
// Context-aware footer: surface position + only the keys that work
|
|
379
|
+
// Context-aware footer: surface position + only the keys that work in the
|
|
380
|
+
// currently-focused list (graph list vs node list).
|
|
257
381
|
const footerLines: Line[] = [
|
|
258
382
|
buildKeyboardHints(width, [
|
|
259
383
|
{ keys: `${this.selectedIndex + 1}/${graphs.length}`, label: 'graph' },
|
|
260
|
-
{ keys: '
|
|
261
|
-
|
|
262
|
-
|
|
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' }]),
|
|
263
388
|
], C),
|
|
264
389
|
];
|
|
265
390
|
|
|
266
391
|
const postureSection: PanelWorkspaceSection = { title: 'Orchestration posture', lines: postureLines };
|
|
267
392
|
const selectedGraphSection: PanelWorkspaceSection = { title: 'Selected Graph', lines: detailLines };
|
|
268
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
|
+
}
|
|
269
398
|
const graphsSection = resolveScrollablePanelSection(width, height, {
|
|
270
399
|
intro,
|
|
271
400
|
footerLines,
|
|
@@ -279,15 +408,14 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
279
408
|
minRows: 4,
|
|
280
409
|
appendWindowSummary: { dimColor: C.dim },
|
|
281
410
|
},
|
|
282
|
-
afterSections
|
|
411
|
+
afterSections,
|
|
283
412
|
});
|
|
284
413
|
this.scrollStart = graphsSection.scrollOffset;
|
|
285
414
|
|
|
286
415
|
const sections: PanelWorkspaceSection[] = [
|
|
287
416
|
postureSection,
|
|
288
417
|
graphsSection.section,
|
|
289
|
-
|
|
290
|
-
nodesSection,
|
|
418
|
+
...afterSections,
|
|
291
419
|
];
|
|
292
420
|
this.needsRender = false;
|
|
293
421
|
const lines = buildPanelWorkspace(width, height, {
|
|
@@ -300,4 +428,38 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
|
|
|
300
428
|
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
301
429
|
return lines.slice(0, height);
|
|
302
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
|
+
}
|
|
303
465
|
}
|