@pellux/goodvibes-tui 0.28.0 → 1.0.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 +133 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/conversation-line-cache.ts +432 -0
- package/src/core/conversation-rendering.ts +11 -3
- package/src/core/conversation.ts +50 -4
- 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-content-actions.ts +8 -3
- 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/input-history.ts +17 -4
- package/src/input/keybindings.ts +48 -8
- package/src/input/panel-integration-actions.ts +107 -1
- package/src/main.ts +19 -11
- 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/base-panel.ts +1 -1
- 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 +209 -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 +310 -129
- 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/agent-detail-modal.ts +37 -8
- package/src/renderer/code-block.ts +58 -28
- package/src/renderer/context-inspector.ts +3 -6
- package/src/renderer/conversation-overlays.ts +25 -11
- package/src/renderer/conversation-surface.ts +1 -21
- package/src/renderer/diff-view.ts +6 -4
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +23 -17
- package/src/renderer/fullscreen-workspace.ts +2 -7
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/history-search-overlay.ts +10 -16
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/markdown.ts +9 -1
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +8 -485
- package/src/renderer/model-workspace.ts +9 -24
- package/src/renderer/overlay-box.ts +23 -19
- package/src/renderer/process-indicator.ts +15 -25
- package/src/renderer/profile-picker-modal.ts +13 -14
- package/src/renderer/prompt-content-width.ts +16 -0
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/selection-modal-overlay.ts +3 -1
- package/src/renderer/semantic-diff.ts +1 -1
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal-helpers.ts +10 -34
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +54 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/surface-layout.ts +0 -12
- package/src/renderer/term-caps.ts +1 -1
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/tool-call.ts +6 -20
- package/src/renderer/ui-factory.ts +48 -37
- package/src/renderer/ui-primitives.ts +41 -3
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/runtime/render-scheduler.ts +80 -0
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +2 -2
- package/src/utils/splash-lines.ts +54 -5
- 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/renderer/file-tree.ts +0 -153
- package/src/renderer/progress.ts +0 -99
- package/src/renderer/status-token.ts +0 -67
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
|
@@ -9,37 +9,30 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { Line } from '../types/grid.ts';
|
|
11
11
|
import { fitDisplay, truncateDisplay } from '../utils/terminal-width.ts';
|
|
12
|
-
import type { OpsEvent } from '@/runtime/index.ts';
|
|
12
|
+
import type { OpsApi, OpsEvent } from '@/runtime/index.ts';
|
|
13
13
|
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
14
14
|
import type { OpsAuditEntry } from '../runtime/diagnostics/panels/ops.ts';
|
|
15
15
|
import { OpsPanel } from '../runtime/diagnostics/panels/ops.ts';
|
|
16
16
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
17
|
+
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
18
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
17
19
|
import {
|
|
18
20
|
buildKeyValueLine,
|
|
19
21
|
buildKeyboardHints,
|
|
20
22
|
buildPanelLine,
|
|
21
23
|
DEFAULT_PANEL_PALETTE,
|
|
24
|
+
extendPalette,
|
|
22
25
|
type PanelPalette,
|
|
23
26
|
} from './polish.ts';
|
|
24
27
|
|
|
25
28
|
// ── Colour palette ──────────────────────────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
dim: '#4b5563',
|
|
34
|
-
label: '#64748b',
|
|
35
|
-
value: '#cbd5e1',
|
|
36
|
-
note: '#eab308',
|
|
37
|
-
seq: '#475569',
|
|
38
|
-
taskColor: '#22d3ee',
|
|
39
|
-
agentColor: '#a78bfa',
|
|
40
|
-
empty: '#334155',
|
|
41
|
-
selectBg: '#0f172a',
|
|
42
|
-
} as const;
|
|
29
|
+
// Domain accents only; base chrome (header/headerBg/dim/label/value/good/bad/
|
|
30
|
+
// warn/empty/selectBg) comes from DEFAULT_PANEL_PALETTE.
|
|
31
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
32
|
+
rejected: '#f97316', // rejected-outcome badge — distinct from success/error
|
|
33
|
+
taskColor: '#22d3ee', // task-target tag
|
|
34
|
+
agentColor: '#a78bfa', // agent-target tag
|
|
35
|
+
} as const);
|
|
43
36
|
|
|
44
37
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
45
38
|
|
|
@@ -53,9 +46,9 @@ function fmtTime(ts: number): string {
|
|
|
53
46
|
|
|
54
47
|
function outcomeColor(outcome: OpsAuditEntry['outcome']): string {
|
|
55
48
|
switch (outcome) {
|
|
56
|
-
case 'success': return C.
|
|
49
|
+
case 'success': return C.good;
|
|
57
50
|
case 'rejected': return C.rejected;
|
|
58
|
-
case 'error': return C.
|
|
51
|
+
case 'error': return C.bad;
|
|
59
52
|
}
|
|
60
53
|
}
|
|
61
54
|
|
|
@@ -71,18 +64,41 @@ function targetColor(kind: OpsAuditEntry['targetKind']): string {
|
|
|
71
64
|
return kind === 'task' ? C.taskColor : C.agentColor;
|
|
72
65
|
}
|
|
73
66
|
|
|
67
|
+
// ── Operator intervention actions ───────────────────────────────────────────
|
|
68
|
+
// Target-entry mode: c/p/u/y act on the currently *selected* audit row's
|
|
69
|
+
// target (task or agent) rather than requiring a typed id, mirroring the
|
|
70
|
+
// `/ops task <action> <id>` / `/ops agent cancel <id>` slash commands.
|
|
71
|
+
|
|
72
|
+
type OpsAction = 'cancel' | 'pause' | 'resume' | 'retry';
|
|
73
|
+
|
|
74
|
+
interface OpsActionSubject {
|
|
75
|
+
readonly action: OpsAction;
|
|
76
|
+
readonly targetKind: OpsAuditEntry['targetKind'];
|
|
77
|
+
readonly targetId: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const ACTION_VERB: Record<OpsAction, string> = {
|
|
81
|
+
cancel: 'Cancel',
|
|
82
|
+
pause: 'Pause',
|
|
83
|
+
resume: 'Resume',
|
|
84
|
+
retry: 'Retry',
|
|
85
|
+
};
|
|
86
|
+
|
|
74
87
|
// ── OpsControlPanel ──────────────────────────────────────────────────────────
|
|
75
88
|
|
|
76
89
|
export class OpsControlPanel extends ScrollableListPanel<OpsAuditEntry> {
|
|
77
90
|
private readonly _opsPanel: OpsPanel;
|
|
91
|
+
private readonly _opsApi: OpsApi | undefined;
|
|
78
92
|
private _unsub: (() => void) | null = null;
|
|
93
|
+
private _confirm: ConfirmState<OpsActionSubject> | null = null;
|
|
79
94
|
|
|
80
|
-
public constructor(eventFeed: UiEventFeed<OpsEvent
|
|
81
|
-
super('ops-control', 'Ops Control', '
|
|
95
|
+
public constructor(eventFeed: UiEventFeed<OpsEvent>, opsApi?: OpsApi) {
|
|
96
|
+
super('ops-control', 'Ops Control', '◓', 'runtime-ops');
|
|
82
97
|
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
83
98
|
this.filterEnabled = true;
|
|
84
99
|
this.filterLabel = 'Filter audit';
|
|
85
100
|
this._opsPanel = new OpsPanel(eventFeed);
|
|
101
|
+
this._opsApi = opsApi;
|
|
86
102
|
this._unsub = this._opsPanel.subscribe(() => this.markDirty());
|
|
87
103
|
}
|
|
88
104
|
|
|
@@ -115,6 +131,94 @@ export class OpsControlPanel extends ScrollableListPanel<OpsAuditEntry> {
|
|
|
115
131
|
|| entry.outcome.toLowerCase().includes(q);
|
|
116
132
|
}
|
|
117
133
|
|
|
134
|
+
// -------------------------------------------------------------------------
|
|
135
|
+
// Input — target-entry mode dispatch (acts on the selected audit row)
|
|
136
|
+
// -------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
public override handleInput(key: string): boolean {
|
|
139
|
+
if (this.lastError !== null) this.clearError();
|
|
140
|
+
|
|
141
|
+
if (this._confirm) {
|
|
142
|
+
const result = handleConfirmInput(this._confirm, key);
|
|
143
|
+
if (result === 'confirmed') {
|
|
144
|
+
this._executeConfirmed(this._confirm.subject);
|
|
145
|
+
this._confirm = null;
|
|
146
|
+
this.markDirty();
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
if (result === 'cancelled') {
|
|
150
|
+
this._confirm = null;
|
|
151
|
+
this.markDirty();
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!this.filterActive) {
|
|
157
|
+
switch (key) {
|
|
158
|
+
case 'c': this._requestAction('cancel'); return true;
|
|
159
|
+
case 'p': this._requestAction('pause'); return true;
|
|
160
|
+
case 'u': this._requestAction('resume'); return true;
|
|
161
|
+
case 'y': this._requestAction('retry'); return true;
|
|
162
|
+
default: break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return super.handleInput(key);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private _requestAction(action: OpsAction): void {
|
|
170
|
+
const selected = this.getSelectedItem();
|
|
171
|
+
if (!selected) return;
|
|
172
|
+
if (action !== 'cancel' && selected.targetKind === 'agent') {
|
|
173
|
+
this.setError(`Only cancel is supported for agent targets ("${action}" is task-only).`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
this._confirm = {
|
|
177
|
+
subject: { action, targetKind: selected.targetKind, targetId: selected.targetId },
|
|
178
|
+
label: `${selected.targetKind} ${selected.targetId}`,
|
|
179
|
+
verb: ACTION_VERB[action],
|
|
180
|
+
};
|
|
181
|
+
this.markDirty();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private _executeConfirmed(subject: OpsActionSubject): void {
|
|
185
|
+
if (!this._opsApi) {
|
|
186
|
+
this.setError('Ops API is not wired for this runtime.');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
if (subject.targetKind === 'agent') {
|
|
191
|
+
this._opsApi.agents.cancel(subject.targetId);
|
|
192
|
+
} else {
|
|
193
|
+
switch (subject.action) {
|
|
194
|
+
case 'cancel': this._opsApi.tasks.cancel(subject.targetId); break;
|
|
195
|
+
case 'pause': this._opsApi.tasks.pause(subject.targetId); break;
|
|
196
|
+
case 'resume': this._opsApi.tasks.resume(subject.targetId); break;
|
|
197
|
+
case 'retry': this._opsApi.tasks.retry(subject.targetId); break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
// Dispatched actions re-appear as OPS_AUDIT rows regardless of legality
|
|
202
|
+
// (OpsControlPlane audits rejections too); this surfaces the rare case
|
|
203
|
+
// where the target vanished before the audit event was emitted.
|
|
204
|
+
this.setError(summarizeError(err));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Live posture: tasks currently eligible for intervention, independent of the audit log. */
|
|
209
|
+
private _postureCounts(): { running: number; blocked: number; retryable: number } {
|
|
210
|
+
const tasks = this._opsApi?.tasks.snapshot().tasks ?? [];
|
|
211
|
+
let running = 0;
|
|
212
|
+
let blocked = 0;
|
|
213
|
+
let retryable = 0;
|
|
214
|
+
for (const task of tasks) {
|
|
215
|
+
if (task.status === 'running') running++;
|
|
216
|
+
else if (task.status === 'blocked') blocked++;
|
|
217
|
+
else if (task.status === 'failed' || task.status === 'cancelled') retryable++;
|
|
218
|
+
}
|
|
219
|
+
return { running, blocked, retryable };
|
|
220
|
+
}
|
|
221
|
+
|
|
118
222
|
protected renderItem(entry: OpsAuditEntry, _index: number, _selected: boolean, width: number): Line {
|
|
119
223
|
const seqStr = String(entry.seq).padStart(4, ' ');
|
|
120
224
|
const timeStr = fmtTime(entry.ts);
|
|
@@ -127,13 +231,13 @@ export class OpsControlPanel extends ScrollableListPanel<OpsAuditEntry> {
|
|
|
127
231
|
const noteRaw = truncateDisplay(entry.note ?? entry.errorMessage ?? '', Math.max(0, width - 63));
|
|
128
232
|
|
|
129
233
|
const segs: Array<[string, string, string?]> = [
|
|
130
|
-
[` ${seqStr} `, C.
|
|
234
|
+
[` ${seqStr} `, C.dim],
|
|
131
235
|
[`${timeStr} `, C.dim],
|
|
132
236
|
[`${action} `, C.value],
|
|
133
237
|
[`${target} `, targetColor(entry.targetKind)],
|
|
134
238
|
[outLabel, outcomeColor(entry.outcome)],
|
|
135
239
|
];
|
|
136
|
-
if (noteRaw) segs.push([` ${noteRaw}`, C.
|
|
240
|
+
if (noteRaw) segs.push([` ${noteRaw}`, C.warn]);
|
|
137
241
|
return buildPanelLine(width, segs);
|
|
138
242
|
}
|
|
139
243
|
|
|
@@ -142,7 +246,9 @@ export class OpsControlPanel extends ScrollableListPanel<OpsAuditEntry> {
|
|
|
142
246
|
}
|
|
143
247
|
|
|
144
248
|
protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
|
|
145
|
-
|
|
249
|
+
// No signpost — the live posture row in the header (running/blocked/retryable
|
|
250
|
+
// task counts) already tells the operator whether there is anything to act on.
|
|
251
|
+
return [];
|
|
146
252
|
}
|
|
147
253
|
|
|
148
254
|
public render(width: number, height: number): Line[] {
|
|
@@ -159,17 +265,24 @@ export class OpsControlPanel extends ScrollableListPanel<OpsAuditEntry> {
|
|
|
159
265
|
else errored++;
|
|
160
266
|
}
|
|
161
267
|
|
|
268
|
+
const posture = this._postureCounts();
|
|
269
|
+
|
|
162
270
|
const headerLines: Line[] = [
|
|
163
271
|
buildKeyValueLine(width, [
|
|
164
272
|
{ label: 'logged', value: String(entries.length), valueColor: entries.length > 0 ? C.value : C.dim },
|
|
165
|
-
{ label: 'ok', value: String(ok), valueColor: ok > 0 ? C.
|
|
273
|
+
{ label: 'ok', value: String(ok), valueColor: ok > 0 ? C.good : C.dim },
|
|
166
274
|
{ label: 'rejected', value: String(rejected), valueColor: rejected > 0 ? C.rejected : C.dim },
|
|
167
|
-
{ label: 'errors', value: String(errored), valueColor: errored > 0 ? C.
|
|
275
|
+
{ label: 'errors', value: String(errored), valueColor: errored > 0 ? C.bad : C.dim },
|
|
276
|
+
], C),
|
|
277
|
+
buildKeyValueLine(width, [
|
|
278
|
+
{ label: 'running', value: String(posture.running), valueColor: posture.running > 0 ? C.warn : C.dim },
|
|
279
|
+
{ label: 'blocked', value: String(posture.blocked), valueColor: posture.blocked > 0 ? C.warn : C.dim },
|
|
280
|
+
{ label: 'retryable', value: String(posture.retryable), valueColor: posture.retryable > 0 ? C.info : C.dim },
|
|
168
281
|
], C),
|
|
169
282
|
buildPanelLine(width, [[' SEQ TIME ACTION TARGET OUT NOTE', C.label]]),
|
|
170
283
|
];
|
|
171
284
|
|
|
172
|
-
const selected =
|
|
285
|
+
const selected = this.getSelectedItem();
|
|
173
286
|
const footerLines: Line[] = [];
|
|
174
287
|
if (selected) {
|
|
175
288
|
const detail = selected.note ?? selected.errorMessage ?? '(no note)';
|
|
@@ -186,10 +299,13 @@ export class OpsControlPanel extends ScrollableListPanel<OpsAuditEntry> {
|
|
|
186
299
|
]),
|
|
187
300
|
buildPanelLine(width, [
|
|
188
301
|
[' ', C.label],
|
|
189
|
-
[truncateDisplay(detail, Math.max(0, width - 4)), C.
|
|
302
|
+
[truncateDisplay(detail, Math.max(0, width - 4)), C.warn],
|
|
190
303
|
]),
|
|
191
304
|
);
|
|
192
305
|
}
|
|
306
|
+
if (this._confirm) {
|
|
307
|
+
footerLines.push(...renderConfirmLines(width, this._confirm));
|
|
308
|
+
}
|
|
193
309
|
footerLines.push(
|
|
194
310
|
this.filterActive
|
|
195
311
|
? buildKeyboardHints(width, [
|
|
@@ -199,6 +315,7 @@ export class OpsControlPanel extends ScrollableListPanel<OpsAuditEntry> {
|
|
|
199
315
|
], C)
|
|
200
316
|
: buildKeyboardHints(width, [
|
|
201
317
|
{ keys: 'Up/Down', label: 'browse log' },
|
|
318
|
+
{ keys: 'c/p/u/y', label: 'cancel/pause/resume/retry selected' },
|
|
202
319
|
{ keys: '/', label: 'filter' },
|
|
203
320
|
], C),
|
|
204
321
|
);
|
|
@@ -11,7 +11,7 @@ import { BasePanel } from './base-panel.ts';
|
|
|
11
11
|
import type { Line } from '../types/grid.ts';
|
|
12
12
|
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
13
13
|
import type { PlannerDecision, ExecutionStrategy } from '@pellux/goodvibes-sdk/platform/core';
|
|
14
|
-
import type { PlannerEvent } from '@/runtime/index.ts';
|
|
14
|
+
import type { PlannerEvent, PlanRuntimeService } from '@/runtime/index.ts';
|
|
15
15
|
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
16
16
|
import type { OpsStrategyQuery } from '../runtime/ui-service-queries.ts';
|
|
17
17
|
import {
|
|
@@ -50,6 +50,15 @@ const STRATEGY_ICON: Record<ExecutionStrategy, string> = {
|
|
|
50
50
|
remote: '▸',
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
// Cycle order for the o (override) and m (mode) keys — mirrors /plan's
|
|
54
|
+
// documented strategy list (planner-command-handler.ts VALID_STRATEGIES).
|
|
55
|
+
const STRATEGY_CYCLE: readonly ExecutionStrategy[] = ['auto', 'single', 'cohort', 'background', 'remote'];
|
|
56
|
+
|
|
57
|
+
function nextStrategy(current: ExecutionStrategy): ExecutionStrategy {
|
|
58
|
+
const idx = STRATEGY_CYCLE.indexOf(current);
|
|
59
|
+
return STRATEGY_CYCLE[(idx + 1) % STRATEGY_CYCLE.length]!;
|
|
60
|
+
}
|
|
61
|
+
|
|
53
62
|
// ---------------------------------------------------------------------------
|
|
54
63
|
// OpsStrategyPanel
|
|
55
64
|
// ---------------------------------------------------------------------------
|
|
@@ -59,13 +68,16 @@ export class OpsStrategyPanel extends BasePanel {
|
|
|
59
68
|
private scrollOffset = 0;
|
|
60
69
|
private history: PlannerDecision[] = [];
|
|
61
70
|
private readonly adaptivePlanner: OpsStrategyQuery;
|
|
71
|
+
private readonly planRuntime: PlanRuntimeService | undefined;
|
|
62
72
|
|
|
63
73
|
constructor(
|
|
64
74
|
private readonly plannerEvents: UiEventFeed<PlannerEvent>,
|
|
65
75
|
adaptivePlanner: OpsStrategyQuery,
|
|
76
|
+
planRuntime?: PlanRuntimeService,
|
|
66
77
|
) {
|
|
67
|
-
super('ops', 'Ops', '
|
|
78
|
+
super('ops', 'Ops', '◫', 'runtime-ops');
|
|
68
79
|
this.adaptivePlanner = adaptivePlanner;
|
|
80
|
+
this.planRuntime = planRuntime;
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
override onActivate(): void {
|
|
@@ -89,6 +101,8 @@ export class OpsStrategyPanel extends BasePanel {
|
|
|
89
101
|
}
|
|
90
102
|
|
|
91
103
|
handleInput(key: string): boolean {
|
|
104
|
+
if (this.lastError !== null) this.clearError();
|
|
105
|
+
|
|
92
106
|
if (key === 'up' || key === 'k') {
|
|
93
107
|
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
94
108
|
this.markDirty();
|
|
@@ -109,18 +123,72 @@ export class OpsStrategyPanel extends BasePanel {
|
|
|
109
123
|
this.markDirty();
|
|
110
124
|
return true;
|
|
111
125
|
}
|
|
126
|
+
if (key === 'o') {
|
|
127
|
+
this._cycleOverride();
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (key === 'c') {
|
|
131
|
+
this._clearOverride();
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (key === 'm') {
|
|
135
|
+
this._cycleMode();
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
112
138
|
return false;
|
|
113
139
|
}
|
|
114
140
|
|
|
141
|
+
// -------------------------------------------------------------------------
|
|
142
|
+
// Override verbs — o=cycle override, c=clear, m=cycle mode, all dispatched
|
|
143
|
+
// through the same /plan subcommand bridge the slash command uses
|
|
144
|
+
// (planning-runtime.ts), so panel and slash-command behavior stay identical.
|
|
145
|
+
// -------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
private _cycleOverride(): void {
|
|
148
|
+
if (!this.planRuntime) {
|
|
149
|
+
this.setError('Plan runtime is not wired for this runtime.');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const current = this.adaptivePlanner.getOverride() ?? this.adaptivePlanner.getMode();
|
|
153
|
+
const result = this.planRuntime('override', [nextStrategy(current)]);
|
|
154
|
+
if (!result.ok) this.setError(result.output);
|
|
155
|
+
this.markDirty();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private _clearOverride(): void {
|
|
159
|
+
if (!this.planRuntime) {
|
|
160
|
+
this.setError('Plan runtime is not wired for this runtime.');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const result = this.planRuntime('clear', []);
|
|
164
|
+
if (!result.ok) this.setError(result.output);
|
|
165
|
+
this.markDirty();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private _cycleMode(): void {
|
|
169
|
+
if (!this.planRuntime) {
|
|
170
|
+
this.setError('Plan runtime is not wired for this runtime.');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const result = this.planRuntime('mode', [nextStrategy(this.adaptivePlanner.getMode())]);
|
|
174
|
+
if (!result.ok) this.setError(result.output);
|
|
175
|
+
this.markDirty();
|
|
176
|
+
}
|
|
177
|
+
|
|
115
178
|
render(width: number, height: number): Line[] {
|
|
116
179
|
const latest = this.adaptivePlanner.getLatest();
|
|
117
180
|
const mode = this.adaptivePlanner.getMode();
|
|
118
181
|
const override = this.adaptivePlanner.getOverride();
|
|
119
182
|
const intro = 'Review adaptive execution planner decisions, overrides, and recent strategy history.';
|
|
183
|
+
const errorLine = this.renderErrorLine(width);
|
|
120
184
|
const footerLines = [
|
|
185
|
+
...(errorLine ? [errorLine] : []),
|
|
121
186
|
buildKeyboardHints(width, [
|
|
122
187
|
{ keys: 'Up/Down', label: 'scroll history' },
|
|
123
188
|
{ keys: 'g/G', label: 'top/bottom' },
|
|
189
|
+
{ keys: 'o', label: 'cycle override' },
|
|
190
|
+
{ keys: 'c', label: 'clear override' },
|
|
191
|
+
{ keys: 'm', label: 'cycle mode' },
|
|
124
192
|
], C),
|
|
125
193
|
];
|
|
126
194
|
const statusLines: Line[] = [
|
|
@@ -153,8 +221,8 @@ export class OpsStrategyPanel extends BasePanel {
|
|
|
153
221
|
lines: buildEmptyState(
|
|
154
222
|
width,
|
|
155
223
|
' No decisions recorded yet',
|
|
156
|
-
'Adaptive planner decisions appear here once the planner begins selecting strategies.
|
|
157
|
-
[{ command: '/
|
|
224
|
+
'Adaptive planner decisions appear here once the planner begins selecting strategies. Seed or inspect an execution plan to drive runtime activity.',
|
|
225
|
+
[{ command: '/plan', summary: 'seed or inspect an execution plan; strategy decisions start appearing here once it runs' }],
|
|
158
226
|
C,
|
|
159
227
|
),
|
|
160
228
|
},
|