@pellux/goodvibes-agent 0.1.102 → 0.1.104
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 +14 -0
- package/README.md +10 -0
- package/docs/README.md +1 -1
- package/docs/getting-started.md +17 -3
- package/package.json +1 -1
- package/src/agent/memory-safety.ts +16 -0
- package/src/cli/help.ts +86 -0
- package/src/cli/local-library-command.ts +516 -0
- package/src/cli/management.ts +17 -0
- package/src/cli/memory-command.ts +630 -0
- package/src/cli/package-verification.ts +10 -0
- package/src/cli/parser.ts +8 -0
- package/src/cli/types.ts +3 -0
- package/src/input/agent-workspace-activation.ts +170 -0
- package/src/input/agent-workspace-categories.ts +8 -1
- package/src/input/agent-workspace-editors.ts +36 -0
- package/src/input/agent-workspace-memory-editor.ts +88 -0
- package/src/input/agent-workspace-setup.ts +7 -5
- package/src/input/agent-workspace-snapshot.ts +40 -4
- package/src/input/agent-workspace-token.ts +51 -0
- package/src/input/agent-workspace-types.ts +13 -3
- package/src/input/agent-workspace.ts +130 -185
- package/src/input/feed-context-factory.ts +1 -3
- package/src/input/handler-feed.ts +1 -4
- package/src/input/handler-interactions.ts +0 -1
- package/src/input/handler-modal-stack.ts +0 -1
- package/src/input/handler-modal-token-routes.ts +0 -11
- package/src/input/handler-picker-routes.ts +11 -20
- package/src/input/handler-ui-state.ts +0 -6
- package/src/input/handler.ts +1 -17
- package/src/main.ts +0 -6
- package/src/panels/builtin/agent.ts +0 -17
- package/src/panels/index.ts +0 -2
- package/src/renderer/agent-workspace.ts +8 -3
- package/src/renderer/conversation-overlays.ts +0 -6
- package/src/renderer/live-tail-modal.ts +10 -69
- package/src/renderer/process-modal.ts +28 -530
- package/src/runtime/bootstrap-core.ts +1 -1
- package/src/runtime/services.ts +3 -4
- package/src/tools/{wrfc-agent-guard.ts → agent-tool-policy-guard.ts} +0 -6
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -521
- package/src/panels/agent-inspector-shared.ts +0 -94
- package/src/panels/agent-logs-panel.ts +0 -559
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/renderer/agent-detail-modal.ts +0 -331
- package/src/renderer/process-summary.ts +0 -67
|
@@ -782,9 +782,3 @@ function narrowStringEnumProperty(
|
|
|
782
782
|
property.enum = [...values];
|
|
783
783
|
property.description = description;
|
|
784
784
|
}
|
|
785
|
-
|
|
786
|
-
// Compatibility exports for inherited terminal-foundation tests/imports.
|
|
787
|
-
export const installWrfcAgentToolGuard = installAgentToolPolicyGuard;
|
|
788
|
-
export const wrapWrfcAgentTool = wrapAgentToolForAgentPolicy;
|
|
789
|
-
export const validateWrfcAgentToolInvocation = validateAgentToolInvocationForAgentPolicy;
|
|
790
|
-
export const normalizeWrfcAgentToolInvocation = normalizeAgentToolInvocationForAgentPolicy;
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '0.1.
|
|
9
|
+
let _version = '0.1.104';
|
|
10
10
|
let _sdkVersion = '0.33.35';
|
|
11
11
|
try {
|
|
12
12
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {
|
|
@@ -1,521 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// AgentInspectorPanel — detailed view of a specific agent's messages and tool
|
|
3
|
-
// calls, with expandable tool call details, scroll, and agent selector.
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
import { readFile } from 'node:fs/promises';
|
|
8
|
-
import type { Line } from '../types/grid.ts';
|
|
9
|
-
import { createEmptyLine } from '../types/grid.ts';
|
|
10
|
-
import { BasePanel } from './base-panel.ts';
|
|
11
|
-
import type { AgentManager, AgentRecord } from '@pellux/goodvibes-sdk/platform/tools';
|
|
12
|
-
import type { AgentMessageBus } from '@pellux/goodvibes-sdk/platform/agents';
|
|
13
|
-
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
14
|
-
import {
|
|
15
|
-
buildEmptyState,
|
|
16
|
-
buildPanelLine,
|
|
17
|
-
buildSelectablePanelLine,
|
|
18
|
-
type StyledPanelSegment,
|
|
19
|
-
buildStyledPanelLine,
|
|
20
|
-
buildPanelWorkspace,
|
|
21
|
-
resolveScrollablePanelSection,
|
|
22
|
-
DEFAULT_PANEL_PALETTE,
|
|
23
|
-
} from './polish.ts';
|
|
24
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
25
|
-
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
26
|
-
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
27
|
-
import {
|
|
28
|
-
type AgentDisplayRow as DisplayRow,
|
|
29
|
-
type AgentInspectorEntryKind as EntryKind,
|
|
30
|
-
type AgentTimelineEntry as TimelineEntry,
|
|
31
|
-
agentKindStyle,
|
|
32
|
-
agentStatusColor,
|
|
33
|
-
formatAgentDuration as formatMs,
|
|
34
|
-
formatAgentTime as shortTime,
|
|
35
|
-
jsonlToTimeline,
|
|
36
|
-
} from './agent-inspector-shared.ts';
|
|
37
|
-
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
// Constants & colour palette
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
|
|
42
|
-
const REFRESH_MS = 500;
|
|
43
|
-
const MAX_JSONL_ENTRIES = 200;
|
|
44
|
-
|
|
45
|
-
const COLOR = {
|
|
46
|
-
// Header / chrome
|
|
47
|
-
headerBg: '#1a1a2e',
|
|
48
|
-
headerFg: '#ffffff',
|
|
49
|
-
statusBar: '#222233',
|
|
50
|
-
statusFg: '#aaaaaa',
|
|
51
|
-
separator: '#333355',
|
|
52
|
-
|
|
53
|
-
// Timeline roles
|
|
54
|
-
user: '#cc88ff', // purple
|
|
55
|
-
assistant: '#ffffff', // white
|
|
56
|
-
tool: '#00ccff', // cyan
|
|
57
|
-
toolResult: '#66ddff', // light cyan
|
|
58
|
-
error: '#ff6666', // red
|
|
59
|
-
system: '#888888', // dim grey
|
|
60
|
-
timestamp: '#555577',
|
|
61
|
-
|
|
62
|
-
// Status badges
|
|
63
|
-
pending: '#ffcc44',
|
|
64
|
-
running: '#44ffcc',
|
|
65
|
-
completed: '#44ff88',
|
|
66
|
-
failed: '#ff4444',
|
|
67
|
-
cancelled: '#888888',
|
|
68
|
-
|
|
69
|
-
// Selector / labels
|
|
70
|
-
label: '#8888bb',
|
|
71
|
-
value: '#ccccdd',
|
|
72
|
-
selected: '#ffee88',
|
|
73
|
-
dimmed: '#555566',
|
|
74
|
-
expandHint: '#445566',
|
|
75
|
-
} as const;
|
|
76
|
-
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
// AgentInspectorPanel
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
export interface AgentInspectorPanelDeps {
|
|
82
|
-
readonly agentManager: Pick<AgentManager, 'list' | 'getStatus'>;
|
|
83
|
-
readonly agentMessageBus: Pick<AgentMessageBus, 'getMessages'>;
|
|
84
|
-
readonly workingDirectory: string;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export class AgentInspectorPanel extends BasePanel {
|
|
88
|
-
// The agent currently being inspected
|
|
89
|
-
private selectedAgentId: string | null = null;
|
|
90
|
-
|
|
91
|
-
// Flattened timeline for the selected agent
|
|
92
|
-
private timeline: TimelineEntry[] = [];
|
|
93
|
-
|
|
94
|
-
// Scroll state
|
|
95
|
-
private scrollOffset = 0;
|
|
96
|
-
|
|
97
|
-
// Cursor index for expand/collapse
|
|
98
|
-
private cursorIndex = 0;
|
|
99
|
-
|
|
100
|
-
// Refresh timer (active only while panel is active)
|
|
101
|
-
private refreshTimerId: ReturnType<typeof setInterval> | null = null;
|
|
102
|
-
|
|
103
|
-
// Row cache — cleared on markDirty(), computed once per render cycle
|
|
104
|
-
private _cachedRows: DisplayRow[] | null = null;
|
|
105
|
-
|
|
106
|
-
constructor(private readonly deps: AgentInspectorPanelDeps) {
|
|
107
|
-
super('inspector', 'Inspector', 'I', 'agent');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// -------------------------------------------------------------------------
|
|
111
|
-
// Row cache
|
|
112
|
-
// -------------------------------------------------------------------------
|
|
113
|
-
|
|
114
|
-
private _getCachedRows(): DisplayRow[] {
|
|
115
|
-
if (!this._cachedRows) this._cachedRows = this._buildVisibleRows();
|
|
116
|
-
return this._cachedRows;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
override markDirty(): void {
|
|
120
|
-
this._cachedRows = null;
|
|
121
|
-
super.markDirty();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// -------------------------------------------------------------------------
|
|
125
|
-
// Public API
|
|
126
|
-
// -------------------------------------------------------------------------
|
|
127
|
-
|
|
128
|
-
/** Focus this panel on a specific agent by ID. */
|
|
129
|
-
inspectAgent(agentId: string): void {
|
|
130
|
-
this.selectedAgentId = agentId;
|
|
131
|
-
this.scrollOffset = 0;
|
|
132
|
-
this.cursorIndex = 0;
|
|
133
|
-
this.timeline = [];
|
|
134
|
-
this.markDirty();
|
|
135
|
-
this._refreshTimeline().catch((err) => { logger.debug('agent inspector timeline refresh failed', { err }); });
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// -------------------------------------------------------------------------
|
|
139
|
-
// Lifecycle
|
|
140
|
-
// -------------------------------------------------------------------------
|
|
141
|
-
|
|
142
|
-
override onActivate(): void {
|
|
143
|
-
this.needsRender = true;
|
|
144
|
-
this._startRefresh();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
override onDeactivate(): void {
|
|
148
|
-
this._stopRefresh();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
override onDestroy(): void {
|
|
152
|
-
this._stopRefresh();
|
|
153
|
-
super.onDestroy();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// -------------------------------------------------------------------------
|
|
157
|
-
// Input
|
|
158
|
-
// -------------------------------------------------------------------------
|
|
159
|
-
|
|
160
|
-
handleInput(key: string): boolean {
|
|
161
|
-
switch (key) {
|
|
162
|
-
case 'up': this._moveCursor(-1); return true;
|
|
163
|
-
case 'down': this._moveCursor(1); return true;
|
|
164
|
-
case 'pageup': this._scroll(-10); return true;
|
|
165
|
-
case 'pagedown': this._scroll(10); return true;
|
|
166
|
-
case 'return': this._toggleExpand(); return true;
|
|
167
|
-
case 'tab': this._nextAgent(); return true;
|
|
168
|
-
default: return false;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// -------------------------------------------------------------------------
|
|
173
|
-
// Render
|
|
174
|
-
// -------------------------------------------------------------------------
|
|
175
|
-
|
|
176
|
-
render(width: number, height: number): Line[] {
|
|
177
|
-
if (height <= 0 || width <= 0) return [];
|
|
178
|
-
|
|
179
|
-
const manager = this.deps.agentManager;
|
|
180
|
-
const agents = manager.list();
|
|
181
|
-
const rec = this.selectedAgentId
|
|
182
|
-
? manager.getStatus(this.selectedAgentId)
|
|
183
|
-
: null;
|
|
184
|
-
const selectorLine = this._renderSelector(width, agents);
|
|
185
|
-
const summaryLines = [
|
|
186
|
-
buildPanelLine(width, [
|
|
187
|
-
[' Agents ', DEFAULT_PANEL_PALETTE.label],
|
|
188
|
-
[String(agents.length), DEFAULT_PANEL_PALETTE.value],
|
|
189
|
-
[' Selected ', DEFAULT_PANEL_PALETTE.label],
|
|
190
|
-
[this.selectedAgentId ? this.selectedAgentId.slice(-8) : 'none', this.selectedAgentId ? DEFAULT_PANEL_PALETTE.info : DEFAULT_PANEL_PALETTE.dim],
|
|
191
|
-
]),
|
|
192
|
-
];
|
|
193
|
-
|
|
194
|
-
if (!rec) {
|
|
195
|
-
return buildPanelWorkspace(width, height, {
|
|
196
|
-
title: ` Inspector [${agents.length} agent${agents.length !== 1 ? 's' : ''}]`,
|
|
197
|
-
intro: 'Inspect a selected agent timeline, tool activity, expanded details, and live/historical message flow.',
|
|
198
|
-
sections: [
|
|
199
|
-
{ title: 'Summary', lines: summaryLines },
|
|
200
|
-
{ title: 'Agents', lines: [selectorLine] },
|
|
201
|
-
{
|
|
202
|
-
lines: buildEmptyState(
|
|
203
|
-
width,
|
|
204
|
-
agents.length === 0 ? ' No agents running' : ' No agent selected',
|
|
205
|
-
agents.length === 0
|
|
206
|
-
? 'Spawn an agent to inspect its live and historical timeline.'
|
|
207
|
-
: 'Press Tab to cycle through available agents and open one in the inspector.',
|
|
208
|
-
[],
|
|
209
|
-
DEFAULT_PANEL_PALETTE,
|
|
210
|
-
),
|
|
211
|
-
},
|
|
212
|
-
],
|
|
213
|
-
footerLines: [
|
|
214
|
-
buildPanelLine(width, [[' Tab', DEFAULT_PANEL_PALETTE.info], [' cycle agents', DEFAULT_PANEL_PALETTE.dim], [' Up/Down', DEFAULT_PANEL_PALETTE.info], [' navigate', DEFAULT_PANEL_PALETTE.dim], [' Enter', DEFAULT_PANEL_PALETTE.info], [' expand', DEFAULT_PANEL_PALETTE.dim]]),
|
|
215
|
-
],
|
|
216
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
summaryLines.push(this._renderAgentInfoSummary(width, rec));
|
|
221
|
-
const allRows = this._getCachedRows();
|
|
222
|
-
if (allRows.length === 0) {
|
|
223
|
-
return buildPanelWorkspace(width, height, {
|
|
224
|
-
title: ` Inspector [${agents.length} agent${agents.length !== 1 ? 's' : ''}]`,
|
|
225
|
-
intro: 'Inspect a selected agent timeline, tool activity, expanded details, and live/historical message flow.',
|
|
226
|
-
sections: [
|
|
227
|
-
{ title: 'Summary', lines: summaryLines },
|
|
228
|
-
{ title: 'Agents', lines: [selectorLine] },
|
|
229
|
-
{
|
|
230
|
-
lines: buildEmptyState(
|
|
231
|
-
width,
|
|
232
|
-
' No messages yet',
|
|
233
|
-
'The selected agent has not emitted any visible timeline entries yet.',
|
|
234
|
-
[],
|
|
235
|
-
DEFAULT_PANEL_PALETTE,
|
|
236
|
-
),
|
|
237
|
-
},
|
|
238
|
-
],
|
|
239
|
-
footerLines: [
|
|
240
|
-
buildPanelLine(width, [[' Tab', DEFAULT_PANEL_PALETTE.info], [' cycle agents', DEFAULT_PANEL_PALETTE.dim], [' Up/Down', DEFAULT_PANEL_PALETTE.info], [' navigate', DEFAULT_PANEL_PALETTE.dim], [' Enter', DEFAULT_PANEL_PALETTE.info], [' expand', DEFAULT_PANEL_PALETTE.dim]]),
|
|
241
|
-
],
|
|
242
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, allRows.length - 1));
|
|
247
|
-
const summarySection = { title: 'Summary', lines: summaryLines } as const;
|
|
248
|
-
const agentsSection = { title: 'Agents', lines: [selectorLine] } as const;
|
|
249
|
-
const timelineSection = resolveScrollablePanelSection(width, height, {
|
|
250
|
-
intro: 'Inspect a selected agent timeline, tool activity, expanded details, and live/historical message flow.',
|
|
251
|
-
footerLines: [
|
|
252
|
-
buildPanelLine(width, [[` L${this.cursorIndex + 1}/${allRows.length}`, DEFAULT_PANEL_PALETTE.dim], [' Tab', DEFAULT_PANEL_PALETTE.info], [' cycle agents', DEFAULT_PANEL_PALETTE.dim], [' Up/Down', DEFAULT_PANEL_PALETTE.info], [' navigate', DEFAULT_PANEL_PALETTE.dim], [' Enter', DEFAULT_PANEL_PALETTE.info], [' expand', DEFAULT_PANEL_PALETTE.dim]]),
|
|
253
|
-
],
|
|
254
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
255
|
-
beforeSections: [summarySection, agentsSection],
|
|
256
|
-
section: {
|
|
257
|
-
title: 'Timeline',
|
|
258
|
-
scrollableLines: allRows.map((row, index) => this._renderTimelineRow(width, row, index === this.cursorIndex)),
|
|
259
|
-
selectedIndex: this.cursorIndex,
|
|
260
|
-
scrollOffset: this.scrollOffset,
|
|
261
|
-
minRows: 8,
|
|
262
|
-
},
|
|
263
|
-
});
|
|
264
|
-
this.scrollOffset = timelineSection.scrollOffset;
|
|
265
|
-
|
|
266
|
-
return buildPanelWorkspace(width, height, {
|
|
267
|
-
title: ` Inspector [${agents.length} agent${agents.length !== 1 ? 's' : ''}]`,
|
|
268
|
-
intro: 'Inspect a selected agent timeline, tool activity, expanded details, and live/historical message flow.',
|
|
269
|
-
sections: [
|
|
270
|
-
summarySection,
|
|
271
|
-
agentsSection,
|
|
272
|
-
timelineSection.section,
|
|
273
|
-
],
|
|
274
|
-
footerLines: [
|
|
275
|
-
buildPanelLine(width, [[` L${this.cursorIndex + 1}/${allRows.length}`, DEFAULT_PANEL_PALETTE.dim], [' Tab', DEFAULT_PANEL_PALETTE.info], [' cycle agents', DEFAULT_PANEL_PALETTE.dim], [' Up/Down', DEFAULT_PANEL_PALETTE.info], [' navigate', DEFAULT_PANEL_PALETTE.dim], [' Enter', DEFAULT_PANEL_PALETTE.info], [' expand', DEFAULT_PANEL_PALETTE.dim]]),
|
|
276
|
-
],
|
|
277
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
private _renderSelector(
|
|
282
|
-
width: number,
|
|
283
|
-
agents: AgentRecord[],
|
|
284
|
-
): Line {
|
|
285
|
-
if (agents.length === 0) {
|
|
286
|
-
return buildStyledPanelLine(width, [
|
|
287
|
-
{ text: ' (no agents)', fg: COLOR.dimmed, bg: COLOR.statusBar, dim: true },
|
|
288
|
-
]);
|
|
289
|
-
}
|
|
290
|
-
const segments: StyledPanelSegment[] = [{ text: ' ', fg: COLOR.statusFg, bg: COLOR.statusBar }];
|
|
291
|
-
for (const agent of agents) {
|
|
292
|
-
const isSelected = agent.id === this.selectedAgentId;
|
|
293
|
-
const shortId = agent.id.slice(-8);
|
|
294
|
-
const badge = ` ${shortId} `;
|
|
295
|
-
segments.push({
|
|
296
|
-
text: badge,
|
|
297
|
-
fg: isSelected ? COLOR.selected : COLOR.dimmed,
|
|
298
|
-
bg: COLOR.statusBar,
|
|
299
|
-
bold: isSelected,
|
|
300
|
-
});
|
|
301
|
-
segments.push({ text: '│', fg: COLOR.separator, bg: COLOR.statusBar });
|
|
302
|
-
}
|
|
303
|
-
return buildStyledPanelLine(width, segments);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
private _renderAgentInfoSummary(width: number, rec: AgentRecord): Line {
|
|
307
|
-
const now = Date.now();
|
|
308
|
-
const elapsed = (rec.completedAt ?? now) - rec.startedAt;
|
|
309
|
-
const taskPreview = rec.task.split('\n')[0] ?? '';
|
|
310
|
-
const maxTask = Math.max(0, width - 40);
|
|
311
|
-
const taskDisplay = taskPreview.length > maxTask
|
|
312
|
-
? taskPreview.slice(0, maxTask - 1) + '\u2026'
|
|
313
|
-
: taskPreview;
|
|
314
|
-
return buildPanelLine(width, [
|
|
315
|
-
[' Status ', DEFAULT_PANEL_PALETTE.label],
|
|
316
|
-
[rec.status.toUpperCase(), rec.status === 'running' ? DEFAULT_PANEL_PALETTE.good : rec.status === 'failed' ? DEFAULT_PANEL_PALETTE.bad : DEFAULT_PANEL_PALETTE.dim],
|
|
317
|
-
[' Duration ', DEFAULT_PANEL_PALETTE.label],
|
|
318
|
-
[formatMs(elapsed), DEFAULT_PANEL_PALETTE.value],
|
|
319
|
-
[' Tools ', DEFAULT_PANEL_PALETTE.label],
|
|
320
|
-
[String(rec.toolCallCount), DEFAULT_PANEL_PALETTE.info],
|
|
321
|
-
// SDK 0.23.0: show addendum indicator when WRFC injected a constraint addendum
|
|
322
|
-
...(rec.systemPromptAddendum
|
|
323
|
-
? [[' Addendum ', DEFAULT_PANEL_PALETTE.label] as [string, string], ['yes', DEFAULT_PANEL_PALETTE.info] as [string, string]]
|
|
324
|
-
: []),
|
|
325
|
-
[' Task ', DEFAULT_PANEL_PALETTE.label],
|
|
326
|
-
[taskDisplay, DEFAULT_PANEL_PALETTE.value],
|
|
327
|
-
]);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// -------------------------------------------------------------------------
|
|
331
|
-
// Timeline row rendering
|
|
332
|
-
// -------------------------------------------------------------------------
|
|
333
|
-
|
|
334
|
-
private _renderTimelineRow(
|
|
335
|
-
width: number,
|
|
336
|
-
row: DisplayRow,
|
|
337
|
-
isCursor: boolean,
|
|
338
|
-
): Line {
|
|
339
|
-
const bg = isCursor ? '#1a2233' : '';
|
|
340
|
-
const ts = shortTime(row.timestamp);
|
|
341
|
-
const { fg, prefix } = agentKindStyle(row.kind, COLOR);
|
|
342
|
-
const hint = row.hasDetail ? (row.expanded ? ' ▾' : ' ▸') : '';
|
|
343
|
-
const prefixText = `${isCursor ? '▸' : ' '} ${ts} ${prefix} `;
|
|
344
|
-
const reserved = prefixText.length + hint.length;
|
|
345
|
-
const contentBudget = Math.max(0, width - reserved);
|
|
346
|
-
const text = truncateDisplay(row.content, contentBudget);
|
|
347
|
-
|
|
348
|
-
return buildSelectablePanelLine(width, [
|
|
349
|
-
{ text: isCursor ? '▸' : ' ', fg: COLOR.selected, bg, bold: isCursor },
|
|
350
|
-
{ text: ' ', fg: COLOR.value, bg },
|
|
351
|
-
{ text: ts, fg: COLOR.timestamp, bg, dim: true },
|
|
352
|
-
{ text: ' ', fg: COLOR.value, bg },
|
|
353
|
-
{ text: prefix, fg, bg, bold: true },
|
|
354
|
-
{ text: ' ', fg: COLOR.value, bg },
|
|
355
|
-
{ text: text, fg: COLOR.value, bg },
|
|
356
|
-
{ text: hint.length > 0 ? hint.padStart(Math.max(hint.length, width - (prefixText.length + text.length))) : '', fg: COLOR.expandHint, bg, dim: true },
|
|
357
|
-
], { selected: isCursor, selectedBg: bg, fillFg: isCursor ? COLOR.selected : '' });
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// -------------------------------------------------------------------------
|
|
361
|
-
// Private — data
|
|
362
|
-
// -------------------------------------------------------------------------
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Build the flat list of DisplayRow items from timeline + expanded detail
|
|
366
|
-
* sub-rows. This is what the renderer walks.
|
|
367
|
-
*/
|
|
368
|
-
private _buildVisibleRows(): DisplayRow[] {
|
|
369
|
-
const rows: DisplayRow[] = [];
|
|
370
|
-
|
|
371
|
-
// Merge bus messages (live) + JSONL (historical), sorted by timestamp
|
|
372
|
-
const busEntries = this._busToTimeline();
|
|
373
|
-
const merged = [...this.timeline, ...busEntries]
|
|
374
|
-
.sort((a, b) => a.timestamp - b.timestamp);
|
|
375
|
-
|
|
376
|
-
// Deduplicate: bus messages that already appear in JSONL will have
|
|
377
|
-
// approximate timestamps. We just show all — bus msgs tend to have
|
|
378
|
-
// unique content.
|
|
379
|
-
for (const entry of merged) {
|
|
380
|
-
rows.push({
|
|
381
|
-
kind: entry.kind,
|
|
382
|
-
timestamp: entry.timestamp,
|
|
383
|
-
content: entry.content,
|
|
384
|
-
hasDetail: !!entry.detail,
|
|
385
|
-
expanded: entry.expanded,
|
|
386
|
-
entryRef: entry,
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
// If expanded and has detail — insert sub-rows
|
|
390
|
-
if (entry.expanded && entry.detail) {
|
|
391
|
-
const detailLines = entry.detail.split('\n');
|
|
392
|
-
for (const dl of detailLines) {
|
|
393
|
-
rows.push({
|
|
394
|
-
kind: 'tool_result',
|
|
395
|
-
timestamp: entry.timestamp,
|
|
396
|
-
content: dl,
|
|
397
|
-
hasDetail: false,
|
|
398
|
-
expanded: false,
|
|
399
|
-
entryRef: null,
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
return rows;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
private _busToTimeline(): TimelineEntry[] {
|
|
409
|
-
if (!this.selectedAgentId) return [];
|
|
410
|
-
const messages = this.deps.agentMessageBus.getMessages(this.selectedAgentId);
|
|
411
|
-
const DEDUP_WINDOW_MS = 2000;
|
|
412
|
-
const seen = new Map<string, number>(); // hash -> last timestamp
|
|
413
|
-
const result: TimelineEntry[] = [];
|
|
414
|
-
for (const msg of messages) {
|
|
415
|
-
const isFromUser = msg.from === 'orchestrator' || msg.from === 'system';
|
|
416
|
-
const kind: EntryKind = isFromUser ? 'user' : 'assistant';
|
|
417
|
-
const contentSnippet = msg.content.slice(0, 50);
|
|
418
|
-
const dedupKey = `${kind}:${contentSnippet}`;
|
|
419
|
-
const lastSeen = seen.get(dedupKey);
|
|
420
|
-
if (lastSeen !== undefined && msg.timestamp - lastSeen < DEDUP_WINDOW_MS) {
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
seen.set(dedupKey, msg.timestamp);
|
|
424
|
-
result.push({
|
|
425
|
-
kind,
|
|
426
|
-
timestamp: msg.timestamp,
|
|
427
|
-
label: msg.from,
|
|
428
|
-
content: msg.content.length > 200
|
|
429
|
-
? msg.content.slice(0, 197) + '\u2026'
|
|
430
|
-
: msg.content,
|
|
431
|
-
expanded: false,
|
|
432
|
-
} satisfies TimelineEntry);
|
|
433
|
-
}
|
|
434
|
-
return result;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// -------------------------------------------------------------------------
|
|
438
|
-
// Private — refresh
|
|
439
|
-
// -------------------------------------------------------------------------
|
|
440
|
-
|
|
441
|
-
private async _refreshTimeline(): Promise<void> {
|
|
442
|
-
if (!this.selectedAgentId) return;
|
|
443
|
-
try {
|
|
444
|
-
const sessionFile = join(
|
|
445
|
-
this.deps.workingDirectory,
|
|
446
|
-
'.goodvibes', GOODVIBES_AGENT_SURFACE_ROOT, 'sessions',
|
|
447
|
-
`${this.selectedAgentId}.jsonl`,
|
|
448
|
-
);
|
|
449
|
-
const raw = await readFile(sessionFile, 'utf-8');
|
|
450
|
-
const logLines = raw.trim().split('\n').filter(Boolean);
|
|
451
|
-
const rows = logLines
|
|
452
|
-
.slice(-MAX_JSONL_ENTRIES)
|
|
453
|
-
.map((line) => {
|
|
454
|
-
try { return JSON.parse(line) as Record<string, unknown>; }
|
|
455
|
-
catch { return null; }
|
|
456
|
-
})
|
|
457
|
-
.filter((r): r is Record<string, unknown> => r !== null);
|
|
458
|
-
this.timeline = jsonlToTimeline(rows);
|
|
459
|
-
} catch (err) {
|
|
460
|
-
const code = (err as NodeJS.ErrnoException).code;
|
|
461
|
-
if (code !== 'ENOENT') {
|
|
462
|
-
logger.debug('AgentInspectorPanel: failed to load session log', { error: summarizeError(err) });
|
|
463
|
-
}
|
|
464
|
-
this.timeline = [];
|
|
465
|
-
}
|
|
466
|
-
this.markDirty();
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
private _startRefresh(): void {
|
|
470
|
-
if (this.refreshTimerId) return;
|
|
471
|
-
this.refreshTimerId = this.registerTimer(setInterval(() => {
|
|
472
|
-
this._refreshTimeline().catch((err) => { logger.debug('agent inspector timeline refresh tick failed', { err }); });
|
|
473
|
-
}, REFRESH_MS));
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
private _stopRefresh(): void {
|
|
477
|
-
if (this.refreshTimerId) {
|
|
478
|
-
this.clearTimer(this.refreshTimerId);
|
|
479
|
-
this.refreshTimerId = null;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// -------------------------------------------------------------------------
|
|
484
|
-
// Private — navigation
|
|
485
|
-
// -------------------------------------------------------------------------
|
|
486
|
-
|
|
487
|
-
private _moveCursor(delta: number): void {
|
|
488
|
-
const rows = this._getCachedRows();
|
|
489
|
-
if (rows.length === 0) return;
|
|
490
|
-
this.cursorIndex = Math.max(0, Math.min(rows.length - 1, this.cursorIndex + delta));
|
|
491
|
-
this.markDirty();
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
private _scroll(delta: number): void {
|
|
495
|
-
this._moveCursor(delta);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
private _toggleExpand(): void {
|
|
499
|
-
const rows = this._getCachedRows();
|
|
500
|
-
const row = rows[this.cursorIndex];
|
|
501
|
-
if (!row?.entryRef || !row.hasDetail) return;
|
|
502
|
-
row.entryRef.expanded = !row.entryRef.expanded;
|
|
503
|
-
this.markDirty();
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
private _nextAgent(): void {
|
|
507
|
-
const agents = this.deps.agentManager.list();
|
|
508
|
-
if (agents.length === 0) return;
|
|
509
|
-
|
|
510
|
-
if (!this.selectedAgentId) {
|
|
511
|
-
this.inspectAgent(agents[0]!.id);
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
const idx = agents.findIndex(a => a.id === this.selectedAgentId);
|
|
516
|
-
const next = agents[(idx + 1) % agents.length];
|
|
517
|
-
if (next) {
|
|
518
|
-
this.inspectAgent(next.id);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
export type AgentInspectorEntryKind = 'user' | 'assistant' | 'tool_call' | 'tool_result' | 'session' | 'error';
|
|
2
|
-
|
|
3
|
-
export interface AgentTimelineEntry {
|
|
4
|
-
kind: AgentInspectorEntryKind;
|
|
5
|
-
timestamp: number;
|
|
6
|
-
label: string;
|
|
7
|
-
content: string;
|
|
8
|
-
detail?: string;
|
|
9
|
-
expanded: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface AgentDisplayRow {
|
|
13
|
-
kind: AgentInspectorEntryKind;
|
|
14
|
-
timestamp: number;
|
|
15
|
-
content: string;
|
|
16
|
-
hasDetail: boolean;
|
|
17
|
-
expanded: boolean;
|
|
18
|
-
entryRef: AgentTimelineEntry | null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type JsonlRow = Record<string, unknown>;
|
|
22
|
-
|
|
23
|
-
export function agentStatusColor(status: string, colors: Record<string, string>): string {
|
|
24
|
-
switch (status) {
|
|
25
|
-
case 'pending': return colors.pending ?? colors.system;
|
|
26
|
-
case 'running': return colors.running ?? colors.system;
|
|
27
|
-
case 'completed': return colors.completed ?? colors.system;
|
|
28
|
-
case 'failed': return colors.failed ?? colors.system;
|
|
29
|
-
case 'cancelled': return colors.cancelled ?? colors.system;
|
|
30
|
-
default: return colors.system;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function formatAgentDuration(ms: number): string {
|
|
35
|
-
if (ms < 1000) return `${ms}ms`;
|
|
36
|
-
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
37
|
-
return `${Math.floor(ms / 60000)}m${Math.floor((ms % 60000) / 1000)}s`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function formatAgentTime(ts: number): string {
|
|
41
|
-
const d = new Date(ts);
|
|
42
|
-
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function jsonlToTimeline(rows: JsonlRow[]): AgentTimelineEntry[] {
|
|
46
|
-
const entries: AgentTimelineEntry[] = [];
|
|
47
|
-
for (const row of rows) {
|
|
48
|
-
const type = String(row.type ?? 'unknown');
|
|
49
|
-
const rawTs = row.timestamp;
|
|
50
|
-
const ts = typeof rawTs === 'string' ? Date.parse(rawTs) : typeof rawTs === 'number' ? rawTs : Date.now();
|
|
51
|
-
switch (type) {
|
|
52
|
-
case 'tool_execution': {
|
|
53
|
-
const toolName = String(row.toolName ?? 'tool');
|
|
54
|
-
const argsStr = row.args !== undefined ? JSON.stringify(row.args, null, 2) : undefined;
|
|
55
|
-
const resultStr = row.result !== undefined ? JSON.stringify(row.result, null, 2) : undefined;
|
|
56
|
-
const detail = [argsStr ? `Args:\n${argsStr}` : '', resultStr ? `Result:\n${resultStr}` : ''].filter(Boolean).join('\n\n');
|
|
57
|
-
entries.push({ kind: 'tool_call', timestamp: ts, label: toolName, content: `[tool] ${toolName}` + (row.durationMs !== undefined ? ` (${row.durationMs}ms)` : ''), detail: detail || undefined, expanded: false });
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
case 'llm_response': {
|
|
61
|
-
const toolCount = Number(row.toolCallCount ?? 0);
|
|
62
|
-
const charLen = Number(row.contentLength ?? 0);
|
|
63
|
-
entries.push({ kind: 'assistant', timestamp: ts, label: 'assistant', content: `[assistant] ${charLen} chars, ${toolCount} tool calls`, expanded: false });
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
case 'meta':
|
|
67
|
-
case 'session_start':
|
|
68
|
-
entries.push({ kind: 'session', timestamp: ts, label: 'session', content: `[session start] ${String(row.agentId ?? '')}`, expanded: false });
|
|
69
|
-
break;
|
|
70
|
-
case 'session_end':
|
|
71
|
-
entries.push({ kind: 'session', timestamp: ts, label: 'session', content: `[session end] ${String(row.status ?? 'unknown')}`, expanded: false });
|
|
72
|
-
break;
|
|
73
|
-
case 'error':
|
|
74
|
-
entries.push({ kind: 'error', timestamp: ts, label: 'error', content: `[error] ${String(row.message ?? row.error ?? 'unknown error')}`, expanded: false });
|
|
75
|
-
break;
|
|
76
|
-
default:
|
|
77
|
-
entries.push({ kind: 'session', timestamp: ts, label: type, content: `[${type}]`, expanded: false });
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return entries;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function agentKindStyle(kind: AgentInspectorEntryKind, colors: Record<string, string>): { fg: string; prefix: string } {
|
|
85
|
-
switch (kind) {
|
|
86
|
-
case 'user': return { fg: colors.user, prefix: '[user] ' };
|
|
87
|
-
case 'assistant': return { fg: colors.assistant, prefix: '[assistant]' };
|
|
88
|
-
case 'tool_call': return { fg: colors.tool, prefix: '[tool] ' };
|
|
89
|
-
case 'tool_result': return { fg: colors.toolResult, prefix: ' \u2514 ' };
|
|
90
|
-
case 'session': return { fg: colors.system, prefix: '[session] ' };
|
|
91
|
-
case 'error': return { fg: colors.error, prefix: '[error] ' };
|
|
92
|
-
default: return { fg: colors.dimmed ?? colors.system, prefix: '[?] ' };
|
|
93
|
-
}
|
|
94
|
-
}
|