@pellux/goodvibes-tui 0.26.0 → 0.28.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 +40 -0
- package/README.md +12 -7
- package/docs/foundation-artifacts/operator-contract.json +4 -1
- package/package.json +2 -2
- package/src/cli/bundle-command.ts +2 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/core/context-usage.ts +53 -0
- package/src/core/conversation-rendering.ts +2 -2
- package/src/core/conversation.ts +53 -8
- package/src/core/session-recovery.ts +26 -18
- package/src/core/system-message-router.ts +42 -26
- package/src/core/turn-event-wiring.ts +13 -16
- package/src/daemon/handlers/calendar/caldav-client.ts +2 -1
- package/src/daemon/handlers/inbox/index.ts +2 -1
- package/src/daemon/handlers/inbox/poller.ts +2 -1
- package/src/daemon/handlers/inbox/providers/discord.ts +2 -1
- package/src/daemon/handlers/inbox/providers/email.ts +2 -1
- package/src/daemon/handlers/inbox/providers/route-util.ts +2 -1
- package/src/daemon/handlers/inbox/providers/slack.ts +2 -1
- package/src/daemon/handlers/triage/integration.ts +2 -1
- package/src/daemon/handlers/triage/pipeline.ts +2 -1
- package/src/daemon/handlers/triage/tagger/discord.ts +2 -1
- package/src/daemon/handlers/triage/tagger/imap.ts +4 -3
- package/src/export/gist-uploader.ts +3 -1
- package/src/input/command-registry.ts +1 -0
- package/src/input/commands/cloudflare-runtime.ts +2 -1
- package/src/input/commands/eval.ts +4 -3
- package/src/input/commands/guidance-runtime.ts +2 -4
- package/src/input/commands/health-runtime.ts +13 -7
- package/src/input/commands/knowledge.ts +2 -1
- package/src/input/commands/runtime-services.ts +40 -10
- package/src/input/handler-command-route.ts +1 -1
- package/src/input/handler-feed-routes.ts +61 -4
- package/src/input/handler-feed.ts +13 -0
- package/src/input/handler-interactions.ts +2 -1
- package/src/input/handler-modal-routes.ts +2 -1
- package/src/input/handler-onboarding-cloudflare.ts +2 -1
- package/src/input/handler-onboarding.ts +8 -8
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-ui-state.ts +5 -1
- package/src/input/keybindings.ts +5 -0
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +10 -0
- package/src/input/tts-settings-actions.ts +2 -1
- package/src/main.ts +52 -60
- package/src/panels/agent-inspector-panel.ts +90 -15
- package/src/panels/agent-inspector-shared.ts +3 -3
- package/src/panels/agent-logs-panel.ts +149 -72
- package/src/panels/approval-panel.ts +146 -95
- package/src/panels/automation-control-panel.ts +24 -1
- package/src/panels/base-panel.ts +28 -3
- package/src/panels/builtin/agent.ts +3 -1
- package/src/panels/builtin/development.ts +2 -1
- package/src/panels/builtin/session.ts +1 -1
- package/src/panels/cockpit-panel.ts +24 -5
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +108 -43
- package/src/panels/context-visualizer-panel.ts +49 -15
- package/src/panels/control-plane-panel.ts +27 -1
- package/src/panels/cost-tracker-panel.ts +87 -65
- package/src/panels/debug-panel.ts +61 -37
- package/src/panels/diff-panel.ts +59 -30
- package/src/panels/docs-panel.ts +30 -24
- package/src/panels/eval-panel.ts +130 -81
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +42 -42
- package/src/panels/file-preview-panel.ts +11 -3
- package/src/panels/forensics-panel.ts +27 -13
- package/src/panels/git-panel.ts +65 -84
- package/src/panels/hooks-panel.ts +36 -2
- package/src/panels/incident-review-panel.ts +31 -10
- package/src/panels/intelligence-panel.ts +152 -71
- package/src/panels/knowledge-graph-panel.ts +89 -27
- package/src/panels/local-auth-panel.ts +17 -11
- package/src/panels/marketplace-panel.ts +33 -13
- package/src/panels/memory-panel.ts +7 -5
- package/src/panels/ops-control-panel.ts +69 -7
- package/src/panels/ops-strategy-panel.ts +61 -43
- package/src/panels/orchestration-panel.ts +34 -4
- package/src/panels/panel-list-panel.ts +33 -8
- package/src/panels/panel-manager.ts +23 -4
- package/src/panels/plan-dashboard-panel.ts +183 -66
- package/src/panels/plugins-panel.ts +48 -10
- package/src/panels/policy-panel.ts +60 -23
- package/src/panels/polish-core.ts +157 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +44 -145
- package/src/panels/project-planning-panel.ts +27 -4
- package/src/panels/provider-accounts-panel.ts +55 -18
- package/src/panels/provider-health-panel.ts +118 -87
- package/src/panels/provider-stats-panel.ts +47 -22
- package/src/panels/qr-panel.ts +23 -11
- package/src/panels/remote-panel.ts +12 -5
- package/src/panels/routes-panel.ts +27 -5
- package/src/panels/sandbox-panel.ts +62 -27
- package/src/panels/schedule-panel.ts +44 -24
- package/src/panels/scrollable-list-panel.ts +124 -10
- package/src/panels/security-panel.ts +72 -20
- package/src/panels/services-panel.ts +68 -22
- package/src/panels/session-browser-panel.ts +42 -26
- package/src/panels/session-maintenance.ts +2 -2
- package/src/panels/settings-sync-panel.ts +30 -15
- package/src/panels/skills-panel.ts +2 -1
- package/src/panels/subscription-panel.ts +21 -3
- package/src/panels/symbol-outline-panel.ts +40 -47
- package/src/panels/system-messages-panel.ts +60 -27
- package/src/panels/tasks-panel.ts +36 -14
- package/src/panels/thinking-panel.ts +32 -36
- package/src/panels/token-budget-panel.ts +80 -53
- package/src/panels/tool-inspector-panel.ts +37 -39
- package/src/panels/types.ts +25 -0
- package/src/panels/watchers-panel.ts +25 -5
- package/src/panels/work-plan-panel.ts +127 -35
- package/src/panels/worktree-panel.ts +65 -20
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +53 -147
- package/src/renderer/agent-detail-modal.ts +2 -2
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- package/src/renderer/modal-utils.ts +0 -10
- package/src/renderer/model-picker-overlay.ts +9 -4
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/process-modal.ts +2 -2
- package/src/renderer/progress.ts +3 -2
- package/src/renderer/tab-strip.ts +148 -34
- package/src/renderer/ui-factory.ts +4 -6
- package/src/runtime/bootstrap-command-context.ts +3 -0
- package/src/runtime/bootstrap-command-parts.ts +3 -1
- package/src/runtime/bootstrap-core.ts +31 -31
- package/src/runtime/bootstrap-shell.ts +1 -0
- package/src/runtime/onboarding/apply.ts +4 -3
- package/src/runtime/onboarding/snapshot.ts +4 -3
- package/src/runtime/process-lifecycle.ts +195 -0
- package/src/runtime/services.ts +4 -1
- package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/runtime/wrfc-persistence.ts +20 -5
- package/src/shell/ui-openers.ts +3 -2
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- package/src/verification/live-verifier.ts +4 -3
- package/src/version.ts +1 -1
- package/src/core/context-auto-compact.ts +0 -110
- package/src/panels/panel-picker.ts +0 -106
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
2
|
import { createEmptyLine } from '../types/grid.ts';
|
|
3
|
+
import { fitDisplay, truncateDisplay } from '../utils/terminal-width.ts';
|
|
3
4
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
4
5
|
import {
|
|
5
6
|
buildDetailBlock,
|
|
@@ -136,8 +137,8 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
136
137
|
|
|
137
138
|
protected renderItem(user: LocalAuthUser, _index: number, selected: boolean, width: number): Line {
|
|
138
139
|
return buildPanelListRow(width, [
|
|
139
|
-
{ text: user.username
|
|
140
|
-
{ text: ` roles=${formatRoles(user.roles)}
|
|
140
|
+
{ text: fitDisplay(user.username, 20), fg: C.value },
|
|
141
|
+
{ text: truncateDisplay(` roles=${formatRoles(user.roles)}`, Math.max(0, width - 24)), fg: C.info },
|
|
141
142
|
], C, { selected });
|
|
142
143
|
}
|
|
143
144
|
|
|
@@ -171,10 +172,10 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
171
172
|
[' bootstrap ', C.label],
|
|
172
173
|
[snapshot.bootstrapCredentialPresent ? 'present' : 'cleared', snapshot.bootstrapCredentialPresent ? C.warn : C.good],
|
|
173
174
|
]),
|
|
174
|
-
buildPanelLine(width, [[' user store ', C.label], [snapshot.userStorePath
|
|
175
|
-
buildPanelLine(width, [[' bootstrap file ', C.label], [snapshot.bootstrapCredentialPath
|
|
175
|
+
buildPanelLine(width, [[' user store ', C.label], [truncateDisplay(snapshot.userStorePath, Math.max(0, width - 13)), C.dim]]),
|
|
176
|
+
buildPanelLine(width, [[' bootstrap file ', C.label], [truncateDisplay(snapshot.bootstrapCredentialPath, Math.max(0, width - 18)), C.dim]]),
|
|
176
177
|
...(issueMessages.length > 0
|
|
177
|
-
? issueMessages.map((issue) => buildPanelLine(width, [[` issue: ${issue}
|
|
178
|
+
? issueMessages.map((issue) => buildPanelLine(width, [[truncateDisplay(` issue: ${issue}`, width), C.warn]]))
|
|
178
179
|
: [buildPanelLine(width, [[' local auth posture looks healthy.', C.good]])]),
|
|
179
180
|
buildGuidanceLine(width, '/auth local rotate-password <user>', 'open masked password entry for the selected user (no plaintext in history)', C),
|
|
180
181
|
], C),
|
|
@@ -198,19 +199,20 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
198
199
|
if (selected) {
|
|
199
200
|
footerLines.push(
|
|
200
201
|
...buildDetailBlock(width, 'Selected user', [
|
|
201
|
-
buildPanelLine(width, [[' username ', C.label], [selected.username, C.value], [' roles ', C.label], [formatRoles(selected.roles)
|
|
202
|
-
buildPanelLine(width, [[` next: /auth local rotate-password ${selected.username}
|
|
203
|
-
buildPanelLine(width, [[` next: /auth local delete-user ${selected.username}
|
|
202
|
+
buildPanelLine(width, [[' username ', C.label], [selected.username, C.value], [' roles ', C.label], [truncateDisplay(formatRoles(selected.roles), Math.max(0, width - 23)), C.info]]),
|
|
203
|
+
buildPanelLine(width, [[truncateDisplay(` next: /auth local rotate-password ${selected.username}`, width), C.dim]]),
|
|
204
|
+
buildPanelLine(width, [[truncateDisplay(` next: /auth local delete-user ${selected.username}`, width), C.dim]]),
|
|
204
205
|
], C),
|
|
205
206
|
);
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
if (snapshot.sessions.length > 0) {
|
|
210
|
+
footerLines.push(buildPanelLine(width, [[` Active sessions (${snapshot.sessions.length})`, C.label]]));
|
|
209
211
|
footerLines.push(
|
|
210
212
|
...snapshot.sessions.slice(0, 8).map((session) => buildPanelLine(width, [
|
|
211
213
|
[' ', C.label],
|
|
212
|
-
[session.username
|
|
213
|
-
[` expires ${new Date(session.expiresAt).toLocaleString()}
|
|
214
|
+
[fitDisplay(session.username, 18), C.value],
|
|
215
|
+
[truncateDisplay(` expires ${new Date(session.expiresAt).toLocaleString()}`, Math.max(0, width - 20)), C.dim],
|
|
214
216
|
])),
|
|
215
217
|
);
|
|
216
218
|
}
|
|
@@ -220,6 +222,10 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
220
222
|
title: 'Local Auth Control Room',
|
|
221
223
|
header: headerLines,
|
|
222
224
|
footer: footerLines,
|
|
225
|
+
hints: [
|
|
226
|
+
{ keys: '↑/↓', label: 'select user' },
|
|
227
|
+
{ keys: '/auth local rotate-password', label: 'masked entry' },
|
|
228
|
+
],
|
|
223
229
|
});
|
|
224
230
|
}
|
|
225
231
|
|
|
@@ -233,7 +239,7 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
233
239
|
const promptLines: Line[] = [
|
|
234
240
|
buildPanelLine(width, [[` ${actionLabel}: ${state.username}`, C.value]]),
|
|
235
241
|
buildPanelLine(width, [['', C.label]]),
|
|
236
|
-
buildPanelLine(width, [[' Password ', C.label], [maskedDisplay
|
|
242
|
+
buildPanelLine(width, [[' Password ', C.label], [truncateDisplay(maskedDisplay, Math.max(0, width - 12)), C.info]]),
|
|
237
243
|
buildPanelLine(width, [['', C.label]]),
|
|
238
244
|
buildPanelLine(width, [[' [Enter] Confirm [Esc] Cancel [Backspace] Delete char', C.dim]]),
|
|
239
245
|
];
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
|
+
import { fitDisplay, truncateDisplay } from '../utils/terminal-width.ts';
|
|
2
3
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
3
4
|
import {
|
|
4
5
|
buildEmptyState,
|
|
5
6
|
buildGuidanceLine,
|
|
6
7
|
buildKeyValueLine,
|
|
8
|
+
buildKeyboardHints,
|
|
7
9
|
buildPanelLine,
|
|
8
10
|
buildPanelWorkspace,
|
|
9
11
|
DEFAULT_PANEL_PALETTE,
|
|
10
|
-
type PanelWorkspaceSection,
|
|
11
12
|
} from './polish.ts';
|
|
12
13
|
import {
|
|
13
14
|
type EcosystemCatalogPathOptions,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
type EcosystemEntryKind,
|
|
19
20
|
} from '@/runtime/index.ts';
|
|
20
21
|
import type { UiMarketplaceSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
|
|
22
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
21
23
|
|
|
22
24
|
const C = {
|
|
23
25
|
...DEFAULT_PANEL_PALETTE,
|
|
@@ -44,9 +46,17 @@ export class MarketplacePanel extends ScrollableListPanel<MarketplaceRow> {
|
|
|
44
46
|
private readonly ecosystemPaths?: EcosystemCatalogPathOptions,
|
|
45
47
|
) {
|
|
46
48
|
super('marketplace', 'Marketplace', 'M', 'monitoring');
|
|
49
|
+
this.filterEnabled = true;
|
|
50
|
+
this.filterLabel = 'Filter marketplace';
|
|
47
51
|
this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
protected override filterMatches(row: MarketplaceRow, q: string): boolean {
|
|
55
|
+
return row.kind.toLowerCase().includes(q)
|
|
56
|
+
|| row.entry.name.toLowerCase().includes(q)
|
|
57
|
+
|| (row.entry.provenance ?? 'local').toLowerCase().includes(q);
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
public override onDestroy(): void {
|
|
51
61
|
this.unsub?.();
|
|
52
62
|
}
|
|
@@ -70,8 +80,8 @@ export class MarketplacePanel extends ScrollableListPanel<MarketplaceRow> {
|
|
|
70
80
|
return buildPanelLine(width, [
|
|
71
81
|
[' ', C.label, bg],
|
|
72
82
|
[row.kind.padEnd(11), C.info, bg],
|
|
73
|
-
[row.entry.name
|
|
74
|
-
[` ${provenance
|
|
83
|
+
[fitDisplay(row.entry.name, 20), C.value, bg],
|
|
84
|
+
[` ${fitDisplay(provenance, 16)}`, provenance === 'local' ? C.dim : C.info, bg],
|
|
75
85
|
[` ${(row.installed ? 'INSTALLED' : 'CURATED').padEnd(9)} `, statusColor(row.installed), bg],
|
|
76
86
|
[` ${row.entry.version ?? 'n/a'}`, C.dim, bg],
|
|
77
87
|
]);
|
|
@@ -114,7 +124,7 @@ export class MarketplacePanel extends ScrollableListPanel<MarketplaceRow> {
|
|
|
114
124
|
this.clearError();
|
|
115
125
|
} catch (e) {
|
|
116
126
|
// I2: surface catalog load failure
|
|
117
|
-
this.setError(`Catalog load failed: ${
|
|
127
|
+
this.setError(`Catalog load failed: ${summarizeError(e)}`);
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
130
|
|
|
@@ -166,13 +176,13 @@ export class MarketplacePanel extends ScrollableListPanel<MarketplaceRow> {
|
|
|
166
176
|
const recommendationLines = recommendations.length > 0
|
|
167
177
|
? recommendations.slice(0, 4).map((recommendation) => buildPanelLine(width, [
|
|
168
178
|
[' ', C.label],
|
|
169
|
-
[`${recommendation.kind} ${recommendation.entry.id}
|
|
170
|
-
[` ${recommendation.title}
|
|
179
|
+
[fitDisplay(`${recommendation.kind} ${recommendation.entry.id}`, 28), C.info],
|
|
180
|
+
[truncateDisplay(` ${recommendation.title}`, Math.max(0, width - 31)), C.dim],
|
|
171
181
|
]))
|
|
172
182
|
: [buildPanelLine(width, [[' No contextual marketplace recommendations right now.', C.dim]])];
|
|
173
183
|
|
|
174
184
|
const startupIssueLines = startupIssues.length > 0
|
|
175
|
-
? startupIssues.slice(0, 4).map((issue) => buildPanelLine(width, [[' ', C.label], [issue
|
|
185
|
+
? startupIssues.slice(0, 4).map((issue) => buildPanelLine(width, [[' ', C.label], [truncateDisplay(issue, Math.max(0, width - 2)), C.warn]]))
|
|
176
186
|
: [buildPanelLine(width, [[' No startup or lifecycle issues are currently pushing marketplace repair recommendations.', C.dim]])];
|
|
177
187
|
|
|
178
188
|
const selectedRow = this.rows[this.selectedIndex];
|
|
@@ -181,11 +191,11 @@ export class MarketplacePanel extends ScrollableListPanel<MarketplaceRow> {
|
|
|
181
191
|
const review = reviewEcosystemCatalogEntry(selectedRow.entry, this.ecosystemPaths!);
|
|
182
192
|
selectedLines.push(buildPanelLine(width, [
|
|
183
193
|
[' Provenance: ', C.label],
|
|
184
|
-
[(selectedRow.entry.provenance ?? '(none)'
|
|
194
|
+
[truncateDisplay(selectedRow.entry.provenance ?? '(none)', Math.max(0, width - 15)), selectedRow.entry.provenance ? C.info : C.dim],
|
|
185
195
|
]));
|
|
186
196
|
selectedLines.push(buildPanelLine(width, [
|
|
187
197
|
[' Source: ', C.label],
|
|
188
|
-
[selectedRow.entry.source
|
|
198
|
+
[truncateDisplay(selectedRow.entry.source, Math.max(0, width - 11)), C.value],
|
|
189
199
|
]));
|
|
190
200
|
selectedLines.push(buildKeyValueLine(width, [
|
|
191
201
|
{ label: 'Compatibility', value: review.compatibility.status, valueColor: review.compatibility.status === 'supported' ? C.good : C.warn },
|
|
@@ -195,9 +205,19 @@ export class MarketplacePanel extends ScrollableListPanel<MarketplaceRow> {
|
|
|
195
205
|
selectedLines.push(buildGuidanceLine(width, '/marketplace review <id>', 'inspect full compatibility and receipt detail for the selected entry', C));
|
|
196
206
|
}
|
|
197
207
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const
|
|
208
|
+
// Context-aware hints: filter mode vs. browse mode (install only makes sense
|
|
209
|
+
// when a curated, not-yet-installed entry is selected).
|
|
210
|
+
const hints = this.filterActive
|
|
211
|
+
? [{ keys: 'type', label: 'filter' }, { keys: 'Enter', label: 'apply' }, { keys: 'Esc', label: 'clear' }]
|
|
212
|
+
: [
|
|
213
|
+
{ keys: 'Up/Down', label: 'move' },
|
|
214
|
+
...(selectedRow && !selectedRow.installed ? [{ keys: '/marketplace install', label: 'add' }] : []),
|
|
215
|
+
{ keys: '/', label: 'filter' },
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
const footer: Line[] = selectedLines.length > 0 && height >= 20
|
|
219
|
+
? [...selectedLines, buildKeyboardHints(width, hints, C)]
|
|
220
|
+
: [buildKeyboardHints(width, hints, C)];
|
|
201
221
|
|
|
202
222
|
return this.renderList(width, height, {
|
|
203
223
|
title: 'Marketplace Control Room',
|
|
@@ -206,7 +226,7 @@ export class MarketplacePanel extends ScrollableListPanel<MarketplaceRow> {
|
|
|
206
226
|
...startupIssueLines,
|
|
207
227
|
...recommendationLines,
|
|
208
228
|
],
|
|
209
|
-
footer
|
|
229
|
+
footer,
|
|
210
230
|
});
|
|
211
231
|
}
|
|
212
232
|
}
|
|
@@ -14,6 +14,7 @@ import type { Line } from '../types/grid.ts';
|
|
|
14
14
|
import type { MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state';
|
|
15
15
|
import type { MemoryClass, MemoryRecord, MemoryReviewState } from '@pellux/goodvibes-sdk/platform/state';
|
|
16
16
|
import { ScrollableListPanel, SearchableListPanel } from './scrollable-list-panel.ts';
|
|
17
|
+
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
17
18
|
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
18
19
|
import {
|
|
19
20
|
buildBodyText,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
getPanelSearchFocusTransition,
|
|
29
30
|
isPanelSearchCancel,
|
|
30
31
|
} from './search-focus.ts';
|
|
32
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
31
33
|
|
|
32
34
|
// ---------------------------------------------------------------------------
|
|
33
35
|
// Colour palette
|
|
@@ -175,7 +177,7 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
|
|
|
175
177
|
[' ', C.label, bg],
|
|
176
178
|
[record.reviewState.padEnd(13), reviewStateColor(record.reviewState), bg],
|
|
177
179
|
[` ${formatConfidence(record.confidence)} `, C.value, bg],
|
|
178
|
-
[record.summary
|
|
180
|
+
[truncateDisplay(record.summary, Math.max(0, width - 26)), C.value, bg],
|
|
179
181
|
]);
|
|
180
182
|
}
|
|
181
183
|
// All-mode row: scope/class + id + time + summary (matches former MemoryPanel row)
|
|
@@ -187,7 +189,7 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
|
|
|
187
189
|
[' ', C.label, bg],
|
|
188
190
|
[fmtTime(record.createdAt), C.dim, bg],
|
|
189
191
|
[' ', C.label, bg],
|
|
190
|
-
[record.summary
|
|
192
|
+
[truncateDisplay(record.summary, Math.max(0, width - 33)), C.value, bg],
|
|
191
193
|
]);
|
|
192
194
|
}
|
|
193
195
|
|
|
@@ -267,7 +269,7 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
|
|
|
267
269
|
});
|
|
268
270
|
}
|
|
269
271
|
} catch (e) {
|
|
270
|
-
this.setError(`Review update failed: ${
|
|
272
|
+
this.setError(`Review update failed: ${summarizeError(e)}`);
|
|
271
273
|
}
|
|
272
274
|
}
|
|
273
275
|
this.refreshReviewRecords();
|
|
@@ -305,13 +307,13 @@ export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
|
|
|
305
307
|
}
|
|
306
308
|
if (key === 's') {
|
|
307
309
|
if (!selected) return false;
|
|
308
|
-
this.confirm = { subject: { id: selected.id, action: 'stale' }, label: selected.summary
|
|
310
|
+
this.confirm = { subject: { id: selected.id, action: 'stale' }, label: truncateDisplay(selected.summary, 40) };
|
|
309
311
|
this.markDirty();
|
|
310
312
|
return true;
|
|
311
313
|
}
|
|
312
314
|
if (key === 'c') {
|
|
313
315
|
if (!selected) return false;
|
|
314
|
-
this.confirm = { subject: { id: selected.id, action: 'contradicted' }, label: selected.summary
|
|
316
|
+
this.confirm = { subject: { id: selected.id, action: 'contradicted' }, label: truncateDisplay(selected.summary, 40) };
|
|
315
317
|
this.markDirty();
|
|
316
318
|
return true;
|
|
317
319
|
}
|
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
* Open via Ctrl+O keybind or `/ops view` command.
|
|
9
9
|
*/
|
|
10
10
|
import type { Line } from '../types/grid.ts';
|
|
11
|
+
import { fitDisplay, truncateDisplay } from '../utils/terminal-width.ts';
|
|
11
12
|
import type { OpsEvent } from '@/runtime/index.ts';
|
|
12
13
|
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
13
14
|
import type { OpsAuditEntry } from '../runtime/diagnostics/panels/ops.ts';
|
|
14
15
|
import { OpsPanel } from '../runtime/diagnostics/panels/ops.ts';
|
|
15
16
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
16
17
|
import {
|
|
18
|
+
buildKeyValueLine,
|
|
19
|
+
buildKeyboardHints,
|
|
17
20
|
buildPanelLine,
|
|
18
21
|
DEFAULT_PANEL_PALETTE,
|
|
19
22
|
type PanelPalette,
|
|
@@ -77,6 +80,8 @@ export class OpsControlPanel extends ScrollableListPanel<OpsAuditEntry> {
|
|
|
77
80
|
public constructor(eventFeed: UiEventFeed<OpsEvent>) {
|
|
78
81
|
super('ops-control', 'Ops Control', 'Q', 'agent');
|
|
79
82
|
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
83
|
+
this.filterEnabled = true;
|
|
84
|
+
this.filterLabel = 'Filter audit';
|
|
80
85
|
this._opsPanel = new OpsPanel(eventFeed);
|
|
81
86
|
this._unsub = this._opsPanel.subscribe(() => this.markDirty());
|
|
82
87
|
}
|
|
@@ -103,16 +108,23 @@ export class OpsControlPanel extends ScrollableListPanel<OpsAuditEntry> {
|
|
|
103
108
|
return [...this._opsPanel.getSnapshot()].reverse();
|
|
104
109
|
}
|
|
105
110
|
|
|
111
|
+
protected override filterMatches(entry: OpsAuditEntry, q: string): boolean {
|
|
112
|
+
return entry.action.toLowerCase().includes(q)
|
|
113
|
+
|| entry.targetKind.toLowerCase().includes(q)
|
|
114
|
+
|| entry.targetId.toLowerCase().includes(q)
|
|
115
|
+
|| entry.outcome.toLowerCase().includes(q);
|
|
116
|
+
}
|
|
117
|
+
|
|
106
118
|
protected renderItem(entry: OpsAuditEntry, _index: number, _selected: boolean, width: number): Line {
|
|
107
119
|
const seqStr = String(entry.seq).padStart(4, ' ');
|
|
108
120
|
const timeStr = fmtTime(entry.ts);
|
|
109
|
-
const action = entry.action
|
|
121
|
+
const action = fitDisplay(entry.action, 15);
|
|
110
122
|
const kindTag = entry.targetKind === 'task' ? 'T:' : 'A:';
|
|
111
|
-
//
|
|
123
|
+
// Last 10 chars of the id keep the unique suffix; fitDisplay caps display width.
|
|
112
124
|
const shortId = entry.targetId.slice(-10);
|
|
113
|
-
const target = (kindTag + shortId
|
|
125
|
+
const target = fitDisplay(kindTag + shortId, 14);
|
|
114
126
|
const outLabel = outcomeLabel(entry.outcome);
|
|
115
|
-
const noteRaw = (entry.note ?? entry.errorMessage ?? ''
|
|
127
|
+
const noteRaw = truncateDisplay(entry.note ?? entry.errorMessage ?? '', Math.max(0, width - 63));
|
|
116
128
|
|
|
117
129
|
const segs: Array<[string, string, string?]> = [
|
|
118
130
|
[` ${seqStr} `, C.seq],
|
|
@@ -134,12 +146,62 @@ export class OpsControlPanel extends ScrollableListPanel<OpsAuditEntry> {
|
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
public render(width: number, height: number): Line[] {
|
|
149
|
+
const entries = this.getVisibleItems();
|
|
150
|
+
this.clampSelection();
|
|
151
|
+
|
|
152
|
+
// Outcome tallies surface posture at a glance (most important runtime info first).
|
|
153
|
+
let ok = 0;
|
|
154
|
+
let rejected = 0;
|
|
155
|
+
let errored = 0;
|
|
156
|
+
for (const e of entries) {
|
|
157
|
+
if (e.outcome === 'success') ok++;
|
|
158
|
+
else if (e.outcome === 'rejected') rejected++;
|
|
159
|
+
else errored++;
|
|
160
|
+
}
|
|
161
|
+
|
|
137
162
|
const headerLines: Line[] = [
|
|
163
|
+
buildKeyValueLine(width, [
|
|
164
|
+
{ label: 'logged', value: String(entries.length), valueColor: entries.length > 0 ? C.value : C.dim },
|
|
165
|
+
{ label: 'ok', value: String(ok), valueColor: ok > 0 ? C.success : C.dim },
|
|
166
|
+
{ label: 'rejected', value: String(rejected), valueColor: rejected > 0 ? C.rejected : C.dim },
|
|
167
|
+
{ label: 'errors', value: String(errored), valueColor: errored > 0 ? C.error : C.dim },
|
|
168
|
+
], C),
|
|
138
169
|
buildPanelLine(width, [[' SEQ TIME ACTION TARGET OUT NOTE', C.label]]),
|
|
139
170
|
];
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
];
|
|
171
|
+
|
|
172
|
+
const selected = entries[this.selectedIndex];
|
|
173
|
+
const footerLines: Line[] = [];
|
|
174
|
+
if (selected) {
|
|
175
|
+
const detail = selected.note ?? selected.errorMessage ?? '(no note)';
|
|
176
|
+
footerLines.push(
|
|
177
|
+
buildPanelLine(width, [
|
|
178
|
+
[' #', C.label],
|
|
179
|
+
[String(selected.seq), C.value],
|
|
180
|
+
[' ', C.dim],
|
|
181
|
+
[outcomeLabel(selected.outcome).trim(), outcomeColor(selected.outcome)],
|
|
182
|
+
[' ', C.dim],
|
|
183
|
+
[selected.action, C.value],
|
|
184
|
+
[' ', C.dim],
|
|
185
|
+
[`${selected.targetKind}:${selected.targetId}`, targetColor(selected.targetKind)],
|
|
186
|
+
]),
|
|
187
|
+
buildPanelLine(width, [
|
|
188
|
+
[' ', C.label],
|
|
189
|
+
[truncateDisplay(detail, Math.max(0, width - 4)), C.note],
|
|
190
|
+
]),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
footerLines.push(
|
|
194
|
+
this.filterActive
|
|
195
|
+
? buildKeyboardHints(width, [
|
|
196
|
+
{ keys: 'type', label: 'filter audit' },
|
|
197
|
+
{ keys: 'Enter', label: 'apply' },
|
|
198
|
+
{ keys: 'Esc', label: 'clear' },
|
|
199
|
+
], C)
|
|
200
|
+
: buildKeyboardHints(width, [
|
|
201
|
+
{ keys: 'Up/Down', label: 'browse log' },
|
|
202
|
+
{ keys: '/', label: 'filter' },
|
|
203
|
+
], C),
|
|
204
|
+
);
|
|
143
205
|
|
|
144
206
|
return this.renderList(width, height, {
|
|
145
207
|
title: 'Operator Control Plane',
|
|
@@ -9,25 +9,37 @@
|
|
|
9
9
|
|
|
10
10
|
import { BasePanel } from './base-panel.ts';
|
|
11
11
|
import type { Line } from '../types/grid.ts';
|
|
12
|
+
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
12
13
|
import type { PlannerDecision, ExecutionStrategy } from '@pellux/goodvibes-sdk/platform/core';
|
|
13
14
|
import type { PlannerEvent } from '@/runtime/index.ts';
|
|
14
15
|
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
15
16
|
import type { OpsStrategyQuery } from '../runtime/ui-service-queries.ts';
|
|
16
17
|
import {
|
|
18
|
+
buildAlignedRow,
|
|
17
19
|
buildEmptyState,
|
|
20
|
+
buildKeyboardHints,
|
|
18
21
|
buildPanelLine,
|
|
19
22
|
buildStyledPanelLine,
|
|
20
23
|
buildPanelWorkspace,
|
|
24
|
+
extendPalette,
|
|
21
25
|
resolveScrollablePanelSection,
|
|
22
26
|
DEFAULT_PANEL_PALETTE,
|
|
23
27
|
} from './polish.ts';
|
|
24
28
|
|
|
25
|
-
const
|
|
29
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
26
30
|
auto: '#00cccc',
|
|
27
31
|
single: '#00cc66',
|
|
28
32
|
cohort: '#cccc00',
|
|
29
33
|
background: '#cc66cc',
|
|
30
34
|
remote: '#cccccc',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const STRATEGY_FG: Record<ExecutionStrategy, string> = {
|
|
38
|
+
auto: C.auto,
|
|
39
|
+
single: C.single,
|
|
40
|
+
cohort: C.cohort,
|
|
41
|
+
background: C.background,
|
|
42
|
+
remote: C.remote,
|
|
31
43
|
};
|
|
32
44
|
|
|
33
45
|
const STRATEGY_ICON: Record<ExecutionStrategy, string> = {
|
|
@@ -104,58 +116,63 @@ export class OpsStrategyPanel extends BasePanel {
|
|
|
104
116
|
const latest = this.adaptivePlanner.getLatest();
|
|
105
117
|
const mode = this.adaptivePlanner.getMode();
|
|
106
118
|
const override = this.adaptivePlanner.getOverride();
|
|
119
|
+
const intro = 'Review adaptive execution planner decisions, overrides, and recent strategy history.';
|
|
120
|
+
const footerLines = [
|
|
121
|
+
buildKeyboardHints(width, [
|
|
122
|
+
{ keys: 'Up/Down', label: 'scroll history' },
|
|
123
|
+
{ keys: 'g/G', label: 'top/bottom' },
|
|
124
|
+
], C),
|
|
125
|
+
];
|
|
107
126
|
const statusLines: Line[] = [
|
|
108
127
|
buildPanelLine(width, [
|
|
109
|
-
[' Mode ',
|
|
110
|
-
[mode.toUpperCase(),
|
|
111
|
-
[' Override ',
|
|
112
|
-
[override ? `${override.toUpperCase()} [ACTIVE]` : 'none', override ?
|
|
128
|
+
[' Mode ', C.label],
|
|
129
|
+
[mode.toUpperCase(), C.value],
|
|
130
|
+
[' Override ', C.label],
|
|
131
|
+
[override ? `${override.toUpperCase()} [ACTIVE]` : 'none', override ? C.warn : C.dim],
|
|
132
|
+
[' Decisions ', C.label],
|
|
133
|
+
[String(this.history.length), this.history.length > 0 ? C.info : C.dim],
|
|
113
134
|
]),
|
|
114
135
|
];
|
|
115
136
|
|
|
116
137
|
if (latest) {
|
|
117
138
|
statusLines.push(buildPanelLine(width, [
|
|
118
|
-
[' Last ',
|
|
139
|
+
[' Last ', C.label],
|
|
119
140
|
[`${STRATEGY_ICON[latest.selected]} ${latest.selected.toUpperCase()}`, STRATEGY_FG[latest.selected]],
|
|
120
|
-
[' Reason ',
|
|
121
|
-
[latest.reasonCode,
|
|
141
|
+
[' Reason ', C.label],
|
|
142
|
+
[latest.reasonCode, C.dim],
|
|
122
143
|
]));
|
|
123
144
|
}
|
|
124
145
|
|
|
125
146
|
if (this.history.length === 0) {
|
|
126
147
|
return buildPanelWorkspace(width, height, {
|
|
127
148
|
title: ' Ops Strategy',
|
|
128
|
-
intro
|
|
149
|
+
intro,
|
|
129
150
|
sections: [
|
|
130
151
|
{ title: 'Status', lines: statusLines },
|
|
131
152
|
{
|
|
132
153
|
lines: buildEmptyState(
|
|
133
154
|
width,
|
|
134
155
|
' No decisions recorded yet',
|
|
135
|
-
'Adaptive planner decisions appear here once the planner begins selecting strategies.',
|
|
136
|
-
[],
|
|
137
|
-
|
|
156
|
+
'Adaptive planner decisions appear here once the planner begins selecting strategies. Run agents or use /ops to drive runtime activity.',
|
|
157
|
+
[{ command: '/ops', summary: 'open the operator control room to drive planner activity' }],
|
|
158
|
+
C,
|
|
138
159
|
),
|
|
139
160
|
},
|
|
140
161
|
],
|
|
141
|
-
footerLines
|
|
142
|
-
|
|
143
|
-
],
|
|
144
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
162
|
+
footerLines,
|
|
163
|
+
palette: C,
|
|
145
164
|
});
|
|
146
165
|
}
|
|
147
166
|
|
|
148
167
|
const historyLines = this._renderHistory(width);
|
|
149
168
|
const statusSection = { title: 'Status', lines: statusLines } as const;
|
|
150
169
|
const historySection = resolveScrollablePanelSection(width, height, {
|
|
151
|
-
intro
|
|
152
|
-
footerLines
|
|
153
|
-
|
|
154
|
-
],
|
|
155
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
170
|
+
intro,
|
|
171
|
+
footerLines,
|
|
172
|
+
palette: C,
|
|
156
173
|
beforeSections: [statusSection],
|
|
157
174
|
section: {
|
|
158
|
-
title:
|
|
175
|
+
title: `Decision History (${this.history.length})`,
|
|
159
176
|
scrollableLines: historyLines,
|
|
160
177
|
scrollOffset: Math.min(this.scrollOffset, Math.max(0, historyLines.length - 1)),
|
|
161
178
|
minRows: 8,
|
|
@@ -165,15 +182,13 @@ export class OpsStrategyPanel extends BasePanel {
|
|
|
165
182
|
|
|
166
183
|
return buildPanelWorkspace(width, height, {
|
|
167
184
|
title: ' Ops Strategy',
|
|
168
|
-
intro
|
|
185
|
+
intro,
|
|
169
186
|
sections: [
|
|
170
187
|
statusSection,
|
|
171
188
|
historySection.section,
|
|
172
189
|
],
|
|
173
|
-
footerLines
|
|
174
|
-
|
|
175
|
-
],
|
|
176
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
190
|
+
footerLines,
|
|
191
|
+
palette: C,
|
|
177
192
|
});
|
|
178
193
|
}
|
|
179
194
|
|
|
@@ -187,11 +202,10 @@ export class OpsStrategyPanel extends BasePanel {
|
|
|
187
202
|
|
|
188
203
|
private _renderHistory(width: number): Line[] {
|
|
189
204
|
if (this.history.length === 0) {
|
|
190
|
-
return [buildStyledPanelLine(width, [{ text: ' No history yet.', fg:
|
|
205
|
+
return [buildStyledPanelLine(width, [{ text: ' No history yet.', fg: C.dim, dim: true }])];
|
|
191
206
|
}
|
|
192
207
|
|
|
193
208
|
const lines: Line[] = [];
|
|
194
|
-
lines.push(buildStyledPanelLine(width, [{ text: ' Decision History', fg: DEFAULT_PANEL_PALETTE.value, bold: true }]));
|
|
195
209
|
|
|
196
210
|
const reversed = [...this.history].reverse();
|
|
197
211
|
for (let i = 0; i < reversed.length; i++) {
|
|
@@ -202,29 +216,33 @@ export class OpsStrategyPanel extends BasePanel {
|
|
|
202
216
|
const num = String(i + 1).padStart(3);
|
|
203
217
|
const overrideMark = d.overrideActive ? ' [O]' : '';
|
|
204
218
|
|
|
205
|
-
// Row 1: index + icon + strategy
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
219
|
+
// Row 1: index + icon + strategy on the left, timestamp right-aligned.
|
|
220
|
+
// buildAlignedRow keeps the timestamp flush-right even with wide glyphs.
|
|
221
|
+
const tsCol = getDisplayWidth(`${ts} `);
|
|
222
|
+
lines.push(buildAlignedRow(
|
|
223
|
+
width,
|
|
224
|
+
[
|
|
225
|
+
{ text: ` ${num}. ${icon} ${d.selected.toUpperCase()}${overrideMark}`, fg, bold: true },
|
|
226
|
+
{ text: `${ts} `, fg: C.dim, dim: true },
|
|
227
|
+
],
|
|
228
|
+
[
|
|
229
|
+
{ width: Math.max(0, width - tsCol - 1) },
|
|
230
|
+
{ width: tsCol, align: 'right' },
|
|
231
|
+
],
|
|
232
|
+
{ gap: 1 },
|
|
233
|
+
));
|
|
216
234
|
|
|
217
235
|
// Row 2: reason code
|
|
218
|
-
lines.push(buildStyledPanelLine(width, [{ text: ` ${d.reasonCode}`, fg:
|
|
236
|
+
lines.push(buildStyledPanelLine(width, [{ text: ` ${d.reasonCode}`, fg: C.dim, dim: true }]));
|
|
219
237
|
|
|
220
238
|
// Row 3+: top-2 scored candidates (auto mode only)
|
|
221
239
|
if (!d.overrideActive && d.candidates.length > 1) {
|
|
222
240
|
const top2 = d.candidates.slice(0, 2);
|
|
223
241
|
for (const c of top2) {
|
|
224
242
|
lines.push(buildStyledPanelLine(width, [
|
|
225
|
-
{ text: ' ', fg:
|
|
243
|
+
{ text: ' ', fg: C.dim, dim: true },
|
|
226
244
|
{ text: c.strategy.padEnd(12), fg: STRATEGY_FG[c.strategy] },
|
|
227
|
-
{ text: ` score ${c.score}`, fg:
|
|
245
|
+
{ text: ` score ${c.score}`, fg: C.dim, dim: true },
|
|
228
246
|
]));
|
|
229
247
|
}
|
|
230
248
|
}
|