@pellux/goodvibes-tui 0.18.20 → 0.19.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 +154 -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 +22 -6
- package/src/core/orchestrator.ts +1 -1
- 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/session.ts +0 -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/feed-context-factory.ts +236 -0
- package/src/input/handler-feed.ts +44 -6
- package/src/input/handler-shortcuts.ts +138 -125
- package/src/input/handler.ts +119 -119
- package/src/input/keybindings.ts +30 -0
- 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/panels/agent-inspector-panel.ts +10 -9
- package/src/panels/agent-logs-panel.ts +26 -6
- package/src/panels/approval-panel.ts +55 -82
- package/src/panels/automation-control-panel.ts +120 -161
- package/src/panels/base-panel.ts +108 -3
- package/src/panels/communication-panel.ts +69 -107
- package/src/panels/context-visualizer-panel.ts +2 -0
- package/src/panels/control-plane-panel.ts +117 -172
- 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 +103 -138
- package/src/panels/incident-review-panel.ts +59 -109
- package/src/panels/knowledge-panel.ts +75 -107
- package/src/panels/local-auth-panel.ts +77 -93
- package/src/panels/marketplace-panel.ts +51 -69
- package/src/panels/mcp-panel.ts +110 -155
- package/src/panels/memory-panel.ts +90 -158
- package/src/panels/ops-control-panel.ts +51 -85
- package/src/panels/orchestration-panel.ts +70 -51
- package/src/panels/panel-list-panel.ts +5 -4
- package/src/panels/panel-manager.ts +25 -2
- package/src/panels/plan-dashboard-panel.ts +2 -0
- package/src/panels/plugins-panel.ts +37 -60
- 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 +91 -141
- package/src/panels/schedule-panel.ts +7 -6
- package/src/panels/scrollable-list-panel.ts +64 -16
- package/src/panels/security-panel.ts +118 -152
- package/src/panels/services-panel.ts +63 -105
- package/src/panels/session-browser-panel.ts +19 -18
- package/src/panels/settings-sync-panel.ts +79 -123
- package/src/panels/skills-panel.ts +114 -230
- package/src/panels/subscription-panel.ts +64 -86
- package/src/panels/system-messages-panel.ts +147 -141
- package/src/panels/tasks-panel.ts +130 -179
- package/src/panels/token-budget-panel.ts +2 -0
- package/src/panels/watchers-panel.ts +89 -137
- 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 +23 -1
- package/src/renderer/diff.ts +8 -0
- package/src/renderer/help-overlay.ts +48 -28
- package/src/renderer/markdown.ts +3 -145
- 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 +1 -1
- package/src/version.ts +1 -1
package/src/panels/base-panel.ts
CHANGED
|
@@ -11,14 +11,54 @@ export abstract class BasePanel implements Panel {
|
|
|
11
11
|
public isPinned = false;
|
|
12
12
|
protected readonly componentHealthMonitor?: ComponentHealthMonitor;
|
|
13
13
|
|
|
14
|
+
// -------------------------------------------------------------------------
|
|
15
|
+
// Timer registry
|
|
16
|
+
// -------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/** All timers registered via registerTimer(). Cleared automatically on onDestroy(). */
|
|
19
|
+
private readonly _timers: Set<ReturnType<typeof setTimeout> | ReturnType<typeof setInterval>> = new Set();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Register a timer id (from setInterval or setTimeout) so it is
|
|
23
|
+
* automatically cleared when the panel is destroyed. Returns the id
|
|
24
|
+
* unchanged so the call can be chained inline:
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* this.registerTimer(setInterval(() => this.refresh(), 5_000));
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
protected registerTimer<T extends ReturnType<typeof setTimeout> | ReturnType<typeof setInterval>>(id: T): T {
|
|
31
|
+
this._timers.add(id);
|
|
32
|
+
return id;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Clear a specific timer and remove it from the registry.
|
|
37
|
+
* Safe to call with an id that was never registered or already cleared.
|
|
38
|
+
*/
|
|
39
|
+
protected clearTimer(id: ReturnType<typeof setTimeout> | ReturnType<typeof setInterval>): void {
|
|
40
|
+
clearInterval(id as ReturnType<typeof setInterval>);
|
|
41
|
+
this._timers.delete(id);
|
|
42
|
+
}
|
|
43
|
+
|
|
14
44
|
// -------------------------------------------------------------------------
|
|
15
45
|
// I2: Error surface slot
|
|
16
46
|
// -------------------------------------------------------------------------
|
|
17
47
|
|
|
18
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Last error message to surface in the panel footer.
|
|
50
|
+
* Auto-cleared on the next keystroke by `ScrollableListPanel.handleInput()` (and any
|
|
51
|
+
* subclass that calls `super.handleInput()` or manually calls `this.clearError()` at
|
|
52
|
+
* the start of its handler). BasePanel itself does NOT auto-clear — only subclasses
|
|
53
|
+
* that opt into the contract do.
|
|
54
|
+
*/
|
|
19
55
|
protected lastError: string | null = null;
|
|
20
56
|
|
|
21
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Set a transient error message. Triggers a re-render.
|
|
59
|
+
* The error will be auto-cleared on the next keystroke if the panel extends
|
|
60
|
+
* `ScrollableListPanel` (which calls `clearError()` at the top of `handleInput()`).
|
|
61
|
+
*/
|
|
22
62
|
protected setError(msg: string): void {
|
|
23
63
|
this.lastError = msg;
|
|
24
64
|
this.needsRender = true;
|
|
@@ -66,6 +106,32 @@ export abstract class BasePanel implements Panel {
|
|
|
66
106
|
this.needsRender = true;
|
|
67
107
|
}
|
|
68
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Run an async operation with the panel's loading spinner visible.
|
|
111
|
+
* The spinner is always cleared on completion, whether the operation succeeds or throws
|
|
112
|
+
* (uses try/finally). Rethrows any error so callers can handle it or forward to setError.
|
|
113
|
+
*
|
|
114
|
+
* @param label Optional label shown next to the spinner.
|
|
115
|
+
* @param fn The async work to run.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* try {
|
|
120
|
+
* await this.withLoading('Loading diff…', () => this.fetchDiff());
|
|
121
|
+
* } catch (err) {
|
|
122
|
+
* this.setError(summarizeError(err));
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
protected async withLoading<T>(label: string | undefined, fn: () => Promise<T>): Promise<T> {
|
|
127
|
+
this.startLoading(label);
|
|
128
|
+
try {
|
|
129
|
+
return await fn();
|
|
130
|
+
} finally {
|
|
131
|
+
this.stopLoading();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
69
135
|
/**
|
|
70
136
|
* Build a spinner Line for the loading state.
|
|
71
137
|
* Returns null when loadingState is not 'loading'.
|
|
@@ -107,7 +173,17 @@ export abstract class BasePanel implements Panel {
|
|
|
107
173
|
|
|
108
174
|
onActivate(): void { this.needsRender = true; }
|
|
109
175
|
onDeactivate(): void {}
|
|
110
|
-
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Called when the panel is permanently removed. Subclasses should call
|
|
179
|
+
* `super.onDestroy()` to ensure all registered timers are cleared.
|
|
180
|
+
*/
|
|
181
|
+
onDestroy(): void {
|
|
182
|
+
for (const id of this._timers) {
|
|
183
|
+
clearInterval(id as ReturnType<typeof setInterval>);
|
|
184
|
+
}
|
|
185
|
+
this._timers.clear();
|
|
186
|
+
}
|
|
111
187
|
|
|
112
188
|
abstract render(width: number, height: number): Line[];
|
|
113
189
|
|
|
@@ -146,4 +222,33 @@ export abstract class BasePanel implements Panel {
|
|
|
146
222
|
protected reportRenderDuration(durationMs: number, now: number = Date.now()): void {
|
|
147
223
|
this.componentHealthMonitor?.recordRender(this.id, durationMs, now);
|
|
148
224
|
}
|
|
225
|
+
|
|
226
|
+
/** Cache of the most recent lines produced by trackedRender. */
|
|
227
|
+
private _lastTrackedLines: Line[] = [];
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Wrap a render body with canRenderNow throttle check, wall-clock timing,
|
|
231
|
+
* and automatic reportRenderDuration.
|
|
232
|
+
*
|
|
233
|
+
* When throttled, returns the previously cached lines (stale but correctly
|
|
234
|
+
* sized) rather than empty lines, avoiding a flicker on every skipped frame.
|
|
235
|
+
*
|
|
236
|
+
* Usage:
|
|
237
|
+
* ```ts
|
|
238
|
+
* render(width: number, height: number): Line[] {
|
|
239
|
+
* return this.trackedRender(() => {
|
|
240
|
+
* // expensive render logic
|
|
241
|
+
* return lines;
|
|
242
|
+
* });
|
|
243
|
+
* }
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
protected trackedRender(fn: () => Line[]): Line[] {
|
|
247
|
+
if (!this.canRenderNow()) return this._lastTrackedLines;
|
|
248
|
+
const start = Date.now();
|
|
249
|
+
const lines = fn();
|
|
250
|
+
this.reportRenderDuration(Date.now() - start);
|
|
251
|
+
this._lastTrackedLines = lines;
|
|
252
|
+
return lines;
|
|
253
|
+
}
|
|
149
254
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import {
|
|
3
|
-
import { BasePanel } from './base-panel.ts';
|
|
2
|
+
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
4
3
|
import type { UiCommunicationSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
|
|
5
4
|
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
6
5
|
import {
|
|
@@ -11,10 +10,9 @@ import {
|
|
|
11
10
|
buildPanelLine,
|
|
12
11
|
buildPanelWorkspace,
|
|
13
12
|
DEFAULT_PANEL_PALETTE,
|
|
14
|
-
|
|
15
|
-
type PanelWorkspaceSection,
|
|
13
|
+
type PanelPalette,
|
|
16
14
|
} from './polish.ts';
|
|
17
|
-
import {
|
|
15
|
+
import { createEmptyLine } from '../types/grid.ts';
|
|
18
16
|
|
|
19
17
|
const C = {
|
|
20
18
|
...DEFAULT_PANEL_PALETTE,
|
|
@@ -26,14 +24,15 @@ const C = {
|
|
|
26
24
|
selectBg: '#0f172a',
|
|
27
25
|
} as const;
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
type CommunicationRecord = UiCommunicationSnapshot['records'][number];
|
|
28
|
+
|
|
29
|
+
export class CommunicationPanel extends ScrollableListPanel<CommunicationRecord> {
|
|
30
30
|
private readonly readModel?: UiReadModel<UiCommunicationSnapshot>;
|
|
31
31
|
private readonly unsub: (() => void) | null;
|
|
32
|
-
private selectedIndex = 0;
|
|
33
|
-
private scrollOffset = 0;
|
|
34
32
|
|
|
35
33
|
public constructor(readModel?: UiReadModel<UiCommunicationSnapshot>) {
|
|
36
34
|
super('communication', 'Communication', 'Y', 'monitoring');
|
|
35
|
+
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
37
36
|
this.readModel = readModel;
|
|
38
37
|
this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
|
|
39
38
|
}
|
|
@@ -42,31 +41,40 @@ export class CommunicationPanel extends BasePanel {
|
|
|
42
41
|
this.unsub?.();
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (records.length === 0) return false;
|
|
48
|
-
if (key === 'up' || key === 'k') {
|
|
49
|
-
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
50
|
-
this.markDirty();
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
if (key === 'down' || key === 'j') {
|
|
54
|
-
this.selectedIndex = Math.min(records.length - 1, this.selectedIndex + 1);
|
|
55
|
-
this.markDirty();
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
return false;
|
|
44
|
+
protected override getPalette(): PanelPalette {
|
|
45
|
+
return C;
|
|
59
46
|
}
|
|
60
47
|
|
|
61
|
-
|
|
48
|
+
protected getItems(): readonly CommunicationRecord[] {
|
|
62
49
|
if (!this.readModel) return [];
|
|
63
|
-
return
|
|
50
|
+
return this.readModel.getSnapshot().records;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected renderItem(record: CommunicationRecord, index: number, selected: boolean, width: number): Line {
|
|
54
|
+
const bg = selected ? C.selectBg : undefined;
|
|
55
|
+
const color = record.status === 'blocked' ? C.error : record.status === 'delivered' ? C.ok : C.info;
|
|
56
|
+
return buildPanelLine(width, [
|
|
57
|
+
[' ', C.label, bg],
|
|
58
|
+
[record.status.padEnd(10), color, bg],
|
|
59
|
+
[` ${record.kind.padEnd(10)}`, C.info, bg],
|
|
60
|
+
[` ${truncateDisplay(`${record.fromId} -> ${record.toId}`, 28).padEnd(28)}`, C.value, bg],
|
|
61
|
+
[` ${truncateDisplay(record.content, Math.max(0, width - 53))}`, C.dim, bg],
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
protected override getEmptyStateMessage(): string {
|
|
66
|
+
return ' No structured communication recorded yet.';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
|
|
70
|
+
return [
|
|
71
|
+
{ command: '/orchestration', summary: 'review graphs and recursive agent activity' },
|
|
72
|
+
{ command: '/communication', summary: 'reopen this workspace once the runtime emits message traffic' },
|
|
73
|
+
];
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
public render(width: number, height: number): Line[] {
|
|
67
|
-
this.needsRender = false;
|
|
68
77
|
const intro = 'Structured agent communication, routing policy outcomes, and delivery status across orchestration trees.';
|
|
69
|
-
const footerLines = [buildPanelLine(width, [[' Up/Down move through messages', C.dim]])];
|
|
70
78
|
|
|
71
79
|
if (!this.readModel) {
|
|
72
80
|
const workspace = buildPanelWorkspace(width, height, {
|
|
@@ -88,104 +96,58 @@ export class CommunicationPanel extends BasePanel {
|
|
|
88
96
|
}
|
|
89
97
|
|
|
90
98
|
const snapshot = this.readModel.getSnapshot();
|
|
91
|
-
const records = this.
|
|
99
|
+
const records = this.getItems();
|
|
100
|
+
|
|
101
|
+
const postureLines: Line[] = [
|
|
102
|
+
buildPanelLine(width, [[' Communication posture', C.label]]),
|
|
103
|
+
buildKeyValueLine(width, [
|
|
104
|
+
{ label: 'sent', value: String(snapshot.totalSent), valueColor: snapshot.totalSent > 0 ? C.info : C.dim },
|
|
105
|
+
{ label: 'delivered', value: String(snapshot.totalDelivered), valueColor: snapshot.totalDelivered > 0 ? C.ok : C.dim },
|
|
106
|
+
{ label: 'blocked', value: String(snapshot.totalBlocked), valueColor: snapshot.totalBlocked > 0 ? C.error : C.dim },
|
|
107
|
+
], C),
|
|
108
|
+
buildGuidanceLine(width, '/orchestration', 'inspect recursive routing, message handoff, and blocked broadcast posture', C),
|
|
109
|
+
];
|
|
92
110
|
|
|
93
111
|
if (records.length === 0) {
|
|
94
|
-
|
|
112
|
+
return this.renderList(width, height, {
|
|
95
113
|
title: 'Communication Control Room',
|
|
96
|
-
|
|
97
|
-
sections: [{
|
|
98
|
-
title: 'Communication posture',
|
|
99
|
-
lines: [
|
|
100
|
-
buildKeyValueLine(width, [
|
|
101
|
-
{ label: 'sent', value: String(snapshot.totalSent), valueColor: snapshot.totalSent > 0 ? C.info : C.dim },
|
|
102
|
-
{ label: 'delivered', value: String(snapshot.totalDelivered), valueColor: snapshot.totalDelivered > 0 ? C.ok : C.dim },
|
|
103
|
-
{ label: 'blocked', value: String(snapshot.totalBlocked), valueColor: snapshot.totalBlocked > 0 ? C.error : C.dim },
|
|
104
|
-
], C),
|
|
105
|
-
buildGuidanceLine(width, '/communication', 'review structured message flow, delivery posture, and blocked routing decisions', C),
|
|
106
|
-
...buildEmptyState(
|
|
107
|
-
width,
|
|
108
|
-
' No structured communication recorded yet.',
|
|
109
|
-
'Messages, escalations, findings, and handoffs will appear here once orchestration starts routing them through the communication policy.',
|
|
110
|
-
[
|
|
111
|
-
{ command: '/orchestration', summary: 'review graphs and recursive agent activity' },
|
|
112
|
-
{ command: '/communication', summary: 'reopen this workspace once the runtime emits message traffic' },
|
|
113
|
-
],
|
|
114
|
-
C,
|
|
115
|
-
),
|
|
116
|
-
],
|
|
117
|
-
}],
|
|
118
|
-
palette: C,
|
|
114
|
+
header: postureLines,
|
|
119
115
|
});
|
|
120
|
-
while (workspace.length < height) workspace.push(createEmptyLine(width));
|
|
121
|
-
return workspace;
|
|
122
116
|
}
|
|
123
117
|
|
|
124
|
-
this.
|
|
125
|
-
const
|
|
118
|
+
this.clampSelection();
|
|
119
|
+
const selected = records[this.selectedIndex];
|
|
120
|
+
|
|
121
|
+
// Update posture with selected info
|
|
122
|
+
const postureWithSelected: Line[] = [
|
|
123
|
+
buildPanelLine(width, [[' Communication posture', C.label]]),
|
|
126
124
|
buildKeyValueLine(width, [
|
|
127
125
|
{ label: 'sent', value: String(snapshot.totalSent), valueColor: snapshot.totalSent > 0 ? C.info : C.dim },
|
|
128
126
|
{ label: 'delivered', value: String(snapshot.totalDelivered), valueColor: snapshot.totalDelivered > 0 ? C.ok : C.dim },
|
|
129
127
|
{ label: 'blocked', value: String(snapshot.totalBlocked), valueColor: snapshot.totalBlocked > 0 ? C.error : C.dim },
|
|
130
|
-
{ label: 'selected', value: `${
|
|
128
|
+
{ label: 'selected', value: `${selected?.fromId ?? 'n/a'} -> ${selected?.toId ?? 'n/a'}`, valueColor: C.value },
|
|
131
129
|
], C),
|
|
132
130
|
buildGuidanceLine(width, '/orchestration', 'inspect recursive routing, message handoff, and blocked broadcast posture', C),
|
|
133
131
|
];
|
|
134
132
|
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
133
|
+
const footerLines: Line[] = [];
|
|
134
|
+
if (selected) {
|
|
135
|
+
footerLines.push(
|
|
136
|
+
buildPanelLine(width, [[' Route: ', C.label], [`${selected.scope} / ${selected.kind}`, C.value], [' Status: ', C.label], [selected.status, selected.status === 'blocked' ? C.error : C.ok]]),
|
|
137
|
+
buildPanelLine(width, [[' From: ', C.label], [selected.fromId, C.value], [' To: ', C.label], [selected.toId, C.value]]),
|
|
138
|
+
buildPanelLine(width, [[' Roles: ', C.label], [`${selected.fromRole ?? 'unknown'} -> ${selected.toRole ?? 'unknown'}`, C.dim]]),
|
|
139
|
+
);
|
|
140
|
+
if (selected.reason) {
|
|
141
|
+
footerLines.push(buildPanelLine(width, [[' Reason: ', C.label], [truncateDisplay(selected.reason, Math.max(0, width - 11)), C.warn]]));
|
|
142
|
+
}
|
|
143
|
+
footerLines.push(...buildBodyText(width, ` Content: ${selected.content}`, C));
|
|
143
144
|
}
|
|
144
|
-
|
|
145
|
-
const postureSection: PanelWorkspaceSection = { title: 'Communication posture', lines: postureLines };
|
|
146
|
-
const detailSection: PanelWorkspaceSection = { title: 'Selected Message', lines: detailLines };
|
|
147
|
-
const rawOverviewLines: Line[] = records.map((record, absolute) => {
|
|
148
|
-
const bg = absolute === this.selectedIndex ? C.selectBg : undefined;
|
|
149
|
-
const color = record.status === 'blocked' ? C.error : record.status === 'delivered' ? C.ok : C.info;
|
|
150
|
-
return buildPanelLine(width, [
|
|
151
|
-
[' ', C.label, bg],
|
|
152
|
-
[record.status.padEnd(10), color, bg],
|
|
153
|
-
[` ${record.kind.padEnd(10)}`, C.info, bg],
|
|
154
|
-
[` ${truncateDisplay(`${record.fromId} -> ${record.toId}`, 28).padEnd(28)}`, C.value, bg],
|
|
155
|
-
[` ${truncateDisplay(record.content, Math.max(0, width - 53))}`, C.dim, bg],
|
|
156
|
-
]);
|
|
157
|
-
});
|
|
158
|
-
const resolvedMessagesSection = resolvePrimaryScrollableSection(width, height, {
|
|
159
|
-
intro,
|
|
160
|
-
footerLines,
|
|
161
|
-
palette: C,
|
|
162
|
-
beforeSections: [postureSection],
|
|
163
|
-
section: {
|
|
164
|
-
title: 'Recent Messages',
|
|
165
|
-
scrollableLines: rawOverviewLines,
|
|
166
|
-
selectedIndex: this.selectedIndex,
|
|
167
|
-
scrollOffset: this.scrollOffset,
|
|
168
|
-
guardRows: 1,
|
|
169
|
-
minRows: 4,
|
|
170
|
-
appendWindowSummary: { dimColor: C.dim },
|
|
171
|
-
},
|
|
172
|
-
afterSections: [detailSection],
|
|
173
|
-
});
|
|
174
|
-
this.scrollOffset = resolvedMessagesSection.scrollOffset;
|
|
145
|
+
footerLines.push(buildPanelLine(width, [[' Up/Down move through messages', C.dim]]));
|
|
175
146
|
|
|
176
|
-
|
|
177
|
-
postureSection,
|
|
178
|
-
resolvedMessagesSection.section,
|
|
179
|
-
detailSection,
|
|
180
|
-
];
|
|
181
|
-
const lines = buildPanelWorkspace(width, height, {
|
|
147
|
+
return this.renderList(width, height, {
|
|
182
148
|
title: 'Communication Control Room',
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
footerLines,
|
|
186
|
-
palette: C,
|
|
149
|
+
header: postureWithSelected,
|
|
150
|
+
footer: footerLines,
|
|
187
151
|
});
|
|
188
|
-
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
189
|
-
return lines.slice(0, height);
|
|
190
152
|
}
|
|
191
153
|
}
|
|
@@ -72,6 +72,7 @@ export class ContextVisualizerPanel extends BasePanel {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
render(width: number, height: number): Line[] {
|
|
75
|
+
return this.trackedRender(() => {
|
|
75
76
|
if (height <= 0 || width <= 0) return [];
|
|
76
77
|
|
|
77
78
|
const input = this.snapshot.input;
|
|
@@ -131,6 +132,7 @@ export class ContextVisualizerPanel extends BasePanel {
|
|
|
131
132
|
],
|
|
132
133
|
palette: DEFAULT_PANEL_PALETTE,
|
|
133
134
|
});
|
|
135
|
+
});
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
private _renderBar(width: number, barWidth: number, input: number, limit: number): Line {
|