@pellux/goodvibes-tui 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +124 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/context-usage.ts +53 -0
- package/src/core/conversation.ts +4 -6
- package/src/core/session-recovery.ts +2 -0
- package/src/core/turn-event-wiring.ts +1 -0
- package/src/input/commands/cost-runtime.ts +49 -0
- package/src/input/commands/eval.ts +4 -3
- package/src/input/commands/operator-runtime.ts +5 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-feed-routes.ts +60 -26
- package/src/input/handler-feed.ts +17 -8
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-shortcuts.ts +51 -0
- package/src/input/handler-ui-state.ts +4 -0
- package/src/input/handler.ts +43 -6
- package/src/input/keybindings.ts +53 -8
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +117 -1
- package/src/main.ts +5 -2
- package/src/panels/agent-inspector-panel.ts +139 -45
- package/src/panels/agent-inspector-shared.ts +147 -3
- package/src/panels/approval-panel.ts +204 -102
- package/src/panels/automation-control-panel.ts +370 -103
- package/src/panels/base-panel.ts +26 -2
- 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 +253 -75
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +159 -56
- package/src/panels/confirm-state.ts +8 -1
- package/src/panels/control-plane-panel.ts +354 -95
- package/src/panels/cost-tracker-panel.ts +243 -57
- package/src/panels/debug-panel.ts +347 -159
- package/src/panels/diff-panel.ts +257 -82
- package/src/panels/docs-panel.ts +166 -64
- package/src/panels/eval-panel.ts +336 -109
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +238 -159
- package/src/panels/file-preview-panel.ts +141 -59
- package/src/panels/git-panel.ts +360 -191
- package/src/panels/hooks-panel.ts +181 -19
- package/src/panels/incident-review-panel.ts +252 -43
- package/src/panels/index.ts +0 -4
- package/src/panels/intelligence-panel.ts +310 -103
- package/src/panels/knowledge-graph-panel.ts +480 -58
- package/src/panels/local-auth-panel.ts +251 -33
- package/src/panels/marketplace-panel.ts +219 -33
- package/src/panels/memory-panel.ts +83 -81
- package/src/panels/ops-control-panel.ts +211 -32
- package/src/panels/ops-strategy-panel.ts +131 -45
- package/src/panels/orchestration-panel.ts +259 -67
- package/src/panels/panel-list-panel.ts +184 -136
- package/src/panels/panel-manager.ts +120 -13
- package/src/panels/plan-dashboard-panel.ts +507 -74
- package/src/panels/plugins-panel.ts +227 -34
- package/src/panels/policy-panel.ts +264 -55
- package/src/panels/polish-core.ts +162 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +67 -149
- package/src/panels/project-planning-answer-actions.ts +134 -0
- package/src/panels/project-planning-panel.ts +84 -113
- package/src/panels/provider-health-panel.ts +438 -480
- 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 +136 -38
- package/src/panels/remote-panel.ts +123 -38
- package/src/panels/routes-panel.ts +87 -24
- package/src/panels/sandbox-panel.ts +232 -65
- package/src/panels/scrollable-list-panel.ts +143 -145
- package/src/panels/security-panel.ts +204 -52
- package/src/panels/services-panel.ts +174 -76
- package/src/panels/session-browser-panel.ts +101 -6
- package/src/panels/session-maintenance.ts +4 -122
- package/src/panels/settings-sync-panel.ts +346 -68
- package/src/panels/skills-panel.ts +157 -89
- package/src/panels/subscription-panel.ts +105 -34
- package/src/panels/symbol-outline-panel.ts +287 -154
- package/src/panels/system-messages-panel.ts +172 -38
- package/src/panels/tasks-panel.ts +348 -134
- package/src/panels/thinking-panel.ts +66 -32
- package/src/panels/token-budget-panel.ts +233 -46
- package/src/panels/tool-inspector-panel.ts +170 -54
- package/src/panels/types.ts +65 -4
- package/src/panels/work-plan-panel.ts +393 -39
- package/src/panels/worktree-panel.ts +239 -61
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +146 -150
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- package/src/renderer/conversation-overlays.ts +25 -11
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +22 -16
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +30 -11
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/overlay-box.ts +16 -10
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/process-indicator.ts +3 -1
- package/src/renderer/progress.ts +8 -6
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +41 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/tab-strip.ts +148 -34
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/ui-factory.ts +45 -36
- package/src/renderer/ui-primitives.ts +23 -2
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- package/src/utils/splash-lines.ts +44 -3
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +13 -0
- package/src/panels/agent-logs-panel.ts +0 -666
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/panels/context-visualizer-panel.ts +0 -214
- package/src/panels/forensics-panel.ts +0 -364
- package/src/panels/panel-picker.ts +0 -106
- package/src/panels/provider-account-snapshot.ts +0 -259
- package/src/panels/provider-accounts-panel.ts +0 -218
- package/src/panels/provider-stats-panel.ts +0 -366
- package/src/panels/schedule-panel.ts +0 -342
- package/src/panels/watchers-panel.ts +0 -193
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// AgentInspectorPanel — detailed view of a specific agent's messages and tool
|
|
3
|
-
// calls
|
|
3
|
+
// calls. WO-110: merged agent-logs-panel.ts in (pause/filter/follow/auto-
|
|
4
|
+
// select/stall-fix) on top of this panel's correct JSONL parser + cancel.
|
|
4
5
|
// ---------------------------------------------------------------------------
|
|
5
6
|
|
|
6
7
|
import { join } from 'node:path';
|
|
@@ -10,6 +11,8 @@ import { createEmptyLine } from '../types/grid.ts';
|
|
|
10
11
|
import { BasePanel } from './base-panel.ts';
|
|
11
12
|
import type { AgentManager, AgentRecord } from '@pellux/goodvibes-sdk/platform/tools';
|
|
12
13
|
import type { AgentMessageBus } from '@pellux/goodvibes-sdk/platform/agents';
|
|
14
|
+
import type { AgentEvent } from '@/runtime/index.ts';
|
|
15
|
+
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
13
16
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
14
17
|
import {
|
|
15
18
|
buildEmptyState,
|
|
@@ -20,22 +23,29 @@ import {
|
|
|
20
23
|
buildPanelWorkspace,
|
|
21
24
|
resolveScrollablePanelSection,
|
|
22
25
|
DEFAULT_PANEL_PALETTE,
|
|
26
|
+
extendPalette,
|
|
23
27
|
} from './polish.ts';
|
|
24
28
|
import {
|
|
25
29
|
type ConfirmState,
|
|
26
30
|
handleConfirmInput,
|
|
27
31
|
} from './confirm-state.ts';
|
|
28
32
|
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
33
|
+
import { abbreviateCount } from '../utils/format-number.ts';
|
|
29
34
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
30
35
|
import {
|
|
31
36
|
type AgentDisplayRow as DisplayRow,
|
|
32
37
|
type AgentInspectorEntryKind as EntryKind,
|
|
33
38
|
type AgentTimelineEntry as TimelineEntry,
|
|
39
|
+
type AgentInspectorFilterType as FilterType,
|
|
34
40
|
agentKindStyle,
|
|
35
|
-
agentStatusColor,
|
|
36
41
|
formatAgentDuration as formatMs,
|
|
37
42
|
formatAgentTime as shortTime,
|
|
38
43
|
jsonlToTimeline,
|
|
44
|
+
matchesInspectorFilter,
|
|
45
|
+
buildWrfcCostSegments,
|
|
46
|
+
subscribeAgentActivity,
|
|
47
|
+
AGENT_INSPECTOR_FILTER_CYCLE,
|
|
48
|
+
AGENT_INSPECTOR_FILTER_LABELS,
|
|
39
49
|
AGENT_TERMINAL_STATUSES,
|
|
40
50
|
AGENT_STALL_THRESHOLD_MS,
|
|
41
51
|
} from './agent-inspector-shared.ts';
|
|
@@ -47,12 +57,12 @@ import {
|
|
|
47
57
|
const REFRESH_MS = 500;
|
|
48
58
|
const MAX_JSONL_ENTRIES = 200;
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
// Domain accents only — base chrome (header/headerBg/label/value/dim/selectBg/
|
|
61
|
+
// warn) comes from DEFAULT_PANEL_PALETTE via extendPalette; the title band is
|
|
62
|
+
// the one canonical UI_TONES.bg.title, not a per-panel override.
|
|
63
|
+
const COLOR = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
64
|
+
// Agent selector chrome
|
|
54
65
|
statusBar: '#222233',
|
|
55
|
-
statusFg: '#aaaaaa',
|
|
56
66
|
separator: '#333355',
|
|
57
67
|
|
|
58
68
|
// Timeline roles
|
|
@@ -64,20 +74,10 @@ const COLOR = {
|
|
|
64
74
|
system: '#888888', // dim grey
|
|
65
75
|
timestamp: '#555577',
|
|
66
76
|
|
|
67
|
-
// Status badges
|
|
68
|
-
pending: '#ffcc44',
|
|
69
|
-
running: '#44ffcc',
|
|
70
|
-
completed: '#44ff88',
|
|
71
|
-
failed: '#ff4444',
|
|
72
|
-
cancelled: '#888888',
|
|
73
|
-
|
|
74
77
|
// Selector / labels
|
|
75
|
-
label: '#8888bb',
|
|
76
|
-
value: '#ccccdd',
|
|
77
78
|
selected: '#ffee88',
|
|
78
|
-
dimmed: '#555566',
|
|
79
79
|
expandHint: '#445566',
|
|
80
|
-
} as const;
|
|
80
|
+
} as const);
|
|
81
81
|
|
|
82
82
|
// ---------------------------------------------------------------------------
|
|
83
83
|
// AgentInspectorPanel
|
|
@@ -91,6 +91,8 @@ export interface AgentInspectorPanelDeps {
|
|
|
91
91
|
readonly workingDirectory: string;
|
|
92
92
|
/** Cancel the agent by id. Uses the same orphan-free path as WRFC. Returns true if cancelled. */
|
|
93
93
|
readonly cancelAgent: (agentId: string) => boolean;
|
|
94
|
+
/** Agent lifecycle feed; drives auto-select-on-spawn + event-driven stall detection (WO-110). */
|
|
95
|
+
readonly agentEvents: UiEventFeed<AgentEvent>;
|
|
94
96
|
/**
|
|
95
97
|
* Request a compositor repaint. The 500ms refresh timer and other async
|
|
96
98
|
* update paths call this (via markDirty) so live agent output is painted
|
|
@@ -127,8 +129,19 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
127
129
|
/** True while this panel is the active view — gates async repaint requests. */
|
|
128
130
|
private _active = false;
|
|
129
131
|
|
|
132
|
+
// ---- ported from agent-logs (WO-110 merge) -------------------------------
|
|
133
|
+
/** Freezes the 500ms JSONL refresh tick while true; manual navigation still works. */
|
|
134
|
+
private paused = false;
|
|
135
|
+
/** When true, cursor snaps to the newest row on every render (tail -f style). */
|
|
136
|
+
private autoFollow = true;
|
|
137
|
+
private filter: FilterType = 'all';
|
|
138
|
+
/** Per-agent last-activity timestamp derived from AGENT_* events — stall detection uses this, not startedAt. */
|
|
139
|
+
private readonly lastActivityByAgent = new Map<string, number>();
|
|
140
|
+
private unsubs: Array<() => void> = [];
|
|
141
|
+
|
|
130
142
|
constructor(private readonly deps: AgentInspectorPanelDeps) {
|
|
131
143
|
super('inspector', 'Inspector', 'I', 'agent');
|
|
144
|
+
this._subscribeAgentEvents();
|
|
132
145
|
}
|
|
133
146
|
|
|
134
147
|
// -------------------------------------------------------------------------
|
|
@@ -159,6 +172,7 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
159
172
|
this.scrollOffset = 0;
|
|
160
173
|
this.cursorIndex = 0;
|
|
161
174
|
this.timeline = [];
|
|
175
|
+
this.autoFollow = true;
|
|
162
176
|
this.markDirty();
|
|
163
177
|
this._refreshTimeline().catch((err) => { logger.debug('agent inspector timeline refresh failed', { err }); });
|
|
164
178
|
}
|
|
@@ -180,6 +194,8 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
180
194
|
|
|
181
195
|
override onDestroy(): void {
|
|
182
196
|
this._stopRefresh();
|
|
197
|
+
for (const unsub of this.unsubs) unsub();
|
|
198
|
+
this.unsubs = [];
|
|
183
199
|
super.onDestroy();
|
|
184
200
|
}
|
|
185
201
|
|
|
@@ -218,6 +234,10 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
218
234
|
case 'return': this._toggleExpand(); return true;
|
|
219
235
|
case 'tab': this._nextAgent(); return true;
|
|
220
236
|
case 'c': this._beginCancelConfirm(); return true;
|
|
237
|
+
case ' ': this._togglePause(); return true;
|
|
238
|
+
case 'f': this._cycleFilter(); return true;
|
|
239
|
+
case 'g': this._jumpTop(); return true;
|
|
240
|
+
case 'G': this._jumpBottom(); return true;
|
|
221
241
|
default: return false;
|
|
222
242
|
}
|
|
223
243
|
}
|
|
@@ -241,6 +261,10 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
241
261
|
[String(agents.length), DEFAULT_PANEL_PALETTE.value],
|
|
242
262
|
[' Selected ', DEFAULT_PANEL_PALETTE.label],
|
|
243
263
|
[this.selectedAgentId ? this.selectedAgentId.slice(-8) : 'none', this.selectedAgentId ? DEFAULT_PANEL_PALETTE.info : DEFAULT_PANEL_PALETTE.dim],
|
|
264
|
+
[' Filter ', DEFAULT_PANEL_PALETTE.label],
|
|
265
|
+
[AGENT_INSPECTOR_FILTER_LABELS[this.filter], DEFAULT_PANEL_PALETTE.info],
|
|
266
|
+
[' Mode ', DEFAULT_PANEL_PALETTE.label],
|
|
267
|
+
[this.paused ? 'paused' : this.autoFollow ? 'auto-follow' : 'manual', this.paused ? DEFAULT_PANEL_PALETTE.warn : this.autoFollow ? DEFAULT_PANEL_PALETTE.good : DEFAULT_PANEL_PALETTE.dim],
|
|
244
268
|
]),
|
|
245
269
|
];
|
|
246
270
|
|
|
@@ -256,9 +280,11 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
256
280
|
width,
|
|
257
281
|
agents.length === 0 ? ' No agents running' : ' No agent selected',
|
|
258
282
|
agents.length === 0
|
|
259
|
-
? 'Spawn an agent to inspect its live and historical timeline.'
|
|
283
|
+
? 'Spawn an agent to inspect its live and historical timeline, tool calls, and output.'
|
|
260
284
|
: 'Press Tab to cycle through available agents and open one in the inspector.',
|
|
261
|
-
|
|
285
|
+
agents.length === 0
|
|
286
|
+
? [{ command: '/spawn <task>', summary: 'launch an agent, then Tab here to inspect its timeline' }]
|
|
287
|
+
: [{ command: 'Tab', summary: 'select the first available agent' }],
|
|
262
288
|
DEFAULT_PANEL_PALETTE,
|
|
263
289
|
),
|
|
264
290
|
},
|
|
@@ -271,10 +297,12 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
271
297
|
}
|
|
272
298
|
|
|
273
299
|
summaryLines.push(this._renderAgentInfoSummary(width, rec));
|
|
300
|
+
const wrfcCostLine = this._renderWrfcCostLine(width, rec);
|
|
301
|
+
if (wrfcCostLine) summaryLines.push(wrfcCostLine);
|
|
274
302
|
const now = Date.now();
|
|
275
303
|
const isStalled = this._isAgentStalled(rec, now);
|
|
276
304
|
if (isStalled) {
|
|
277
|
-
summaryLines.push(buildPanelLine(width, [[' STALLED',
|
|
305
|
+
summaryLines.push(buildPanelLine(width, [[' STALLED', COLOR.warn], [' — no activity for 5+ minutes', DEFAULT_PANEL_PALETTE.dim]]));
|
|
278
306
|
}
|
|
279
307
|
const allRows = this._getCachedRows();
|
|
280
308
|
if (allRows.length === 0) {
|
|
@@ -288,20 +316,34 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
288
316
|
lines: buildEmptyState(
|
|
289
317
|
width,
|
|
290
318
|
' No messages yet',
|
|
291
|
-
|
|
292
|
-
|
|
319
|
+
rec.status === 'running'
|
|
320
|
+
? 'The selected agent is running but has not emitted visible timeline entries yet — live output streams in as it works.'
|
|
321
|
+
: 'The selected agent has not emitted any visible timeline entries.',
|
|
322
|
+
rec.status === 'running'
|
|
323
|
+
? [{ command: 'c', summary: 'cancel this running agent if it appears stalled' }]
|
|
324
|
+
: [{ command: 'Tab', summary: 'cycle to another agent with timeline activity' }],
|
|
293
325
|
DEFAULT_PANEL_PALETTE,
|
|
294
326
|
),
|
|
295
327
|
},
|
|
296
328
|
],
|
|
297
329
|
footerLines: [
|
|
298
|
-
buildPanelLine(width, [
|
|
330
|
+
buildPanelLine(width, [
|
|
331
|
+
[' Tab', DEFAULT_PANEL_PALETTE.info], [' cycle agents', DEFAULT_PANEL_PALETTE.dim],
|
|
332
|
+
[' Up/Down', DEFAULT_PANEL_PALETTE.info], [' navigate', DEFAULT_PANEL_PALETTE.dim],
|
|
333
|
+
[' Enter', DEFAULT_PANEL_PALETTE.info], [' expand', DEFAULT_PANEL_PALETTE.dim],
|
|
334
|
+
[' Space', DEFAULT_PANEL_PALETTE.info], [this.paused ? ' resume' : ' pause', DEFAULT_PANEL_PALETTE.dim],
|
|
335
|
+
[' f', DEFAULT_PANEL_PALETTE.info], [' filter', DEFAULT_PANEL_PALETTE.dim],
|
|
336
|
+
]),
|
|
299
337
|
],
|
|
300
338
|
palette: DEFAULT_PANEL_PALETTE,
|
|
301
339
|
});
|
|
302
340
|
}
|
|
303
341
|
|
|
304
|
-
|
|
342
|
+
if (this.autoFollow) {
|
|
343
|
+
this.cursorIndex = allRows.length - 1;
|
|
344
|
+
} else {
|
|
345
|
+
this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, allRows.length - 1));
|
|
346
|
+
}
|
|
305
347
|
const selectedRec = this.selectedAgentId
|
|
306
348
|
? this.deps.agentManager.getStatus(this.selectedAgentId)
|
|
307
349
|
: null;
|
|
@@ -332,6 +374,9 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
332
374
|
[' Tab', DEFAULT_PANEL_PALETTE.info], [' cycle agents', DEFAULT_PANEL_PALETTE.dim],
|
|
333
375
|
[' Up/Down', DEFAULT_PANEL_PALETTE.info], [' navigate', DEFAULT_PANEL_PALETTE.dim],
|
|
334
376
|
[' Enter', DEFAULT_PANEL_PALETTE.info], [' expand', DEFAULT_PANEL_PALETTE.dim],
|
|
377
|
+
[' Space', DEFAULT_PANEL_PALETTE.info], [this.paused ? ' resume' : ' pause', DEFAULT_PANEL_PALETTE.dim],
|
|
378
|
+
[' f', DEFAULT_PANEL_PALETTE.info], [' filter', DEFAULT_PANEL_PALETTE.dim],
|
|
379
|
+
[' g/G', DEFAULT_PANEL_PALETTE.info], [this.autoFollow ? ' top/follow' : ' top/bottom', DEFAULT_PANEL_PALETTE.dim],
|
|
335
380
|
[' c', cancelHintFg], [cancellable ? ' cancel' : ' cancel (n/a)', DEFAULT_PANEL_PALETTE.dim],
|
|
336
381
|
]);
|
|
337
382
|
|
|
@@ -373,17 +418,17 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
373
418
|
): Line {
|
|
374
419
|
if (agents.length === 0) {
|
|
375
420
|
return buildStyledPanelLine(width, [
|
|
376
|
-
{ text: ' (no agents)', fg: COLOR.
|
|
421
|
+
{ text: ' (no agents)', fg: COLOR.dim, bg: COLOR.statusBar, dim: true },
|
|
377
422
|
]);
|
|
378
423
|
}
|
|
379
|
-
const segments: StyledPanelSegment[] = [{ text: ' ', fg: COLOR.
|
|
424
|
+
const segments: StyledPanelSegment[] = [{ text: ' ', fg: COLOR.dim, bg: COLOR.statusBar }];
|
|
380
425
|
for (const agent of agents) {
|
|
381
426
|
const isSelected = agent.id === this.selectedAgentId;
|
|
382
427
|
const shortId = agent.id.slice(-8);
|
|
383
428
|
const badge = ` ${shortId} `;
|
|
384
429
|
segments.push({
|
|
385
430
|
text: badge,
|
|
386
|
-
fg: isSelected ? COLOR.selected : COLOR.
|
|
431
|
+
fg: isSelected ? COLOR.selected : COLOR.dim,
|
|
387
432
|
bg: COLOR.statusBar,
|
|
388
433
|
bold: isSelected,
|
|
389
434
|
});
|
|
@@ -397,9 +442,7 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
397
442
|
const elapsed = (rec.completedAt ?? now) - rec.startedAt;
|
|
398
443
|
const taskPreview = rec.task.split('\n')[0] ?? '';
|
|
399
444
|
const maxTask = Math.max(0, width - 40);
|
|
400
|
-
const taskDisplay = taskPreview
|
|
401
|
-
? taskPreview.slice(0, maxTask - 1) + '\u2026'
|
|
402
|
-
: taskPreview;
|
|
445
|
+
const taskDisplay = truncateDisplay(taskPreview, maxTask);
|
|
403
446
|
return buildPanelLine(width, [
|
|
404
447
|
[' Status ', DEFAULT_PANEL_PALETTE.label],
|
|
405
448
|
[rec.status.toUpperCase(), rec.status === 'running' ? DEFAULT_PANEL_PALETTE.good : rec.status === 'failed' ? DEFAULT_PANEL_PALETTE.bad : DEFAULT_PANEL_PALETTE.dim],
|
|
@@ -416,6 +459,12 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
416
459
|
]);
|
|
417
460
|
}
|
|
418
461
|
|
|
462
|
+
/** WRFC badge + per-agent tokens/cost (WO-110); null when neither applies. */
|
|
463
|
+
private _renderWrfcCostLine(width: number, rec: AgentRecord): Line | null {
|
|
464
|
+
const segments = buildWrfcCostSegments(rec, DEFAULT_PANEL_PALETTE, abbreviateCount);
|
|
465
|
+
return segments ? buildPanelLine(width, segments) : null;
|
|
466
|
+
}
|
|
467
|
+
|
|
419
468
|
// -------------------------------------------------------------------------
|
|
420
469
|
// Timeline row rendering
|
|
421
470
|
// -------------------------------------------------------------------------
|
|
@@ -425,7 +474,7 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
425
474
|
row: DisplayRow,
|
|
426
475
|
isCursor: boolean,
|
|
427
476
|
): Line {
|
|
428
|
-
const bg = isCursor ?
|
|
477
|
+
const bg = isCursor ? COLOR.selectBg : '';
|
|
429
478
|
const ts = shortTime(row.timestamp);
|
|
430
479
|
const { fg, prefix } = agentKindStyle(row.kind, COLOR);
|
|
431
480
|
const hint = row.hasDetail ? (row.expanded ? ' ▾' : ' ▸') : '';
|
|
@@ -450,10 +499,7 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
450
499
|
// Private — data
|
|
451
500
|
// -------------------------------------------------------------------------
|
|
452
501
|
|
|
453
|
-
/**
|
|
454
|
-
* Build the flat list of DisplayRow items from timeline + expanded detail
|
|
455
|
-
* sub-rows. This is what the renderer walks.
|
|
456
|
-
*/
|
|
502
|
+
/** Flat DisplayRow list from timeline + expanded detail sub-rows, filtered by `this.filter`. */
|
|
457
503
|
private _buildVisibleRows(): DisplayRow[] {
|
|
458
504
|
const rows: DisplayRow[] = [];
|
|
459
505
|
|
|
@@ -475,10 +521,10 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
475
521
|
|
|
476
522
|
merged.sort((a, b) => a.timestamp - b.timestamp);
|
|
477
523
|
|
|
478
|
-
//
|
|
479
|
-
// approximate timestamps. We just show all — bus msgs tend to have
|
|
480
|
-
// unique content.
|
|
524
|
+
// Entries are filtered against `this.filter` here (WO-110).
|
|
481
525
|
for (const entry of merged) {
|
|
526
|
+
if (!matchesInspectorFilter(entry.kind, this.filter)) continue;
|
|
527
|
+
|
|
482
528
|
rows.push({
|
|
483
529
|
kind: entry.kind,
|
|
484
530
|
timestamp: entry.timestamp,
|
|
@@ -527,9 +573,7 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
527
573
|
kind,
|
|
528
574
|
timestamp: msg.timestamp,
|
|
529
575
|
label: msg.from,
|
|
530
|
-
content: msg.content
|
|
531
|
-
? msg.content.slice(0, 197) + '\u2026'
|
|
532
|
-
: msg.content,
|
|
576
|
+
content: truncateDisplay(msg.content, 200),
|
|
533
577
|
expanded: false,
|
|
534
578
|
} satisfies TimelineEntry);
|
|
535
579
|
}
|
|
@@ -610,6 +654,9 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
610
654
|
private _startRefresh(): void {
|
|
611
655
|
if (this.refreshTimerId) return;
|
|
612
656
|
this.refreshTimerId = this.registerTimer(setInterval(() => {
|
|
657
|
+
// WO-110: Space pause/resume freezes the JSONL tail (ported from
|
|
658
|
+
// agent-logs); manual navigation and agent switching still work.
|
|
659
|
+
if (this.paused) return;
|
|
613
660
|
this._refreshTimeline().catch((err) => { logger.debug('agent inspector timeline refresh tick failed', { err }); });
|
|
614
661
|
}, REFRESH_MS));
|
|
615
662
|
}
|
|
@@ -621,6 +668,51 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
621
668
|
}
|
|
622
669
|
}
|
|
623
670
|
|
|
671
|
+
// -------------------------------------------------------------------------
|
|
672
|
+
// Private — agent lifecycle event subscriptions (WO-110 merge)
|
|
673
|
+
// -------------------------------------------------------------------------
|
|
674
|
+
|
|
675
|
+
/** Auto-select-on-spawn + real event-driven last-activity (stall fix), ported from agent-logs. */
|
|
676
|
+
private _subscribeAgentEvents(): void {
|
|
677
|
+
this.unsubs.push(...subscribeAgentActivity(this.deps.agentEvents, {
|
|
678
|
+
onActivity: (agentId) => {
|
|
679
|
+
this.lastActivityByAgent.set(agentId, Date.now());
|
|
680
|
+
if (!this.paused && agentId === this.selectedAgentId) this.markDirty();
|
|
681
|
+
},
|
|
682
|
+
onSpawn: (agentId) => {
|
|
683
|
+
const current = this.selectedAgentId ? this.deps.agentManager.getStatus(this.selectedAgentId) : null;
|
|
684
|
+
if (!current || AGENT_TERMINAL_STATUSES.has(current.status)) this.inspectAgent(agentId);
|
|
685
|
+
},
|
|
686
|
+
onTerminal: (agentId) => {
|
|
687
|
+
this.lastActivityByAgent.delete(agentId);
|
|
688
|
+
this.markDirty();
|
|
689
|
+
},
|
|
690
|
+
}));
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
private _togglePause(): void {
|
|
694
|
+
this.paused = !this.paused;
|
|
695
|
+
this.markDirty();
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
private _cycleFilter(): void {
|
|
699
|
+
const idx = AGENT_INSPECTOR_FILTER_CYCLE.indexOf(this.filter);
|
|
700
|
+
this.filter = AGENT_INSPECTOR_FILTER_CYCLE[(idx + 1) % AGENT_INSPECTOR_FILTER_CYCLE.length]!;
|
|
701
|
+
this.cursorIndex = 0;
|
|
702
|
+
this.markDirty();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
private _jumpTop(): void {
|
|
706
|
+
this.autoFollow = false;
|
|
707
|
+
this.cursorIndex = 0;
|
|
708
|
+
this.markDirty();
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
private _jumpBottom(): void {
|
|
712
|
+
this.autoFollow = true;
|
|
713
|
+
this.markDirty();
|
|
714
|
+
}
|
|
715
|
+
|
|
624
716
|
// -------------------------------------------------------------------------
|
|
625
717
|
// Private — navigation
|
|
626
718
|
// -------------------------------------------------------------------------
|
|
@@ -669,15 +761,17 @@ export class AgentInspectorPanel extends BasePanel {
|
|
|
669
761
|
if (!this.selectedAgentId) return;
|
|
670
762
|
const rec = this.deps.agentManager.getStatus(this.selectedAgentId);
|
|
671
763
|
if (!rec || AGENT_TERMINAL_STATUSES.has(rec.status)) return;
|
|
672
|
-
const
|
|
764
|
+
const firstLine = rec.task.split('\n')[0];
|
|
765
|
+
const label = firstLine ? truncateDisplay(firstLine, 40) : rec.id.slice(-8);
|
|
673
766
|
this.confirmCancel = { subject: rec.id, label };
|
|
674
767
|
this.markDirty();
|
|
675
768
|
}
|
|
676
769
|
|
|
677
|
-
/**
|
|
770
|
+
/** Stalled = non-terminal + no AGENT_* activity for AGENT_STALL_THRESHOLD_MS (not just elapsed-since-start). */
|
|
678
771
|
private _isAgentStalled(rec: AgentRecord, now: number): boolean {
|
|
679
772
|
if (AGENT_TERMINAL_STATUSES.has(rec.status)) return false;
|
|
680
|
-
|
|
773
|
+
const lastActivity = this.lastActivityByAgent.get(rec.id) ?? rec.startedAt;
|
|
774
|
+
return (now - lastActivity) >= AGENT_STALL_THRESHOLD_MS;
|
|
681
775
|
}
|
|
682
776
|
|
|
683
777
|
/**
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { formatDuration } from '../utils/format-duration.ts';
|
|
2
|
+
import { calcSessionCost } from '../export/cost-utils.ts';
|
|
3
|
+
import type { AgentEvent } from '@/runtime/index.ts';
|
|
4
|
+
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
5
|
+
|
|
1
6
|
export type AgentInspectorEntryKind = 'user' | 'assistant' | 'tool_call' | 'tool_result' | 'session' | 'error';
|
|
2
7
|
|
|
3
8
|
// ---------------------------------------------------------------------------
|
|
@@ -61,9 +66,7 @@ export function agentStatusColor(status: string, colors: Record<string, string>)
|
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
export function formatAgentDuration(ms: number): string {
|
|
64
|
-
|
|
65
|
-
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
66
|
-
return `${Math.floor(ms / 60000)}m${Math.floor((ms % 60000) / 1000)}s`;
|
|
69
|
+
return formatDuration(ms);
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
export function formatAgentTime(ts: number): string {
|
|
@@ -121,3 +124,144 @@ export function agentKindStyle(kind: AgentInspectorEntryKind, colors: Record<str
|
|
|
121
124
|
default: return { fg: colors.dimmed ?? colors.system, prefix: '[?] ' };
|
|
122
125
|
}
|
|
123
126
|
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Inspector filter modes (ported from the merged agent-logs console — WO-110)
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
export type AgentInspectorFilterType = 'all' | 'assistant' | 'tool' | 'error';
|
|
133
|
+
|
|
134
|
+
export const AGENT_INSPECTOR_FILTER_CYCLE: readonly AgentInspectorFilterType[] = ['all', 'assistant', 'tool', 'error'];
|
|
135
|
+
|
|
136
|
+
export const AGENT_INSPECTOR_FILTER_LABELS: Record<AgentInspectorFilterType, string> = {
|
|
137
|
+
all: 'All',
|
|
138
|
+
assistant: 'Assistant',
|
|
139
|
+
tool: 'Tool',
|
|
140
|
+
error: 'Error',
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* True when a timeline entry's kind should be visible under the given filter.
|
|
145
|
+
* Mapped against the REAL row kinds jsonlToTimeline() produces (tool_call /
|
|
146
|
+
* tool_result), not the legacy agent-logs-shared schema (assistant/tool_call/
|
|
147
|
+
* tool_result vs. the writer-emitted tool_execution/llm_response) that never
|
|
148
|
+
* matched anything.
|
|
149
|
+
*/
|
|
150
|
+
export function matchesInspectorFilter(kind: AgentInspectorEntryKind, filter: AgentInspectorFilterType): boolean {
|
|
151
|
+
switch (filter) {
|
|
152
|
+
case 'all': return true;
|
|
153
|
+
case 'assistant': return kind === 'assistant';
|
|
154
|
+
case 'tool': return kind === 'tool_call' || kind === 'tool_result';
|
|
155
|
+
case 'error': return kind === 'error';
|
|
156
|
+
default: return true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Per-agent cost/token summary (calcSessionCost, cost-utils.ts)
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
export function formatAgentCost(usd: number): string {
|
|
165
|
+
if (usd === 0) return '$0.00';
|
|
166
|
+
if (usd < 0.0001) return '<$0.0001';
|
|
167
|
+
if (usd < 0.01) return `$${usd.toFixed(4)}`;
|
|
168
|
+
return `$${usd.toFixed(3)}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface AgentUsageSummary {
|
|
172
|
+
readonly tokens: number;
|
|
173
|
+
readonly cost: number;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
interface AgentUsageLike {
|
|
177
|
+
readonly usage?: {
|
|
178
|
+
readonly inputTokens: number;
|
|
179
|
+
readonly outputTokens: number;
|
|
180
|
+
readonly cacheReadTokens?: number;
|
|
181
|
+
readonly cacheWriteTokens?: number;
|
|
182
|
+
} | null;
|
|
183
|
+
readonly model?: string | null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Total token count + cost for an agent record's usage, or null when no usage
|
|
188
|
+
* data has landed yet (honest-UX: never fabricate a $0.00/0-token reading for
|
|
189
|
+
* an agent that simply hasn't reported usage).
|
|
190
|
+
*/
|
|
191
|
+
export function summarizeAgentUsage(rec: AgentUsageLike): AgentUsageSummary | null {
|
|
192
|
+
if (!rec.usage) return null;
|
|
193
|
+
const inputTokens = rec.usage.inputTokens + (rec.usage.cacheReadTokens ?? 0) + (rec.usage.cacheWriteTokens ?? 0);
|
|
194
|
+
const cost = calcSessionCost(
|
|
195
|
+
rec.usage.inputTokens,
|
|
196
|
+
rec.usage.outputTokens,
|
|
197
|
+
rec.usage.cacheReadTokens ?? 0,
|
|
198
|
+
rec.usage.cacheWriteTokens ?? 0,
|
|
199
|
+
rec.model ?? 'unknown',
|
|
200
|
+
);
|
|
201
|
+
return { tokens: inputTokens + rec.usage.outputTokens, cost };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// WRFC badge segments (AgentRecord.wrfcId/wrfcRole)
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
interface WrfcLike {
|
|
209
|
+
readonly wrfcId?: string | null;
|
|
210
|
+
readonly wrfcRole?: string | null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Segments for the WRFC chain badge + token/cost line, or null when neither applies. */
|
|
214
|
+
export function buildWrfcCostSegments(
|
|
215
|
+
rec: WrfcLike & AgentUsageLike,
|
|
216
|
+
palette: { readonly label: string; readonly info: string },
|
|
217
|
+
formatTokens: (n: number) => string,
|
|
218
|
+
): Array<[string, string]> | null {
|
|
219
|
+
const segments: Array<[string, string]> = [];
|
|
220
|
+
if (rec.wrfcId) {
|
|
221
|
+
segments.push([' WRFC ', palette.label]);
|
|
222
|
+
segments.push([`${rec.wrfcRole ?? 'agent'} · ${rec.wrfcId.slice(-8)}`, palette.info]);
|
|
223
|
+
}
|
|
224
|
+
const usage = summarizeAgentUsage(rec);
|
|
225
|
+
if (usage) {
|
|
226
|
+
segments.push([segments.length > 0 ? ' Tokens ' : ' Tokens ', palette.label]);
|
|
227
|
+
segments.push([formatTokens(usage.tokens), palette.info]);
|
|
228
|
+
segments.push([' Cost ', palette.label]);
|
|
229
|
+
segments.push([formatAgentCost(usage.cost), palette.info]);
|
|
230
|
+
}
|
|
231
|
+
return segments.length > 0 ? segments : null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// Agent lifecycle event subscriptions (ported from agent-logs — WO-110)
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
export interface AgentActivityHooks {
|
|
239
|
+
/** Any AGENT_* activity event for this agent (running/progress/stream/awaiting/finalizing/spawning). */
|
|
240
|
+
readonly onActivity: (agentId: string) => void;
|
|
241
|
+
/** AGENT_SPAWNING only — used for auto-select-newest-agent. */
|
|
242
|
+
readonly onSpawn: (agentId: string) => void;
|
|
243
|
+
/** AGENT_COMPLETED/FAILED/CANCELLED. */
|
|
244
|
+
readonly onTerminal: (agentId: string) => void;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Wires the standard set of agent lifecycle listeners; returns their unsubscribe functions. */
|
|
248
|
+
export function subscribeAgentActivity(agentEvents: UiEventFeed<AgentEvent>, hooks: AgentActivityHooks): Array<() => void> {
|
|
249
|
+
const unsubs: Array<() => void> = [];
|
|
250
|
+
const activityTypes = [
|
|
251
|
+
'AGENT_RUNNING', 'AGENT_PROGRESS', 'AGENT_STREAM_DELTA',
|
|
252
|
+
'AGENT_AWAITING_MESSAGE', 'AGENT_AWAITING_TOOL', 'AGENT_FINALIZING',
|
|
253
|
+
] as const;
|
|
254
|
+
for (const type of activityTypes) {
|
|
255
|
+
unsubs.push(agentEvents.on(type, (payload: { agentId: string }) => hooks.onActivity(payload.agentId)));
|
|
256
|
+
}
|
|
257
|
+
unsubs.push(agentEvents.on('AGENT_SPAWNING', (payload) => {
|
|
258
|
+
hooks.onActivity(payload.agentId);
|
|
259
|
+
hooks.onSpawn(payload.agentId);
|
|
260
|
+
}));
|
|
261
|
+
unsubs.push(
|
|
262
|
+
agentEvents.on('AGENT_COMPLETED', (payload) => hooks.onTerminal(payload.agentId)),
|
|
263
|
+
agentEvents.on('AGENT_FAILED', (payload) => hooks.onTerminal(payload.agentId)),
|
|
264
|
+
agentEvents.on('AGENT_CANCELLED', (payload) => hooks.onTerminal(payload.agentId)),
|
|
265
|
+
);
|
|
266
|
+
return unsubs;
|
|
267
|
+
}
|