@pellux/goodvibes-tui 0.18.23 → 0.19.1
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 +71 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +7 -3
- package/src/core/conversation-rendering.ts +8 -6
- package/src/core/orchestrator.ts +1 -1
- package/src/daemon/cli.ts +54 -0
- package/src/input/commands/diff-runtime.ts +6 -5
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +2 -2
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/session-content.ts +1 -1
- package/src/input/commands/shell-core.ts +3 -2
- package/src/input/commands/skills-runtime.ts +2 -2
- package/src/input/commands/subscription-runtime.ts +4 -4
- package/src/input/handler.ts +8 -10
- package/src/input/model-picker.ts +6 -2
- package/src/input/panel-integration-actions.ts +2 -1
- package/src/input/settings-modal-types.ts +60 -0
- package/src/input/settings-modal.ts +83 -65
- package/src/main.ts +52 -0
- package/src/panels/agent-inspector-panel.ts +10 -9
- package/src/panels/agent-logs-panel.ts +26 -6
- package/src/panels/approval-panel.ts +1 -0
- package/src/panels/automation-control-panel.ts +1 -0
- package/src/panels/base-panel.ts +108 -3
- package/src/panels/communication-panel.ts +1 -0
- package/src/panels/context-visualizer-panel.ts +2 -0
- package/src/panels/control-plane-panel.ts +1 -0
- package/src/panels/diff-panel.ts +2 -0
- package/src/panels/file-explorer-panel.ts +51 -31
- package/src/panels/file-preview-panel.ts +57 -35
- package/src/panels/git-panel.ts +12 -13
- package/src/panels/hooks-panel.ts +3 -1
- package/src/panels/incident-review-panel.ts +4 -2
- package/src/panels/knowledge-panel.ts +75 -107
- package/src/panels/local-auth-panel.ts +1 -0
- package/src/panels/marketplace-panel.ts +51 -69
- package/src/panels/mcp-panel.ts +3 -1
- package/src/panels/memory-panel.ts +90 -158
- package/src/panels/ops-control-panel.ts +1 -0
- package/src/panels/orchestration-panel.ts +70 -51
- package/src/panels/panel-list-panel.ts +5 -4
- package/src/panels/panel-manager.ts +3 -0
- package/src/panels/plan-dashboard-panel.ts +2 -0
- package/src/panels/plugins-panel.ts +1 -0
- package/src/panels/polish.ts +51 -2
- package/src/panels/provider-accounts-panel.ts +1 -0
- package/src/panels/provider-health-panel.ts +6 -8
- package/src/panels/routes-panel.ts +3 -1
- package/src/panels/schedule-panel.ts +7 -6
- package/src/panels/scrollable-list-panel.ts +19 -2
- package/src/panels/security-panel.ts +17 -15
- package/src/panels/services-panel.ts +6 -4
- package/src/panels/session-browser-panel.ts +19 -18
- package/src/panels/settings-sync-panel.ts +3 -1
- package/src/panels/skills-panel.ts +114 -230
- package/src/panels/subscription-panel.ts +1 -0
- package/src/panels/system-messages-panel.ts +147 -141
- package/src/panels/tasks-panel.ts +1 -0
- package/src/panels/token-budget-panel.ts +2 -0
- package/src/panels/watchers-panel.ts +1 -0
- package/src/panels/worktree-panel.ts +1 -0
- package/src/panels/wrfc-panel.ts +2 -0
- package/src/renderer/agent-detail-modal.ts +2 -2
- package/src/renderer/ansi-sanitize.ts +76 -0
- package/src/renderer/buffer.ts +12 -1
- package/src/renderer/help-overlay.ts +14 -3
- package/src/renderer/model-picker-overlay.ts +9 -2
- package/src/renderer/settings-modal-helpers.ts +27 -0
- package/src/renderer/settings-modal.ts +18 -1
- package/src/renderer/status-glyphs.ts +21 -0
- package/src/renderer/status-token.ts +4 -8
- package/src/renderer/tool-call.ts +4 -3
- package/src/runtime/bootstrap-core.ts +1 -1
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap.ts +7 -8
- package/src/runtime/diagnostics/panels/policy.ts +2 -1
- package/src/shell/ui-openers.ts +44 -3
- package/src/version.ts +1 -1
|
@@ -1,33 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MemoryPanel — project memory substrate TUI panel.
|
|
3
|
+
*
|
|
4
|
+
* Migrated to SearchableListPanel<MemoryRecord> (Wave B1).
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
import type { Line } from '../types/grid.ts';
|
|
6
8
|
import type { MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state/memory-store';
|
|
7
9
|
import type { MemoryRecord, MemoryClass } from '@pellux/goodvibes-sdk/platform/state/memory-store';
|
|
8
|
-
import {
|
|
10
|
+
import { SearchableListPanel } from './scrollable-list-panel.ts';
|
|
9
11
|
import {
|
|
10
12
|
buildBodyText,
|
|
11
|
-
buildEmptyState,
|
|
12
13
|
buildGuidanceLine,
|
|
13
14
|
buildKeyValueLine,
|
|
14
15
|
buildPanelLine,
|
|
15
|
-
|
|
16
|
-
buildPanelWorkspace,
|
|
17
|
-
resolveScrollablePanelSection,
|
|
16
|
+
extendPalette,
|
|
18
17
|
DEFAULT_PANEL_PALETTE,
|
|
19
|
-
type PanelWorkspaceSection,
|
|
20
18
|
} from './polish.ts';
|
|
21
19
|
import {
|
|
22
20
|
getPanelSearchFocusTransition,
|
|
23
|
-
isPanelSearchBackspace,
|
|
24
21
|
isPanelSearchCancel,
|
|
25
|
-
isPanelSearchCommit,
|
|
26
|
-
isPanelSearchPrintable,
|
|
27
22
|
} from './search-focus.ts';
|
|
28
23
|
|
|
29
|
-
const C = {
|
|
30
|
-
...DEFAULT_PANEL_PALETTE,
|
|
24
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
31
25
|
header: '#94a3b8',
|
|
32
26
|
headerBg: '#1e293b',
|
|
33
27
|
decision: '#38bdf8',
|
|
@@ -42,7 +36,7 @@ const C = {
|
|
|
42
36
|
selected: '#1e3a5f',
|
|
43
37
|
searchBg: '#0f172a',
|
|
44
38
|
searchFg: '#e2e8f0',
|
|
45
|
-
}
|
|
39
|
+
});
|
|
46
40
|
|
|
47
41
|
function fmtTime(ts: number): string {
|
|
48
42
|
const d = new Date(ts);
|
|
@@ -63,13 +57,9 @@ function classColor(cls: MemoryClass): string {
|
|
|
63
57
|
}
|
|
64
58
|
}
|
|
65
59
|
|
|
66
|
-
export class MemoryPanel extends
|
|
60
|
+
export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
|
|
67
61
|
private registry: MemoryRegistry;
|
|
68
|
-
private
|
|
69
|
-
private selectedIdx = 0;
|
|
70
|
-
private scrollOffset = 0;
|
|
71
|
-
private searchMode = false;
|
|
72
|
-
private searchQuery = '';
|
|
62
|
+
private filterFocused = false;
|
|
73
63
|
private unsubscribe?: () => void;
|
|
74
64
|
|
|
75
65
|
constructor(registry: MemoryRegistry) {
|
|
@@ -79,9 +69,11 @@ export class MemoryPanel extends BasePanel {
|
|
|
79
69
|
|
|
80
70
|
onActivate(): void {
|
|
81
71
|
super.onActivate();
|
|
82
|
-
this.
|
|
72
|
+
this.searchQuery = '';
|
|
73
|
+
this.invalidateFilter();
|
|
74
|
+
this.filterFocused = false;
|
|
83
75
|
this.unsubscribe = this.registry.subscribe(() => {
|
|
84
|
-
this.
|
|
76
|
+
this.invalidateFilter();
|
|
85
77
|
this.markDirty();
|
|
86
78
|
});
|
|
87
79
|
}
|
|
@@ -95,134 +87,112 @@ export class MemoryPanel extends BasePanel {
|
|
|
95
87
|
this.unsubscribe = undefined;
|
|
96
88
|
}
|
|
97
89
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// SearchableListPanel implementation
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
protected getAllItems(): readonly MemoryRecord[] {
|
|
95
|
+
return this.registry.search({ limit: 100 });
|
|
104
96
|
}
|
|
105
97
|
|
|
106
|
-
|
|
107
|
-
|
|
98
|
+
protected matchesSearch(record: MemoryRecord, query: string): boolean {
|
|
99
|
+
const q = query.trim().toLowerCase();
|
|
100
|
+
if (!q) return true;
|
|
101
|
+
const haystack = [
|
|
102
|
+
record.summary,
|
|
103
|
+
record.detail ?? '',
|
|
104
|
+
record.cls,
|
|
105
|
+
record.scope,
|
|
106
|
+
record.tags.join(' '),
|
|
107
|
+
].join(' ').toLowerCase();
|
|
108
|
+
return haystack.includes(q);
|
|
109
|
+
}
|
|
108
110
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
protected renderItem(record: MemoryRecord, index: number, selected: boolean, width: number): Line {
|
|
112
|
+
const bg = selected ? C.selected : undefined;
|
|
113
|
+
return buildPanelLine(width, [
|
|
114
|
+
[' ', C.label, bg],
|
|
115
|
+
[`[${record.scope.slice(0, 1).toUpperCase()}/${record.cls.slice(0, 3).toUpperCase()}] `, classColor(record.cls), bg],
|
|
116
|
+
[record.id.slice(-8), C.dim, bg],
|
|
117
|
+
[' ', C.label, bg],
|
|
118
|
+
[fmtTime(record.createdAt), C.dim, bg],
|
|
119
|
+
[' ', C.label, bg],
|
|
120
|
+
[record.summary.slice(0, Math.max(0, width - 33)), C.value, bg],
|
|
121
|
+
]);
|
|
122
|
+
}
|
|
115
123
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
return true;
|
|
138
|
-
case 'r':
|
|
139
|
-
this.reload();
|
|
124
|
+
protected override getPalette() { return C; }
|
|
125
|
+
protected override getEmptyStateMessage() {
|
|
126
|
+
return this.searchQuery
|
|
127
|
+
? ` No records matching "${this.searchQuery}"`
|
|
128
|
+
: ' No memory records. Use /recall add <class> <summary> to create one.';
|
|
129
|
+
}
|
|
130
|
+
protected override getEmptyStateActions() {
|
|
131
|
+
return [
|
|
132
|
+
{ command: '/recall add fact <summary>', summary: 'capture a durable fact directly' },
|
|
133
|
+
{ command: '/recall capture incident latest', summary: 'promote the latest incident into memory' },
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
handleInput(key: string): boolean {
|
|
138
|
+
// Filter-focus mode: typing goes into the search query
|
|
139
|
+
if (this.filterFocused) {
|
|
140
|
+
const items = this.getItems();
|
|
141
|
+
const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.selectedIndex, itemCount: items.length });
|
|
142
|
+
if (transition === 'focus-list') {
|
|
143
|
+
this.filterFocused = false;
|
|
140
144
|
this.markDirty();
|
|
141
145
|
return true;
|
|
146
|
+
}
|
|
147
|
+
if (isPanelSearchCancel(key)) {
|
|
148
|
+
this.filterFocused = false;
|
|
149
|
+
return super.handleInput(key);
|
|
150
|
+
}
|
|
151
|
+
return super.handleInput(key);
|
|
142
152
|
}
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
153
|
|
|
146
|
-
|
|
147
|
-
const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.
|
|
148
|
-
if (transition === 'focus-
|
|
149
|
-
this.
|
|
150
|
-
this.selectedIdx = 0;
|
|
151
|
-
this.markDirty();
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
if (isPanelSearchCommit(key) || isPanelSearchCancel(key)) {
|
|
155
|
-
this.searchMode = false;
|
|
156
|
-
this.reload();
|
|
157
|
-
this.markDirty();
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
if (isPanelSearchBackspace(key)) {
|
|
161
|
-
this.searchQuery = this.searchQuery.slice(0, -1);
|
|
154
|
+
const items = this.getItems();
|
|
155
|
+
const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.selectedIndex, itemCount: items.length });
|
|
156
|
+
if (transition === 'focus-search') {
|
|
157
|
+
this.filterFocused = true;
|
|
162
158
|
this.markDirty();
|
|
163
159
|
return true;
|
|
164
160
|
}
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
|
|
162
|
+
if (key === 'r') {
|
|
163
|
+
this.invalidateFilter();
|
|
167
164
|
this.markDirty();
|
|
168
165
|
return true;
|
|
169
166
|
}
|
|
170
|
-
|
|
167
|
+
|
|
168
|
+
return super.handleInput(key);
|
|
171
169
|
}
|
|
172
170
|
|
|
173
171
|
render(width: number, height: number): Line[] {
|
|
172
|
+
this.clampSelection();
|
|
174
173
|
const intro = 'Durable project memory across decisions, constraints, incidents, patterns, risks, runbooks, and related provenance.';
|
|
175
174
|
|
|
176
|
-
|
|
177
|
-
this.reload();
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (!this.records.length) {
|
|
181
|
-
const message = this.searchQuery
|
|
182
|
-
? `No records matching "${this.searchQuery}"`
|
|
183
|
-
: 'No memory records. Use /recall add <class> <summary> to create one.';
|
|
184
|
-
return buildPanelWorkspace(width, height, {
|
|
185
|
-
title: 'Memory',
|
|
186
|
-
intro,
|
|
187
|
-
sections: [{
|
|
188
|
-
lines: buildEmptyState(
|
|
189
|
-
width,
|
|
190
|
-
` ${message}`,
|
|
191
|
-
'Memory becomes useful once durable facts, incidents, and decisions are promoted into the project substrate.',
|
|
192
|
-
[
|
|
193
|
-
{ command: '/recall add fact <summary>', summary: 'capture a durable fact directly' },
|
|
194
|
-
{ command: '/recall capture incident latest', summary: 'promote the latest incident into memory' },
|
|
195
|
-
],
|
|
196
|
-
C,
|
|
197
|
-
),
|
|
198
|
-
}],
|
|
199
|
-
footerLines: [
|
|
200
|
-
buildPanelLine(width, [[' / search j/k or Up/Down move r reload', C.dim]]),
|
|
201
|
-
],
|
|
202
|
-
palette: C,
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
175
|
+
const records = this.getItems();
|
|
206
176
|
const byClass = new Map<MemoryClass, number>();
|
|
207
|
-
for (const record of
|
|
177
|
+
for (const record of records) {
|
|
208
178
|
byClass.set(record.cls, (byClass.get(record.cls) ?? 0) + 1);
|
|
209
179
|
}
|
|
210
180
|
|
|
211
|
-
const
|
|
181
|
+
const filterLine = this.buildFilterInputLine(width, 'Filter', this.filterFocused);
|
|
182
|
+
|
|
183
|
+
const summaryLines: Line[] = [
|
|
212
184
|
buildKeyValueLine(width, [
|
|
213
|
-
{ label: 'records', value: String(
|
|
185
|
+
{ label: 'records', value: String(records.length), valueColor: C.value },
|
|
214
186
|
{ label: 'facts', value: String(byClass.get('fact') ?? 0), valueColor: C.fact },
|
|
215
187
|
{ label: 'decisions', value: String(byClass.get('decision') ?? 0), valueColor: C.decision },
|
|
216
188
|
{ label: 'incidents', value: String(byClass.get('incident') ?? 0), valueColor: C.incident },
|
|
217
189
|
{ label: 'runbooks', value: String(byClass.get('runbook') ?? 0), valueColor: C.runbook },
|
|
218
190
|
], C),
|
|
219
|
-
|
|
220
|
-
? [buildSearchInputLine(width, '', `${this.searchMode ? '/ ' : '~ '}${this.searchQuery}${this.searchMode ? '_' : ''}`, C, { active: this.searchMode, bg: C.searchBg, valueColor: C.searchFg })]
|
|
221
|
-
: []),
|
|
191
|
+
filterLine,
|
|
222
192
|
buildGuidanceLine(width, '/recall review', 'review durable knowledge and queue posture from the command surface', C),
|
|
223
193
|
];
|
|
224
194
|
|
|
225
|
-
const selected =
|
|
195
|
+
const selected = records[this.selectedIndex];
|
|
226
196
|
const selectedLines: Line[] = [];
|
|
227
197
|
if (selected) {
|
|
228
198
|
selectedLines.push(buildKeyValueLine(width, [
|
|
@@ -243,51 +213,13 @@ export class MemoryPanel extends BasePanel {
|
|
|
243
213
|
}
|
|
244
214
|
}
|
|
245
215
|
|
|
246
|
-
|
|
247
|
-
const selectedSection: PanelWorkspaceSection = selectedLines.length > 0 ? { title: 'Selected', lines: selectedLines } : { title: 'Selected', lines: [] };
|
|
248
|
-
const recordsSection = resolveScrollablePanelSection(width, height, {
|
|
249
|
-
intro,
|
|
250
|
-
footerLines: [
|
|
251
|
-
buildPanelLine(width, [[' / search j/k or Up/Down move r reload Esc clear search', C.dim]]),
|
|
252
|
-
],
|
|
253
|
-
palette: C,
|
|
254
|
-
beforeSections: [summarySection],
|
|
255
|
-
section: {
|
|
256
|
-
title: 'Records',
|
|
257
|
-
scrollableLines: this.records.map((record, globalIndex) => {
|
|
258
|
-
const bg = globalIndex === this.selectedIdx ? C.selected : undefined;
|
|
259
|
-
return buildPanelLine(width, [
|
|
260
|
-
[' ', C.label, bg],
|
|
261
|
-
[`[${record.scope.slice(0, 1).toUpperCase()}/${record.cls.slice(0, 3).toUpperCase()}] `, classColor(record.cls), bg],
|
|
262
|
-
[record.id.slice(-8), C.dim, bg],
|
|
263
|
-
[' ', C.label, bg],
|
|
264
|
-
[fmtTime(record.createdAt), C.dim, bg],
|
|
265
|
-
[' ', C.label, bg],
|
|
266
|
-
[record.summary.slice(0, Math.max(0, width - 33)), C.value, bg],
|
|
267
|
-
]);
|
|
268
|
-
}),
|
|
269
|
-
selectedIndex: this.selectedIdx,
|
|
270
|
-
scrollOffset: this.scrollOffset,
|
|
271
|
-
minRows: 4,
|
|
272
|
-
appendWindowSummary: { dimColor: C.dim },
|
|
273
|
-
},
|
|
274
|
-
afterSections: selectedLines.length > 0 ? [selectedSection] : [],
|
|
275
|
-
});
|
|
276
|
-
this.scrollOffset = recordsSection.scrollOffset;
|
|
277
|
-
const sections: PanelWorkspaceSection[] = [
|
|
278
|
-
summarySection,
|
|
279
|
-
recordsSection.section,
|
|
280
|
-
];
|
|
281
|
-
if (selectedLines.length > 0) sections.push(selectedSection);
|
|
282
|
-
|
|
283
|
-
return buildPanelWorkspace(width, height, {
|
|
216
|
+
return this.renderList(width, height, {
|
|
284
217
|
title: 'Memory',
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
218
|
+
header: summaryLines,
|
|
219
|
+
footer: [
|
|
220
|
+
...selectedLines,
|
|
288
221
|
buildPanelLine(width, [[' / search j/k or Up/Down move r reload Esc clear search', C.dim]]),
|
|
289
222
|
],
|
|
290
|
-
palette: C,
|
|
291
223
|
});
|
|
292
224
|
}
|
|
293
225
|
}
|
|
@@ -76,6 +76,7 @@ export class OpsControlPanel extends ScrollableListPanel<OpsAuditEntry> {
|
|
|
76
76
|
|
|
77
77
|
public constructor(eventFeed: UiEventFeed<OpsEvent>) {
|
|
78
78
|
super('ops-control', 'Ops Control', 'Q', 'agent');
|
|
79
|
+
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
79
80
|
this._opsPanel = new OpsPanel(eventFeed);
|
|
80
81
|
this._unsub = this._opsPanel.subscribe(() => this.markDirty());
|
|
81
82
|
}
|
|
@@ -1,7 +1,16 @@
|
|
|
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
|
+
|
|
1
9
|
import type { Line } from '../types/grid.ts';
|
|
2
10
|
import { createEmptyLine } from '../types/grid.ts';
|
|
3
|
-
import {
|
|
11
|
+
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
4
12
|
import type { UiOrchestrationSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
|
|
13
|
+
import type { OrchestrationGraphRecord } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/orchestration';
|
|
5
14
|
import {
|
|
6
15
|
buildEmptyState,
|
|
7
16
|
buildGuidanceLine,
|
|
@@ -10,11 +19,12 @@ import {
|
|
|
10
19
|
buildPanelWorkspace,
|
|
11
20
|
resolveScrollablePanelSection,
|
|
12
21
|
DEFAULT_PANEL_PALETTE,
|
|
22
|
+
extendPalette,
|
|
23
|
+
type PanelPalette,
|
|
13
24
|
type PanelWorkspaceSection,
|
|
14
25
|
} from './polish.ts';
|
|
15
26
|
|
|
16
|
-
const C = {
|
|
17
|
-
...DEFAULT_PANEL_PALETTE,
|
|
27
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
18
28
|
header: '#94a3b8',
|
|
19
29
|
headerBg: '#1e293b',
|
|
20
30
|
running: '#22c55e',
|
|
@@ -23,30 +33,22 @@ const C = {
|
|
|
23
33
|
failed: '#ef4444',
|
|
24
34
|
completed: '#a78bfa',
|
|
25
35
|
selectBg: '#0f172a',
|
|
26
|
-
} as const;
|
|
36
|
+
} as const);
|
|
27
37
|
|
|
28
38
|
function statusColor(status: string): string {
|
|
29
39
|
switch (status) {
|
|
30
|
-
case 'ready':
|
|
31
|
-
|
|
32
|
-
case '
|
|
33
|
-
|
|
34
|
-
case '
|
|
35
|
-
|
|
36
|
-
case 'failed':
|
|
37
|
-
return C.failed;
|
|
38
|
-
case 'completed':
|
|
39
|
-
return C.completed;
|
|
40
|
-
default:
|
|
41
|
-
return C.dim;
|
|
40
|
+
case 'ready': return C.ready;
|
|
41
|
+
case 'running': return C.running;
|
|
42
|
+
case 'blocked': return C.blocked;
|
|
43
|
+
case 'failed': return C.failed;
|
|
44
|
+
case 'completed': return C.completed;
|
|
45
|
+
default: return C.dim;
|
|
42
46
|
}
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
export class OrchestrationPanel extends
|
|
49
|
+
export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRecord> {
|
|
46
50
|
private readonly readModel?: UiReadModel<UiOrchestrationSnapshot>;
|
|
47
51
|
private readonly unsub: (() => void) | null;
|
|
48
|
-
private selectedIndex = 0;
|
|
49
|
-
private scrollOffset = 0;
|
|
50
52
|
|
|
51
53
|
public constructor(readModel?: UiReadModel<UiOrchestrationSnapshot>) {
|
|
52
54
|
super('orchestration', 'Orchestration', 'Q', 'monitoring');
|
|
@@ -58,32 +60,52 @@ export class OrchestrationPanel extends BasePanel {
|
|
|
58
60
|
this.unsub?.();
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (key === 'up' || key === 'k') {
|
|
65
|
-
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
66
|
-
this.markDirty();
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
if (key === 'down' || key === 'j') {
|
|
70
|
-
this.selectedIndex = Math.min(graphs.length - 1, this.selectedIndex + 1);
|
|
71
|
-
this.markDirty();
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// ScrollableListPanel contract
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
76
66
|
|
|
77
|
-
|
|
67
|
+
protected getItems(): readonly OrchestrationGraphRecord[] {
|
|
78
68
|
if (!this.readModel) return [];
|
|
79
69
|
return [...this.readModel.getSnapshot().graphs].sort((a, b) => b.createdAt - a.createdAt);
|
|
80
70
|
}
|
|
81
71
|
|
|
72
|
+
protected renderItem(
|
|
73
|
+
graph: OrchestrationGraphRecord,
|
|
74
|
+
index: number,
|
|
75
|
+
selected: boolean,
|
|
76
|
+
width: number,
|
|
77
|
+
): Line {
|
|
78
|
+
const bg = selected ? C.selectBg : undefined;
|
|
79
|
+
return buildPanelLine(width, [
|
|
80
|
+
[' ', C.label, bg],
|
|
81
|
+
[graph.status.padEnd(10), statusColor(graph.status), bg],
|
|
82
|
+
[` ${graph.mode.padEnd(17)}`, C.value, bg],
|
|
83
|
+
[` ${graph.id.slice(0, 8)} `, C.dim, bg],
|
|
84
|
+
[graph.title.slice(0, Math.max(0, width - 39)), C.value, bg],
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected override getPalette(): PanelPalette {
|
|
89
|
+
return C;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected override getEmptyStateMessage(): string {
|
|
93
|
+
return ' No orchestration graphs recorded yet.';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Input — base class handles all navigation (up/down/j/k/pageup/pagedown/g/G)
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Render — multi-section layout (posture + scrollable graphs + detail + nodes)
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
82
104
|
public render(width: number, height: number): Line[] {
|
|
83
|
-
this.needsRender = false;
|
|
84
105
|
const intro = 'Task graphs, node contracts, recursion guards, and WRFC-visible orchestration state.';
|
|
85
106
|
|
|
86
107
|
if (!this.readModel) {
|
|
108
|
+
this.needsRender = false;
|
|
87
109
|
const workspace = buildPanelWorkspace(width, height, {
|
|
88
110
|
title: 'Orchestration Control Room',
|
|
89
111
|
intro,
|
|
@@ -103,7 +125,7 @@ export class OrchestrationPanel extends BasePanel {
|
|
|
103
125
|
}
|
|
104
126
|
|
|
105
127
|
const snapshot = this.readModel.getSnapshot();
|
|
106
|
-
const graphs = this.
|
|
128
|
+
const graphs = this.getItems();
|
|
107
129
|
const postureLines = [
|
|
108
130
|
buildKeyValueLine(width, [
|
|
109
131
|
{ label: 'graphs', value: String(snapshot.totalGraphs), valueColor: snapshot.totalGraphs > 0 ? C.value : C.dim },
|
|
@@ -115,6 +137,7 @@ export class OrchestrationPanel extends BasePanel {
|
|
|
115
137
|
buildGuidanceLine(width, '/orchestration', 'inspect recursive execution posture, graph health, and node contract flow', C),
|
|
116
138
|
];
|
|
117
139
|
if (graphs.length === 0) {
|
|
140
|
+
this.needsRender = false;
|
|
118
141
|
const workspace = buildPanelWorkspace(width, height, {
|
|
119
142
|
title: 'Orchestration Control Room',
|
|
120
143
|
intro,
|
|
@@ -124,7 +147,7 @@ export class OrchestrationPanel extends BasePanel {
|
|
|
124
147
|
...postureLines,
|
|
125
148
|
...buildEmptyState(
|
|
126
149
|
width,
|
|
127
|
-
|
|
150
|
+
this.getEmptyStateMessage(),
|
|
128
151
|
'Graphs, nodes, child contracts, and recursion guard trips will appear here as orchestration starts.',
|
|
129
152
|
[
|
|
130
153
|
{ command: '/tasks', summary: 'create or inspect task flows that feed orchestration graphs' },
|
|
@@ -140,7 +163,7 @@ export class OrchestrationPanel extends BasePanel {
|
|
|
140
163
|
return workspace;
|
|
141
164
|
}
|
|
142
165
|
|
|
143
|
-
this.
|
|
166
|
+
this.clampSelection();
|
|
144
167
|
const selected = graphs[this.selectedIndex]!;
|
|
145
168
|
const detailLines: Line[] = [
|
|
146
169
|
buildPanelLine(width, [
|
|
@@ -208,6 +231,10 @@ export class OrchestrationPanel extends BasePanel {
|
|
|
208
231
|
]);
|
|
209
232
|
});
|
|
210
233
|
|
|
234
|
+
const scrollableLines: Line[] = graphs.map((graph, index) =>
|
|
235
|
+
this.renderItem(graph, index, index === this.selectedIndex, width),
|
|
236
|
+
);
|
|
237
|
+
|
|
211
238
|
const postureSection: PanelWorkspaceSection = { title: 'Orchestration posture', lines: postureLines };
|
|
212
239
|
const selectedGraphSection: PanelWorkspaceSection = { title: 'Selected Graph', lines: detailLines };
|
|
213
240
|
const nodesSection: PanelWorkspaceSection = { title: 'Nodes', lines: nodeLines };
|
|
@@ -217,24 +244,15 @@ export class OrchestrationPanel extends BasePanel {
|
|
|
217
244
|
beforeSections: [postureSection],
|
|
218
245
|
section: {
|
|
219
246
|
title: 'Graphs',
|
|
220
|
-
scrollableLines
|
|
221
|
-
const bg = absolute === this.selectedIndex ? C.selectBg : undefined;
|
|
222
|
-
return buildPanelLine(width, [
|
|
223
|
-
[' ', C.label, bg],
|
|
224
|
-
[graph.status.padEnd(10), statusColor(graph.status), bg],
|
|
225
|
-
[` ${graph.mode.padEnd(17)}`, C.value, bg],
|
|
226
|
-
[` ${graph.id.slice(0, 8)} `, C.dim, bg],
|
|
227
|
-
[graph.title.slice(0, Math.max(0, width - 39)), C.value, bg],
|
|
228
|
-
]);
|
|
229
|
-
}),
|
|
247
|
+
scrollableLines,
|
|
230
248
|
selectedIndex: this.selectedIndex,
|
|
231
|
-
scrollOffset: this.
|
|
249
|
+
scrollOffset: this.scrollStart,
|
|
232
250
|
minRows: 4,
|
|
233
251
|
appendWindowSummary: { dimColor: C.dim },
|
|
234
252
|
},
|
|
235
253
|
afterSections: [selectedGraphSection, nodesSection],
|
|
236
254
|
});
|
|
237
|
-
this.
|
|
255
|
+
this.scrollStart = graphsSection.scrollOffset;
|
|
238
256
|
|
|
239
257
|
const sections: PanelWorkspaceSection[] = [
|
|
240
258
|
postureSection,
|
|
@@ -242,6 +260,7 @@ export class OrchestrationPanel extends BasePanel {
|
|
|
242
260
|
selectedGraphSection,
|
|
243
261
|
nodesSection,
|
|
244
262
|
];
|
|
263
|
+
this.needsRender = false;
|
|
245
264
|
const lines = buildPanelWorkspace(width, height, {
|
|
246
265
|
title: 'Orchestration Control Room',
|
|
247
266
|
intro,
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
isPanelSearchCommit,
|
|
40
40
|
isPanelSearchPrintable,
|
|
41
41
|
} from './search-focus.ts';
|
|
42
|
+
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
|
|
42
43
|
|
|
43
44
|
// ── Colour palette ────────────────────────────────────────────────────────────
|
|
44
45
|
const C = {
|
|
@@ -163,7 +164,7 @@ export class PanelListPanel extends BasePanel {
|
|
|
163
164
|
try {
|
|
164
165
|
this.panelManager.open(selectedPanel.reg.id);
|
|
165
166
|
} catch (err) {
|
|
166
|
-
|
|
167
|
+
logger.warn(`[panel-list] failed to open panel: ${err}`);
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
170
|
this.markDirty();
|
|
@@ -205,7 +206,7 @@ export class PanelListPanel extends BasePanel {
|
|
|
205
206
|
try {
|
|
206
207
|
this.panelManager.open(selectedPanel.reg.id);
|
|
207
208
|
} catch (err) {
|
|
208
|
-
|
|
209
|
+
logger.warn(`[panel-list] failed to open panel: ${err}`);
|
|
209
210
|
}
|
|
210
211
|
this.markDirty();
|
|
211
212
|
}
|
|
@@ -221,7 +222,7 @@ export class PanelListPanel extends BasePanel {
|
|
|
221
222
|
pm.open(selectedPanel.reg.id, pane);
|
|
222
223
|
pm.show();
|
|
223
224
|
} catch (err) {
|
|
224
|
-
|
|
225
|
+
logger.warn(`[panel-list] failed to place panel: ${err}`);
|
|
225
226
|
}
|
|
226
227
|
this.markDirty();
|
|
227
228
|
}
|
|
@@ -234,7 +235,7 @@ export class PanelListPanel extends BasePanel {
|
|
|
234
235
|
try {
|
|
235
236
|
this.panelManager.moveToOtherPane(selectedPanel.reg.id);
|
|
236
237
|
} catch (err) {
|
|
237
|
-
|
|
238
|
+
logger.warn(`[panel-list] failed to move panel: ${err}`);
|
|
238
239
|
}
|
|
239
240
|
this.markDirty();
|
|
240
241
|
}
|
|
@@ -216,6 +216,7 @@ export class PanelManager {
|
|
|
216
216
|
p.activeIndex = (p.activeIndex - 1 + p.panels.length) % p.panels.length;
|
|
217
217
|
const newPanel = p.panels[p.activeIndex];
|
|
218
218
|
if (newPanel) newPanel.onActivate();
|
|
219
|
+
this._invalidateWorkspaceTabs();
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
activateByIndex(index: number): void {
|
|
@@ -259,6 +260,7 @@ export class PanelManager {
|
|
|
259
260
|
togglePaneFocus(): void {
|
|
260
261
|
if (!this._bottomPaneVisible || this.bottomPane.panels.length === 0) return;
|
|
261
262
|
this._focusedPane = this._focusedPane === 'top' ? 'bottom' : 'top';
|
|
263
|
+
this._invalidateWorkspaceTabs();
|
|
262
264
|
}
|
|
263
265
|
|
|
264
266
|
// -------------------------------------------------------------------------
|
|
@@ -457,6 +459,7 @@ export class PanelManager {
|
|
|
457
459
|
this._focusedPane = 'top';
|
|
458
460
|
this._bottomPaneVisible = false;
|
|
459
461
|
this._visible = false;
|
|
462
|
+
this._invalidateWorkspaceTabs();
|
|
460
463
|
}
|
|
461
464
|
|
|
462
465
|
// -------------------------------------------------------------------------
|
|
@@ -80,6 +80,7 @@ export class PlanDashboardPanel extends BasePanel {
|
|
|
80
80
|
// --------------------------------------------------------------------------
|
|
81
81
|
|
|
82
82
|
render(width: number, height: number): Line[] {
|
|
83
|
+
return this.trackedRender(() => {
|
|
83
84
|
const plan = this.planManager.getActive();
|
|
84
85
|
if (!plan) {
|
|
85
86
|
return buildPanelWorkspace(width, height, {
|
|
@@ -100,6 +101,7 @@ export class PlanDashboardPanel extends BasePanel {
|
|
|
100
101
|
});
|
|
101
102
|
}
|
|
102
103
|
return this.renderPlan(plan, width, height);
|
|
104
|
+
});
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
// --------------------------------------------------------------------------
|
|
@@ -52,6 +52,7 @@ export class PluginsPanel extends ScrollableListPanel<PluginStatus> {
|
|
|
52
52
|
|
|
53
53
|
public constructor(manager: PluginManagerObserver) {
|
|
54
54
|
super('plugins', 'Plugins', 'P', 'monitoring');
|
|
55
|
+
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
55
56
|
this.manager = manager;
|
|
56
57
|
this.unsub = manager.subscribe(() => this.markDirty());
|
|
57
58
|
}
|