@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
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'fs/promises';
|
|
2
|
-
import { type Line } from '../types/grid.ts';
|
|
3
|
-
import { ModalFactory } from './modal-factory.ts';
|
|
4
|
-
import type { AgentManager } from '@pellux/goodvibes-sdk/platform/tools';
|
|
5
|
-
import type { AgentMessageBus } from '@pellux/goodvibes-sdk/platform/agents';
|
|
6
|
-
import type { WrfcController } from '@pellux/goodvibes-sdk/platform/agents';
|
|
7
|
-
import { formatDuration } from './modal-utils.ts';
|
|
8
|
-
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
9
|
-
import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
|
|
10
|
-
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
11
|
-
|
|
12
|
-
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
13
|
-
|
|
14
|
-
const TOKENS_PER_TOOL_CALL = 400;
|
|
15
|
-
const MAX_LOG_ENTRIES = 10;
|
|
16
|
-
const AGENT_ID_DISPLAY_LENGTH = 16;
|
|
17
|
-
|
|
18
|
-
export interface AgentDetailModalDeps {
|
|
19
|
-
readonly agentManager: Pick<AgentManager, 'getStatus'>;
|
|
20
|
-
readonly agentMessageBus: Pick<AgentMessageBus, 'getMessages'>;
|
|
21
|
-
readonly sessionLogPathResolver: (agentId: string) => string;
|
|
22
|
-
/** Optional — when supplied, constraint data from the agent's WRFC chain is shown (SDK 0.23.0). */
|
|
23
|
-
readonly wrfcController?: Pick<WrfcController, 'getChain'>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ─── AgentDetailModal ─────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* AgentDetailModal — deep-view modal for a single running/completed agent.
|
|
30
|
-
*
|
|
31
|
-
* Displays task description, template, model, status, duration, tool-call
|
|
32
|
-
* count, estimated token usage, recent messages from AgentMessageBus, and
|
|
33
|
-
* the agent's current progress note.
|
|
34
|
-
*/
|
|
35
|
-
export class AgentDetailModal {
|
|
36
|
-
public active = false;
|
|
37
|
-
public agentId: string | null = null;
|
|
38
|
-
|
|
39
|
-
/** Cached JSONL log entries, loaded on open(). */
|
|
40
|
-
public logEntries: Record<string, unknown>[] = [];
|
|
41
|
-
public logTotal = 0;
|
|
42
|
-
|
|
43
|
-
private refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
44
|
-
private onRefresh: (() => void) | null = null;
|
|
45
|
-
|
|
46
|
-
constructor(readonly deps: AgentDetailModalDeps) {}
|
|
47
|
-
|
|
48
|
-
/** Set a callback to trigger re-render when log data updates. */
|
|
49
|
-
setOnRefresh(fn: () => void): void {
|
|
50
|
-
this.onRefresh = fn;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
open(agentId: string): void {
|
|
54
|
-
this.agentId = agentId;
|
|
55
|
-
this.active = true;
|
|
56
|
-
this.logEntries = [];
|
|
57
|
-
this.logTotal = 0;
|
|
58
|
-
this.loadLog().catch((err) => { logger.debug('agent detail log load failed', { err }); });
|
|
59
|
-
// Auto-refresh log every 500ms while modal is open
|
|
60
|
-
if (this.refreshTimer) clearInterval(this.refreshTimer);
|
|
61
|
-
this.refreshTimer = setInterval(() => {
|
|
62
|
-
this.loadLog().then(() => this.onRefresh?.()).catch((err) => { logger.debug('agent detail log refresh tick failed', { err }); });
|
|
63
|
-
}, 500);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
close(): void {
|
|
67
|
-
this.active = false;
|
|
68
|
-
this.agentId = null;
|
|
69
|
-
this.logEntries = [];
|
|
70
|
-
this.logTotal = 0;
|
|
71
|
-
if (this.refreshTimer) {
|
|
72
|
-
clearInterval(this.refreshTimer);
|
|
73
|
-
this.refreshTimer = null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async loadLog(): Promise<void> {
|
|
78
|
-
if (!this.agentId) { this.logEntries = []; this.logTotal = 0; return; }
|
|
79
|
-
try {
|
|
80
|
-
const sessionFile = this.deps.sessionLogPathResolver(this.agentId);
|
|
81
|
-
const logContent = await readFile(sessionFile, 'utf-8');
|
|
82
|
-
const logLines = logContent.trim().split('\n');
|
|
83
|
-
this.logTotal = logLines.length;
|
|
84
|
-
const parsed = logLines.slice(-MAX_LOG_ENTRIES).map(line => {
|
|
85
|
-
try { return JSON.parse(line) as Record<string, unknown>; } catch { return null; }
|
|
86
|
-
});
|
|
87
|
-
const failedCount = parsed.filter(e => e === null).length;
|
|
88
|
-
if (failedCount > 0) {
|
|
89
|
-
logger.debug('AgentDetailModal: skipped malformed JSONL lines', { count: failedCount });
|
|
90
|
-
}
|
|
91
|
-
this.logEntries = parsed.filter((e): e is Record<string, unknown> => e !== null);
|
|
92
|
-
} catch (err) {
|
|
93
|
-
const code = (err as NodeJS.ErrnoException).code;
|
|
94
|
-
if (code !== 'ENOENT') {
|
|
95
|
-
logger.debug('AgentDetailModal: failed to load session log', { error: summarizeError(err) });
|
|
96
|
-
}
|
|
97
|
-
this.logEntries = [];
|
|
98
|
-
this.logTotal = 0;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
104
|
-
|
|
105
|
-
/** Rough token estimate: toolCallCount * avg tokens per tool exchange. */
|
|
106
|
-
function estimateTokens(toolCallCount: number): number {
|
|
107
|
-
return toolCallCount * TOKENS_PER_TOOL_CALL;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ─── renderAgentDetailModal ───────────────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Render the agent detail modal as Line[] for overlay in the viewport.
|
|
114
|
-
*
|
|
115
|
-
* Shows a deep view of the selected agent: task, template, model, status,
|
|
116
|
-
* duration, tool call count, token estimate, recent bus messages, and
|
|
117
|
-
* progress text.
|
|
118
|
-
*
|
|
119
|
-
* @param modal AgentDetailModal state
|
|
120
|
-
* @param width Terminal width
|
|
121
|
-
*/
|
|
122
|
-
export function renderAgentDetailModal(
|
|
123
|
-
modal: AgentDetailModal,
|
|
124
|
-
width: number,
|
|
125
|
-
viewportHeight = 24,
|
|
126
|
-
): Line[] {
|
|
127
|
-
if (!modal.agentId) return [];
|
|
128
|
-
const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
|
|
129
|
-
margin: 2,
|
|
130
|
-
maxWidth: width - 4,
|
|
131
|
-
chromeRows: 6,
|
|
132
|
-
minContentRows: 10,
|
|
133
|
-
maxContentRows: 22,
|
|
134
|
-
});
|
|
135
|
-
const targetContentRows = Math.max(18, Math.min(22, getStableOverlayContentRows(metrics.contentRows, 12) + 8));
|
|
136
|
-
|
|
137
|
-
const rec = modal.deps.agentManager.getStatus(modal.agentId);
|
|
138
|
-
if (!rec) {
|
|
139
|
-
return ModalFactory.createModal({
|
|
140
|
-
title: 'Agent Detail',
|
|
141
|
-
width: metrics.boxWidth,
|
|
142
|
-
margin: metrics.margin,
|
|
143
|
-
targetContentRows,
|
|
144
|
-
sections: [
|
|
145
|
-
{ type: 'text', content: '(agent not found)' },
|
|
146
|
-
],
|
|
147
|
-
hints: ['[Esc] Close'],
|
|
148
|
-
}, width);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const now = Date.now();
|
|
152
|
-
const elapsedMs = (rec.completedAt ?? now) - rec.startedAt;
|
|
153
|
-
const tokenEst = estimateTokens(rec.toolCallCount);
|
|
154
|
-
|
|
155
|
-
// ── Build sections ────────────────────────────────────────────────────────
|
|
156
|
-
|
|
157
|
-
const sections: import('./modal-factory.ts').ModalSection[] = [];
|
|
158
|
-
|
|
159
|
-
// Task — show first line only, capped at 120 chars
|
|
160
|
-
const taskFirstLine = rec.task.split('\n')[0].replace(/^(WRFC\s+(Fix|Review)\s+Request\s*)/i, '').trim();
|
|
161
|
-
const taskDisplay = taskFirstLine.length > 120 ? taskFirstLine.slice(0, 117) + '\u2026' : taskFirstLine;
|
|
162
|
-
sections.push({
|
|
163
|
-
type: 'text',
|
|
164
|
-
content: `Task: ${taskDisplay}`,
|
|
165
|
-
style: { bold: true },
|
|
166
|
-
});
|
|
167
|
-
sections.push({ type: 'separator' });
|
|
168
|
-
|
|
169
|
-
// Metadata grid
|
|
170
|
-
const modelStr = rec.model ? `${rec.provider ?? ''}/${rec.model}` : (rec.provider ?? '(default)');
|
|
171
|
-
sections.push({ type: 'text', content: `Template : ${rec.template}` });
|
|
172
|
-
sections.push({ type: 'text', content: `Model : ${modelStr}` });
|
|
173
|
-
sections.push({ type: 'text', content: `Status : ${rec.status}` });
|
|
174
|
-
sections.push({ type: 'text', content: `Duration : ${formatDuration(elapsedMs)}` });
|
|
175
|
-
sections.push({ type: 'separator' });
|
|
176
|
-
|
|
177
|
-
// Metrics
|
|
178
|
-
sections.push({ type: 'text', content: `Tool calls : ${rec.toolCallCount}` });
|
|
179
|
-
sections.push({ type: 'text', content: `Est tokens : ~${tokenEst.toLocaleString()}` });
|
|
180
|
-
|
|
181
|
-
// SDK 0.23.0: systemPromptAddendum indicator — confirms WRFC constraint addendum was injected
|
|
182
|
-
if (rec.systemPromptAddendum) {
|
|
183
|
-
sections.push({
|
|
184
|
-
type: 'text',
|
|
185
|
-
content: 'Addendum : yes (WRFC constraint layer injected)',
|
|
186
|
-
style: { fg: '#aaffee' },
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// SDK 0.23.0: constraint data from WRFC chain (engineer constraints + reviewer findings)
|
|
191
|
-
if (rec.wrfcId && modal.deps.wrfcController) {
|
|
192
|
-
try {
|
|
193
|
-
const chain = modal.deps.wrfcController.getChain(rec.wrfcId);
|
|
194
|
-
if (chain && chain.constraints.length > 0) {
|
|
195
|
-
sections.push({ type: 'separator' });
|
|
196
|
-
sections.push({
|
|
197
|
-
type: 'text',
|
|
198
|
-
content: `Constraints (${chain.constraints.length}):`,
|
|
199
|
-
style: { dim: true },
|
|
200
|
-
});
|
|
201
|
-
for (const c of chain.constraints) {
|
|
202
|
-
const text = c.text.length > 80 ? c.text.slice(0, 77) + '…' : c.text;
|
|
203
|
-
sections.push({
|
|
204
|
-
type: 'text',
|
|
205
|
-
content: ` [${c.id}] ${text}`,
|
|
206
|
-
style: { fg: '246' },
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
// Reviewer constraint findings (if review has completed)
|
|
210
|
-
const findings = chain.reviewerReport?.constraintFindings;
|
|
211
|
-
if (findings && findings.length > 0) {
|
|
212
|
-
const unsatisfied = findings.filter((f) => !f.satisfied);
|
|
213
|
-
sections.push({
|
|
214
|
-
type: 'text',
|
|
215
|
-
content: `Findings : ${findings.length} checked, ${unsatisfied.length} unsatisfied`,
|
|
216
|
-
style: { fg: unsatisfied.length > 0 ? '#ff6666' : '#44ff88' },
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
} catch {
|
|
221
|
-
// wrfcController.getChain throws when chain not found — normal during teardown
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Progress
|
|
226
|
-
if (rec.progress) {
|
|
227
|
-
sections.push({ type: 'separator' });
|
|
228
|
-
sections.push({
|
|
229
|
-
type: 'text',
|
|
230
|
-
content: `Progress: ${rec.progress}`,
|
|
231
|
-
style: { fg: '#00ffcc' },
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Error
|
|
236
|
-
if (rec.error) {
|
|
237
|
-
sections.push({ type: 'separator' });
|
|
238
|
-
sections.push({
|
|
239
|
-
type: 'text',
|
|
240
|
-
content: `Error: ${rec.error}`,
|
|
241
|
-
style: { fg: '#ff6666' },
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Recent messages from AgentMessageBus
|
|
246
|
-
const recentMessages = modal.deps.agentMessageBus
|
|
247
|
-
.getMessages(modal.agentId)
|
|
248
|
-
.slice(-4); // last 4 messages
|
|
249
|
-
|
|
250
|
-
if (recentMessages.length > 0) {
|
|
251
|
-
sections.push({ type: 'separator' });
|
|
252
|
-
sections.push({
|
|
253
|
-
type: 'text',
|
|
254
|
-
content: `Recent messages (${recentMessages.length}):`,
|
|
255
|
-
style: { dim: true },
|
|
256
|
-
});
|
|
257
|
-
for (const msg of recentMessages) {
|
|
258
|
-
const fromLabel = msg.from === '*' ? 'broadcast' : msg.from.slice(0, 12);
|
|
259
|
-
const preview = msg.content.length > 50
|
|
260
|
-
? msg.content.slice(0, 47) + '\u2026'
|
|
261
|
-
: msg.content;
|
|
262
|
-
sections.push({
|
|
263
|
-
type: 'text',
|
|
264
|
-
content: ` [${fromLabel}] ${preview}`,
|
|
265
|
-
style: { fg: '246' },
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Execution history from cached JSONL session log (loaded on open())
|
|
271
|
-
if (modal.logEntries.length > 0) {
|
|
272
|
-
sections.push({ type: 'separator' });
|
|
273
|
-
sections.push({
|
|
274
|
-
type: 'text',
|
|
275
|
-
content: `Execution Log (${modal.logTotal} events, showing last ${modal.logEntries.length}):`,
|
|
276
|
-
style: { dim: true },
|
|
277
|
-
});
|
|
278
|
-
for (const entry of modal.logEntries) {
|
|
279
|
-
const entryType = String(entry.type ?? 'unknown');
|
|
280
|
-
const rawTs = entry.timestamp;
|
|
281
|
-
const ts = typeof rawTs === 'string' && rawTs.length >= 19
|
|
282
|
-
? rawTs.slice(11, 19)
|
|
283
|
-
: '';
|
|
284
|
-
let detail = '';
|
|
285
|
-
if (entryType === 'tool_execution') detail = ` ${entry.toolName}`;
|
|
286
|
-
if (entryType === 'llm_response') detail = ` (${entry.toolCallCount} tools, ${entry.contentLength} chars)`;
|
|
287
|
-
if (entryType === 'session_end') detail = ` [${entry.status}]`;
|
|
288
|
-
sections.push({
|
|
289
|
-
type: 'text',
|
|
290
|
-
content: ` ${ts} ${entryType}${detail}`,
|
|
291
|
-
style: { fg: '246' },
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Streaming content — show live output when agent is actively streaming
|
|
297
|
-
const STREAMING_MAX_CHARS = 500;
|
|
298
|
-
if (rec.status === 'running' && rec.streamingContent) {
|
|
299
|
-
const content = rec.streamingContent;
|
|
300
|
-
const truncated = content.length > STREAMING_MAX_CHARS;
|
|
301
|
-
const display = truncated ? content.slice(-STREAMING_MAX_CHARS) : content;
|
|
302
|
-
sections.push({ type: 'separator' });
|
|
303
|
-
sections.push({
|
|
304
|
-
type: 'text',
|
|
305
|
-
content: truncated
|
|
306
|
-
? `Streaming (last ${STREAMING_MAX_CHARS} of ${content.length} chars \u2191 scroll for more):`
|
|
307
|
-
: 'Streaming:',
|
|
308
|
-
style: { fg: '#00ffcc', dim: true },
|
|
309
|
-
});
|
|
310
|
-
// Split into display lines, capped at width for readability
|
|
311
|
-
const maxLineWidth = Math.max(width - 10, 40);
|
|
312
|
-
const streamLines = display.split('\n');
|
|
313
|
-
for (const line of streamLines) {
|
|
314
|
-
const trimmed = line.length > maxLineWidth ? line.slice(0, maxLineWidth - 1) + '\u2026' : line;
|
|
315
|
-
sections.push({
|
|
316
|
-
type: 'text',
|
|
317
|
-
content: ` ${trimmed}`,
|
|
318
|
-
style: { fg: '#aaffee' },
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return ModalFactory.createModal({
|
|
324
|
-
title: `Agent: ${rec.id.slice(0, AGENT_ID_DISPLAY_LENGTH)}`,
|
|
325
|
-
width: metrics.boxWidth,
|
|
326
|
-
margin: metrics.margin,
|
|
327
|
-
targetContentRows,
|
|
328
|
-
sections,
|
|
329
|
-
hints: ['[Esc] Close'],
|
|
330
|
-
}, width);
|
|
331
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
export type ProcessSummaryAgent = {
|
|
2
|
-
readonly id: string;
|
|
3
|
-
readonly progress?: string;
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
export type RuntimeProcessSummaryAgent = {
|
|
7
|
-
readonly id: string;
|
|
8
|
-
readonly latestProgress?: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export type WrfcProcessSummaryChain = {
|
|
12
|
-
readonly state?: string;
|
|
13
|
-
readonly ownerAgentId?: string;
|
|
14
|
-
readonly engineerAgentId?: string;
|
|
15
|
-
readonly reviewerAgentId?: string;
|
|
16
|
-
readonly fixerAgentId?: string;
|
|
17
|
-
readonly allAgentIds?: readonly unknown[];
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export type RunningAgentSummary = {
|
|
21
|
-
readonly count: number;
|
|
22
|
-
readonly progress?: string;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export function summarizeRunningAgents(
|
|
26
|
-
managerAgents: readonly ProcessSummaryAgent[],
|
|
27
|
-
runtimeAgents: readonly RuntimeProcessSummaryAgent[],
|
|
28
|
-
wrfcChains: readonly WrfcProcessSummaryChain[],
|
|
29
|
-
): RunningAgentSummary {
|
|
30
|
-
const runningAgentIds = new Set<string>();
|
|
31
|
-
let progress: string | undefined;
|
|
32
|
-
|
|
33
|
-
for (const agent of managerAgents) {
|
|
34
|
-
runningAgentIds.add(agent.id);
|
|
35
|
-
if (!progress && agent.progress) progress = agent.progress;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
for (const agent of runtimeAgents) {
|
|
39
|
-
runningAgentIds.add(agent.id);
|
|
40
|
-
if (!progress && agent.latestProgress) progress = agent.latestProgress;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
for (const chain of wrfcChains) {
|
|
44
|
-
if (isTerminalWrfcState(chain.state)) continue;
|
|
45
|
-
const chainAgentIds = collectChainAgentIds(chain);
|
|
46
|
-
const hasVisibleChainWork = Array.from(chainAgentIds).some((id) => runningAgentIds.has(id));
|
|
47
|
-
if (!hasVisibleChainWork || !chain.ownerAgentId) continue;
|
|
48
|
-
runningAgentIds.add(chain.ownerAgentId);
|
|
49
|
-
if (!progress) progress = `WRFC chain ${chain.state ?? 'running'}`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return { count: runningAgentIds.size, progress };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function isTerminalWrfcState(state: string | undefined): boolean {
|
|
56
|
-
return state === 'passed' || state === 'failed';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function collectChainAgentIds(chain: WrfcProcessSummaryChain): Set<string> {
|
|
60
|
-
return new Set([
|
|
61
|
-
chain.ownerAgentId,
|
|
62
|
-
chain.engineerAgentId,
|
|
63
|
-
chain.reviewerAgentId,
|
|
64
|
-
chain.fixerAgentId,
|
|
65
|
-
...(chain.allAgentIds ?? []),
|
|
66
|
-
].filter((id): id is string => typeof id === 'string' && id.length > 0));
|
|
67
|
-
}
|