@pellux/goodvibes-agent 0.1.101 → 0.1.103
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 -18
- 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 +646 -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-setup.ts +2 -2
- package/src/input/agent-workspace-snapshot.ts +4 -4
- package/src/input/agent-workspace-types.ts +2 -2
- package/src/input/command-registry.ts +0 -8
- 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 +3 -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-command-parts.ts +0 -28
- package/src/runtime/bootstrap-core.ts +1 -1
- package/src/runtime/bootstrap.ts +3 -12
- 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
|
@@ -150,18 +150,11 @@ export function createBootstrapCommandActions(
|
|
|
150
150
|
| 'exit'
|
|
151
151
|
| 'reloadSystemPrompt'
|
|
152
152
|
| 'showPanel'
|
|
153
|
-
| 'openForensicsPanel'
|
|
154
|
-
| 'openIncidentPanel'
|
|
155
153
|
| 'openPolicyPanel'
|
|
156
|
-
| 'openHooksPanel'
|
|
157
|
-
| 'openCommunicationPanel'
|
|
158
|
-
| 'openOrchestrationPanel'
|
|
159
|
-
| 'openCockpitPanel'
|
|
160
154
|
| 'openMcpWorkspace'
|
|
161
155
|
| 'openAgentWorkspace'
|
|
162
156
|
| 'openSecurityPanel'
|
|
163
157
|
| 'openKnowledgePanel'
|
|
164
|
-
| 'openRemotePanel'
|
|
165
158
|
| 'openSubscriptionPanel'
|
|
166
159
|
> {
|
|
167
160
|
const {
|
|
@@ -244,27 +237,9 @@ export function createBootstrapCommandActions(
|
|
|
244
237
|
exit: () => unwiredShellAction('exit'),
|
|
245
238
|
reloadSystemPrompt: loadSystemPrompt,
|
|
246
239
|
showPanel,
|
|
247
|
-
openForensicsPanel: () => {
|
|
248
|
-
showPanel('forensics');
|
|
249
|
-
},
|
|
250
|
-
openIncidentPanel: () => {
|
|
251
|
-
showPanel('incident');
|
|
252
|
-
},
|
|
253
240
|
openPolicyPanel: () => {
|
|
254
241
|
showPanel('policy');
|
|
255
242
|
},
|
|
256
|
-
openHooksPanel: () => {
|
|
257
|
-
showPanel('hooks');
|
|
258
|
-
},
|
|
259
|
-
openCommunicationPanel: () => {
|
|
260
|
-
showPanel('communication');
|
|
261
|
-
},
|
|
262
|
-
openOrchestrationPanel: () => {
|
|
263
|
-
showPanel('orchestration');
|
|
264
|
-
},
|
|
265
|
-
openCockpitPanel: () => {
|
|
266
|
-
showPanel('cockpit');
|
|
267
|
-
},
|
|
268
243
|
openMcpWorkspace: () => unwiredShellAction('openMcpWorkspace'),
|
|
269
244
|
openAgentWorkspace: () => unwiredShellAction('openAgentWorkspace'),
|
|
270
245
|
openSecurityPanel: () => {
|
|
@@ -273,9 +248,6 @@ export function createBootstrapCommandActions(
|
|
|
273
248
|
openKnowledgePanel: () => {
|
|
274
249
|
showPanel('knowledge');
|
|
275
250
|
},
|
|
276
|
-
openRemotePanel: () => {
|
|
277
|
-
showPanel('remote');
|
|
278
|
-
},
|
|
279
251
|
openSubscriptionPanel: () => {
|
|
280
252
|
showPanel('subscription');
|
|
281
253
|
},
|
|
@@ -29,7 +29,7 @@ import { registerBootstrapHookBridge } from '@/runtime/index.ts';
|
|
|
29
29
|
import { registerBootstrapRuntimeEvents } from '@/runtime/index.ts';
|
|
30
30
|
import { createRuntimeServices, type RuntimeServices } from './services.ts';
|
|
31
31
|
import { createUiRuntimeServices, type UiRuntimeServices } from './ui-services.ts';
|
|
32
|
-
import { installAgentToolPolicyGuard } from '../tools/
|
|
32
|
+
import { installAgentToolPolicyGuard } from '../tools/agent-tool-policy-guard.ts';
|
|
33
33
|
import { registerAgentLocalRegistryTool } from '../tools/agent-local-registry-tool.ts';
|
|
34
34
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
35
35
|
|
package/src/runtime/bootstrap.ts
CHANGED
|
@@ -553,19 +553,10 @@ export async function bootstrapRuntime(
|
|
|
553
553
|
};
|
|
554
554
|
|
|
555
555
|
// ── Phase 12b: Operator intervention wiring (feature-gated) ──────────────
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
//
|
|
556
|
+
// Keep the copied control-plane state internal. GoodVibes Agent does not
|
|
557
|
+
// expose the copied local ops-control panel; operator control is surfaced
|
|
558
|
+
// through Agent-owned status, approvals, automation, and delegation flows.
|
|
559
559
|
ctx.commandContext.ops.acpManager = acpManager;
|
|
560
|
-
if (opsControlPlane) {
|
|
561
|
-
ctx.commandContext.openOpsPanel = () => {
|
|
562
|
-
if (ctx.commandContext.showPanel) ctx.commandContext.showPanel('ops-control');
|
|
563
|
-
else {
|
|
564
|
-
panelManager.open('ops-control');
|
|
565
|
-
requestRender();
|
|
566
|
-
}
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
560
|
|
|
570
561
|
// Wire exit from options if provided; otherwise main.ts binds the shell bridge.
|
|
571
562
|
if (options?.exit) {
|
package/src/runtime/services.ts
CHANGED
|
@@ -341,7 +341,8 @@ export interface RuntimeServices {
|
|
|
341
341
|
readonly fileUndoManager: FileUndoManager;
|
|
342
342
|
readonly integrationHelpers: IntegrationHelperService;
|
|
343
343
|
/**
|
|
344
|
-
* Re-root
|
|
344
|
+
* Re-root workspace-bound stores to a new working directory.
|
|
345
|
+
* Agent memory is home/profile-owned and intentionally does not move on workspace swap.
|
|
345
346
|
* Called by WorkspaceSwapManager after the new directory has been verified.
|
|
346
347
|
* Stores that require a process restart emit a warn-level log; they continue serving
|
|
347
348
|
* the old path until the daemon restarts with the new --working-dir.
|
|
@@ -472,7 +473,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
472
473
|
});
|
|
473
474
|
const artifactStore = new ArtifactStore({ configManager });
|
|
474
475
|
const memoryEmbeddingRegistry = new MemoryEmbeddingProviderRegistry({ configManager });
|
|
475
|
-
const memoryDbPath =
|
|
476
|
+
const memoryDbPath = shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'memory.sqlite');
|
|
476
477
|
const memoryStore = new MemoryStore(memoryDbPath, {
|
|
477
478
|
embeddingRegistry: memoryEmbeddingRegistry,
|
|
478
479
|
});
|
|
@@ -733,8 +734,6 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
733
734
|
fileUndoManager,
|
|
734
735
|
integrationHelpers,
|
|
735
736
|
async rerootStores(newWorkingDir: string): Promise<void> {
|
|
736
|
-
const newMemoryDbPath = join(newWorkingDir, '.goodvibes', GOODVIBES_AGENT_SURFACE_ROOT, 'memory.sqlite');
|
|
737
|
-
await memoryStore.reroot(newMemoryDbPath);
|
|
738
737
|
await projectIndex.reroot(newWorkingDir);
|
|
739
738
|
},
|
|
740
739
|
};
|
|
@@ -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.103';
|
|
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
|
-
}
|