@synergenius/flow-weaver-pack-weaver 0.9.159 → 0.9.164
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/dist/ai-chat-provider.d.ts.map +1 -1
- package/dist/ai-chat-provider.js +17 -11
- package/dist/ai-chat-provider.js.map +1 -1
- package/dist/bot/ai-router.js +5 -5
- package/dist/bot/ai-router.js.map +1 -1
- package/dist/bot/assistant-tools.d.ts.map +1 -1
- package/dist/bot/assistant-tools.js +6 -7
- package/dist/bot/assistant-tools.js.map +1 -1
- package/dist/bot/capability-registry.d.ts.map +1 -1
- package/dist/bot/capability-registry.js +37 -14
- package/dist/bot/capability-registry.js.map +1 -1
- package/dist/bot/dashboard.js +1 -1
- package/dist/bot/dashboard.js.map +1 -1
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.d.ts.map +1 -1
- package/dist/bot/index.js.map +1 -1
- package/dist/bot/instance-manager.js +3 -3
- package/dist/bot/instance-manager.js.map +1 -1
- package/dist/bot/profile-store.d.ts.map +1 -1
- package/dist/bot/profile-store.js +11 -9
- package/dist/bot/profile-store.js.map +1 -1
- package/dist/bot/profile-types.d.ts +2 -2
- package/dist/bot/profile-types.d.ts.map +1 -1
- package/dist/bot/runner.d.ts +1 -0
- package/dist/bot/runner.d.ts.map +1 -1
- package/dist/bot/runner.js +6 -2
- package/dist/bot/runner.js.map +1 -1
- package/dist/bot/step-executor.d.ts.map +1 -1
- package/dist/bot/step-executor.js +10 -0
- package/dist/bot/step-executor.js.map +1 -1
- package/dist/bot/swarm-controller.d.ts +3 -5
- package/dist/bot/swarm-controller.d.ts.map +1 -1
- package/dist/bot/swarm-controller.js +157 -74
- package/dist/bot/swarm-controller.js.map +1 -1
- package/dist/bot/task-prompt-builder.d.ts +2 -3
- package/dist/bot/task-prompt-builder.d.ts.map +1 -1
- package/dist/bot/task-prompt-builder.js +81 -67
- package/dist/bot/task-prompt-builder.js.map +1 -1
- package/dist/bot/task-store.d.ts +3 -3
- package/dist/bot/task-store.d.ts.map +1 -1
- package/dist/bot/task-store.js +89 -75
- package/dist/bot/task-store.js.map +1 -1
- package/dist/bot/task-types.d.ts +54 -26
- package/dist/bot/task-types.d.ts.map +1 -1
- package/dist/bot/task-types.js +6 -2
- package/dist/bot/task-types.js.map +1 -1
- package/dist/bot/types.d.ts +2 -0
- package/dist/bot/types.d.ts.map +1 -1
- package/dist/bot/weaver-tools.d.ts.map +1 -1
- package/dist/bot/weaver-tools.js +10 -0
- package/dist/bot/weaver-tools.js.map +1 -1
- package/dist/cli-handlers.d.ts +0 -1
- package/dist/cli-handlers.d.ts.map +1 -1
- package/dist/cli-handlers.js +5 -9
- package/dist/cli-handlers.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/node-types/agent-execute.d.ts.map +1 -1
- package/dist/node-types/agent-execute.js +95 -63
- package/dist/node-types/agent-execute.js.map +1 -1
- package/dist/node-types/plan-task.js +8 -8
- package/dist/node-types/plan-task.js.map +1 -1
- package/dist/ui/bot-panel.js +1 -1
- package/dist/ui/capability-editor.js +37 -14
- package/dist/ui/chat-task-result.js +1 -7
- package/dist/ui/profile-editor.js +37 -14
- package/dist/ui/swarm-controls.js +2 -2
- package/dist/ui/swarm-dashboard.js +72 -109
- package/dist/ui/task-detail-view.js +21 -42
- package/dist/ui/task-editor.js +13 -50
- package/dist/ui/task-pool-list.js +0 -2
- package/flowweaver.manifest.json +1 -1
- package/package.json +1 -1
- package/src/ai-chat-provider.ts +15 -11
- package/src/bot/ai-router.ts +5 -5
- package/src/bot/assistant-tools.ts +6 -7
- package/src/bot/capability-registry.ts +37 -14
- package/src/bot/dashboard.ts +1 -1
- package/src/bot/index.ts +5 -1
- package/src/bot/instance-manager.ts +3 -3
- package/src/bot/profile-store.ts +12 -10
- package/src/bot/profile-types.ts +2 -2
- package/src/bot/runner.ts +6 -2
- package/src/bot/step-executor.ts +11 -0
- package/src/bot/swarm-controller.ts +164 -78
- package/src/bot/task-prompt-builder.ts +86 -71
- package/src/bot/task-store.ts +101 -78
- package/src/bot/task-types.ts +81 -36
- package/src/bot/types.ts +2 -0
- package/src/bot/weaver-tools.ts +11 -0
- package/src/cli-handlers.ts +5 -9
- package/src/index.ts +6 -0
- package/src/node-types/agent-execute.ts +99 -62
- package/src/node-types/plan-task.ts +8 -8
- package/src/ui/bot-panel.tsx +3 -3
- package/src/ui/chat-task-result.tsx +5 -14
- package/src/ui/swarm-controls.tsx +3 -3
- package/src/ui/swarm-dashboard.tsx +3 -3
- package/src/ui/task-detail-view.tsx +29 -52
- package/src/ui/task-editor.tsx +14 -51
- package/src/ui/task-pool-list.tsx +1 -3
|
@@ -177,14 +177,16 @@ export async function weaverAgentExecute(
|
|
|
177
177
|
|
|
178
178
|
auditEmit('run-start', { task: task.instruction });
|
|
179
179
|
|
|
180
|
+
const filesCreated: string[] = [];
|
|
181
|
+
const filesModified: string[] = [];
|
|
182
|
+
const stepLog: LocalStepLogEntry[] = [];
|
|
183
|
+
const checks: Record<string, string> = {};
|
|
184
|
+
const taskStart = Date.now();
|
|
185
|
+
|
|
180
186
|
try {
|
|
181
187
|
const provider = await createProvider(pInfo, projectDir);
|
|
182
188
|
const executor = createWeaverExecutor(projectDir);
|
|
183
189
|
|
|
184
|
-
const filesModified: string[] = [];
|
|
185
|
-
const stepLog: LocalStepLogEntry[] = [];
|
|
186
|
-
const taskStart = Date.now();
|
|
187
|
-
|
|
188
190
|
// Route tool events through renderer + track state
|
|
189
191
|
const onToolEvent = (event: ToolEvent) => {
|
|
190
192
|
renderer.onToolEvent(event);
|
|
@@ -195,8 +197,22 @@ export async function weaverAgentExecute(
|
|
|
195
197
|
status: event.isError ? 'error' : 'ok',
|
|
196
198
|
detail: event.isError ? (event.result ?? '').slice(0, 200) : event.name,
|
|
197
199
|
});
|
|
198
|
-
|
|
199
|
-
|
|
200
|
+
|
|
201
|
+
// Track file changes
|
|
202
|
+
if (!event.isError && event.args?.file) {
|
|
203
|
+
if (event.name === 'write_file') filesCreated.push(event.args.file as string);
|
|
204
|
+
if (event.name === 'patch_file') filesModified.push(event.args.file as string);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Capture automated check results from tool output
|
|
208
|
+
if (event.name === 'run_shell' && event.result) {
|
|
209
|
+
const output = event.result;
|
|
210
|
+
if (event.args?.command && String(event.args.command).includes('tsc')) {
|
|
211
|
+
checks.tsc = event.isError ? output.slice(0, 500) : 'pass';
|
|
212
|
+
}
|
|
213
|
+
if (event.args?.command && (String(event.args.command).includes('vitest') || String(event.args.command).includes('npm test'))) {
|
|
214
|
+
checks.tests = event.isError ? output.slice(0, 500) : 'pass';
|
|
215
|
+
}
|
|
200
216
|
}
|
|
201
217
|
}
|
|
202
218
|
};
|
|
@@ -227,84 +243,93 @@ export async function weaverAgentExecute(
|
|
|
227
243
|
outputTokens: usage.completionTokens,
|
|
228
244
|
});
|
|
229
245
|
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
246
|
+
const uniqueCreated = [...new Set(filesCreated)];
|
|
247
|
+
const uniqueModified = [...new Set(filesModified)];
|
|
248
|
+
const durationMs = Date.now() - taskStart;
|
|
249
|
+
|
|
250
|
+
// --- Determine outcome ---
|
|
251
|
+
// Extract remainingWork and blockers from AI's last text message
|
|
252
|
+
const lastMsg = result.messages?.filter((m: { role: string }) => m.role === 'assistant').pop();
|
|
253
|
+
const lastText = typeof lastMsg?.content === 'string' ? lastMsg.content : '';
|
|
254
|
+
|
|
255
|
+
let remainingWork: string | undefined;
|
|
256
|
+
let blockers: string[] | undefined;
|
|
257
|
+
|
|
258
|
+
// Check for explicit BLOCKED/NEEDS_CONTEXT signals
|
|
259
|
+
const blockedMatch = lastText.match(/(?:BLOCKED|NEEDS_CONTEXT)[:\s]*(.+?)(?:\n|$)/i);
|
|
260
|
+
if (blockedMatch) {
|
|
261
|
+
blockers = [blockedMatch[1].trim()];
|
|
245
262
|
}
|
|
246
263
|
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
const errorRate = errorCount / stepLog.length;
|
|
252
|
-
if (errorRate > 0.5) {
|
|
253
|
-
validationPassed = false;
|
|
254
|
-
renderer.warn(
|
|
255
|
-
`Step error rate too high (${errorCount}/${stepLog.length} = ${Math.round(errorRate * 100)}%) — task marked as failed`,
|
|
256
|
-
);
|
|
257
|
-
}
|
|
264
|
+
// Check for remaining work mentions
|
|
265
|
+
const remainingMatch = lastText.match(/(?:remaining|still need|todo|left to do)[:\s]*(.+?)(?:\n\n|$)/i);
|
|
266
|
+
if (remainingMatch && !blockers) {
|
|
267
|
+
remainingWork = remainingMatch[1].trim().slice(0, 500);
|
|
258
268
|
}
|
|
259
269
|
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
.filter((f: string) => f && f.includes('/'));
|
|
270
|
-
const { existsSync } = await import('node:fs');
|
|
271
|
-
const { resolve } = await import('node:path');
|
|
272
|
-
const missing = expectedFiles.filter((f: string) => !existsSync(resolve(projectDir, f)));
|
|
273
|
-
if (missing.length > 0) {
|
|
274
|
-
validationPassed = false;
|
|
275
|
-
renderer.warn(`Expected files not found: ${missing.join(', ')} — task marked as failed`);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
270
|
+
// Determine outcome based on convergent model
|
|
271
|
+
let outcome: 'contributed' | 'completed' | 'stalled' | 'crashed';
|
|
272
|
+
if (blockers && blockers.length > 0) {
|
|
273
|
+
outcome = 'stalled';
|
|
274
|
+
} else if (result.success && result.toolCallCount > 0 && !remainingWork) {
|
|
275
|
+
outcome = 'completed';
|
|
276
|
+
} else {
|
|
277
|
+
// Made progress (files changed) or at least tried (tool calls > 0)
|
|
278
|
+
outcome = 'contributed';
|
|
278
279
|
}
|
|
279
280
|
|
|
281
|
+
// Build RunProgress — this is what gets stored on the task
|
|
282
|
+
const runProgressJson = JSON.stringify({
|
|
283
|
+
outcome,
|
|
284
|
+
filesCreated: uniqueCreated,
|
|
285
|
+
filesModified: uniqueModified,
|
|
286
|
+
summary: result.summary || `${outcome}: ${result.toolCallCount} tool calls`,
|
|
287
|
+
remainingWork,
|
|
288
|
+
blockers,
|
|
289
|
+
checks: Object.keys(checks).length > 0 ? checks : undefined,
|
|
290
|
+
tokensUsed: usage.promptTokens + usage.completionTokens,
|
|
291
|
+
cost: estimatedCost,
|
|
292
|
+
durationMs,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Legacy context fields for backward compat with workflow nodes that read them
|
|
280
296
|
context.resultJson = JSON.stringify({
|
|
281
|
-
success:
|
|
297
|
+
success: outcome === 'completed',
|
|
282
298
|
summary: result.summary,
|
|
283
299
|
toolCallCount: result.toolCallCount,
|
|
300
|
+
outcome,
|
|
284
301
|
usage: { inputTokens: usage.promptTokens, outputTokens: usage.completionTokens, estimatedCost },
|
|
285
302
|
});
|
|
286
|
-
context.filesModified = JSON.stringify(
|
|
303
|
+
context.filesModified = JSON.stringify([...uniqueCreated, ...uniqueModified]);
|
|
287
304
|
context.stepLogJson = JSON.stringify(stepLog);
|
|
288
|
-
context.allValid =
|
|
305
|
+
context.allValid = outcome === 'completed';
|
|
306
|
+
// Attach RunProgress as a dedicated context field for the swarm controller
|
|
307
|
+
(context as unknown as Record<string, unknown>).runProgressJson = runProgressJson;
|
|
289
308
|
|
|
290
309
|
auditEmit('run-complete', {
|
|
291
|
-
success:
|
|
310
|
+
success: outcome === 'completed',
|
|
311
|
+
outcome,
|
|
292
312
|
toolCalls: result.toolCallCount,
|
|
293
|
-
|
|
313
|
+
filesCreated: uniqueCreated.length,
|
|
314
|
+
filesModified: uniqueModified.length,
|
|
294
315
|
tokens: { in: usage.promptTokens, out: usage.completionTokens },
|
|
295
316
|
estimatedCost,
|
|
296
317
|
});
|
|
297
318
|
|
|
298
|
-
renderer.taskEnd(
|
|
319
|
+
renderer.taskEnd(outcome === 'completed', {
|
|
299
320
|
toolCalls: result.toolCallCount,
|
|
300
321
|
inputTokens: usage.promptTokens,
|
|
301
322
|
outputTokens: usage.completionTokens,
|
|
302
323
|
estimatedCost,
|
|
303
|
-
filesModified:
|
|
304
|
-
elapsed:
|
|
324
|
+
filesModified: uniqueCreated.length + uniqueModified.length,
|
|
325
|
+
elapsed: durationMs,
|
|
305
326
|
});
|
|
306
327
|
|
|
307
|
-
return {
|
|
328
|
+
return {
|
|
329
|
+
onSuccess: outcome === 'completed',
|
|
330
|
+
onFailure: outcome !== 'completed',
|
|
331
|
+
ctx: JSON.stringify(context),
|
|
332
|
+
};
|
|
308
333
|
} catch (err: unknown) {
|
|
309
334
|
const msg = err instanceof Error ? err.message : String(err);
|
|
310
335
|
let errorDetail = msg;
|
|
@@ -312,10 +337,22 @@ export async function weaverAgentExecute(
|
|
|
312
337
|
if (guidance) errorDetail = `${msg}\n Hint: ${guidance}`;
|
|
313
338
|
renderer.error('Agent error', errorDetail);
|
|
314
339
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
340
|
+
// Crashed — infrastructure failure
|
|
341
|
+
const crashProgress = JSON.stringify({
|
|
342
|
+
outcome: 'crashed',
|
|
343
|
+
filesCreated: [...new Set(filesCreated)],
|
|
344
|
+
filesModified: [...new Set(filesModified)],
|
|
345
|
+
summary: `Crashed: ${msg.slice(0, 200)}`,
|
|
346
|
+
tokensUsed: 0,
|
|
347
|
+
cost: 0,
|
|
348
|
+
durationMs: Date.now() - taskStart,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
context.resultJson = JSON.stringify({ success: false, error: msg, outcome: 'crashed' });
|
|
352
|
+
context.filesModified = JSON.stringify([...new Set([...filesCreated, ...filesModified])]);
|
|
353
|
+
context.stepLogJson = JSON.stringify(stepLog);
|
|
318
354
|
context.allValid = false;
|
|
355
|
+
(context as unknown as Record<string, unknown>).runProgressJson = crashProgress;
|
|
319
356
|
|
|
320
357
|
return { onSuccess: false, onFailure: true, ctx: JSON.stringify(context) };
|
|
321
358
|
}
|
|
@@ -214,15 +214,15 @@ Rules:
|
|
|
214
214
|
2. Use patch_file for modifications, write_file only for new files.
|
|
215
215
|
3. Verify your work by running tests or tsc when appropriate.`;
|
|
216
216
|
|
|
217
|
-
// Append retry context when this is a
|
|
218
|
-
const
|
|
219
|
-
if (
|
|
220
|
-
const retryParts: string[] = ['\n\n--- RETRY CONTEXT (
|
|
221
|
-
|
|
222
|
-
retryParts.push('Previous attempt failed with error: ' + task.lastError);
|
|
223
|
-
}
|
|
224
|
-
const summaries: Array<{ outcome?: string; filesModified?: string[]; summary?: string }> = task.runSummaries ?? [];
|
|
217
|
+
// Append retry context when this is a convergent re-run (runHistory has prior entries)
|
|
218
|
+
const runCount = task.runHistory?.length ?? 0;
|
|
219
|
+
if (runCount > 0) {
|
|
220
|
+
const retryParts: string[] = ['\n\n--- RETRY CONTEXT (run ' + (runCount + 1) + ') ---'];
|
|
221
|
+
const summaries: Array<{ outcome?: string; filesModified?: string[]; summary?: string; error?: string }> = task.runHistory ?? [];
|
|
225
222
|
const lastSummary = summaries.length > 0 ? summaries[summaries.length - 1] : undefined;
|
|
223
|
+
if (lastSummary?.error) {
|
|
224
|
+
retryParts.push('Previous attempt failed with error: ' + lastSummary.error);
|
|
225
|
+
}
|
|
226
226
|
if (lastSummary) {
|
|
227
227
|
if (lastSummary.outcome) retryParts.push('Last outcome: ' + lastSummary.outcome);
|
|
228
228
|
if (lastSummary.summary) retryParts.push('Last summary: ' + lastSummary.summary);
|
package/src/ui/bot-panel.tsx
CHANGED
|
@@ -32,7 +32,7 @@ interface InstanceInfo {
|
|
|
32
32
|
tokensUsed: number;
|
|
33
33
|
cost: number;
|
|
34
34
|
tasksCompleted: number;
|
|
35
|
-
|
|
35
|
+
runsIncomplete: number;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
interface SwarmStatus {
|
|
@@ -40,7 +40,7 @@ interface SwarmStatus {
|
|
|
40
40
|
instances: Record<string, InstanceInfo>;
|
|
41
41
|
maxConcurrent: number;
|
|
42
42
|
tasksCompleted: number;
|
|
43
|
-
|
|
43
|
+
runsIncomplete: number;
|
|
44
44
|
totalTokensUsed: number;
|
|
45
45
|
totalCost: number;
|
|
46
46
|
startedAt?: string;
|
|
@@ -237,7 +237,7 @@ function BotPanel() {
|
|
|
237
237
|
),
|
|
238
238
|
React.createElement(Flex, { variant: 'column-center-center-nowrap-1' },
|
|
239
239
|
React.createElement(Typography, { variant: 'smallCaption-thick', color: 'color-text-high' },
|
|
240
|
-
String(swarm?.
|
|
240
|
+
String(swarm?.runsIncomplete ?? 0),
|
|
241
241
|
),
|
|
242
242
|
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-subtle' }, 'Failed'),
|
|
243
243
|
),
|
|
@@ -18,18 +18,15 @@ const { Flex, Typography, StatusIcon, Button } = require('@fw/plugin-ui-kit');
|
|
|
18
18
|
interface TaskData {
|
|
19
19
|
id: string;
|
|
20
20
|
title: string;
|
|
21
|
-
status: 'open' | '
|
|
21
|
+
status: 'open' | 'in-progress' | 'done' | 'cancelled';
|
|
22
22
|
isParent: boolean;
|
|
23
|
-
currentBotId?: string;
|
|
24
23
|
assignedProfile?: string;
|
|
25
|
-
attempt?: number;
|
|
26
|
-
maxAttempts?: number;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
interface SubtaskData {
|
|
30
27
|
id: string;
|
|
31
28
|
title: string;
|
|
32
|
-
status: 'open' | '
|
|
29
|
+
status: 'open' | 'in-progress' | 'done' | 'cancelled';
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
interface ChatTaskResultProps {
|
|
@@ -62,8 +59,6 @@ type StatusKind = 'running' | 'completed' | 'failed' | 'pending';
|
|
|
62
59
|
function statusToIcon(status: TaskData['status']): StatusKind {
|
|
63
60
|
switch (status) {
|
|
64
61
|
case 'open':
|
|
65
|
-
case 'pending':
|
|
66
|
-
case 'blocked':
|
|
67
62
|
return 'pending';
|
|
68
63
|
case 'in-progress':
|
|
69
64
|
return 'running';
|
|
@@ -79,9 +74,7 @@ function statusToIcon(status: TaskData['status']): StatusKind {
|
|
|
79
74
|
function statusLabel(status: TaskData['status']): string {
|
|
80
75
|
switch (status) {
|
|
81
76
|
case 'open': return 'Open';
|
|
82
|
-
case 'pending': return 'Queued';
|
|
83
77
|
case 'in-progress': return 'Running';
|
|
84
|
-
case 'blocked': return 'Blocked';
|
|
85
78
|
case 'done': return 'Done';
|
|
86
79
|
case 'cancelled': return 'Cancelled';
|
|
87
80
|
default: return status;
|
|
@@ -159,11 +152,9 @@ function ChatTaskResult(props: ChatTaskResultProps | null) {
|
|
|
159
152
|
const hasSubtasks = task.isParent && totalCount > 0;
|
|
160
153
|
|
|
161
154
|
// Bot info
|
|
162
|
-
const botLabel = task.
|
|
163
|
-
? `
|
|
164
|
-
:
|
|
165
|
-
? `Profile: ${task.assignedProfile}`
|
|
166
|
-
: null;
|
|
155
|
+
const botLabel = task.assignedProfile
|
|
156
|
+
? `Profile: ${task.assignedProfile}`
|
|
157
|
+
: null;
|
|
167
158
|
|
|
168
159
|
const handleOpenDashboard = useCallback(() => {
|
|
169
160
|
openWorkspace({
|
|
@@ -24,7 +24,7 @@ interface SwarmStatus {
|
|
|
24
24
|
instances: Record<string, InstanceInfo>;
|
|
25
25
|
maxConcurrent: number;
|
|
26
26
|
tasksCompleted: number;
|
|
27
|
-
|
|
27
|
+
runsIncomplete: number;
|
|
28
28
|
totalTokensUsed: number;
|
|
29
29
|
totalCost: number;
|
|
30
30
|
startedAt?: string;
|
|
@@ -205,10 +205,10 @@ function SwarmControls({ swarmStatus, onRefresh }: SwarmControlsProps) {
|
|
|
205
205
|
variant: 'caption-regular',
|
|
206
206
|
color: 'color-text-subtle',
|
|
207
207
|
}, `${swarmStatus.tasksCompleted} done`),
|
|
208
|
-
swarmStatus && swarmStatus.
|
|
208
|
+
swarmStatus && swarmStatus.runsIncomplete > 0 && React.createElement(Typography, {
|
|
209
209
|
variant: 'caption-regular',
|
|
210
210
|
color: 'color-status-negative',
|
|
211
|
-
}, `${swarmStatus.
|
|
211
|
+
}, `${swarmStatus.runsIncomplete} incomplete`),
|
|
212
212
|
),
|
|
213
213
|
|
|
214
214
|
// Right: action buttons
|
|
@@ -51,7 +51,7 @@ interface InstanceInfo {
|
|
|
51
51
|
tokensUsed: number;
|
|
52
52
|
cost: number;
|
|
53
53
|
tasksCompleted: number;
|
|
54
|
-
|
|
54
|
+
runsIncomplete: number;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
interface SwarmBudgetLayer {
|
|
@@ -66,7 +66,7 @@ interface SwarmStatus {
|
|
|
66
66
|
instances: Record<string, InstanceInfo>;
|
|
67
67
|
maxConcurrent: number;
|
|
68
68
|
tasksCompleted: number;
|
|
69
|
-
|
|
69
|
+
runsIncomplete: number;
|
|
70
70
|
totalTokensUsed: number;
|
|
71
71
|
totalCost: number;
|
|
72
72
|
budgets?: {
|
|
@@ -77,7 +77,7 @@ interface SwarmStatus {
|
|
|
77
77
|
packVersion?: string;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
type TaskStatus = 'open' | '
|
|
80
|
+
type TaskStatus = 'open' | 'in-progress' | 'done' | 'cancelled';
|
|
81
81
|
|
|
82
82
|
interface PoolTask {
|
|
83
83
|
id: string;
|
|
@@ -24,23 +24,27 @@ import type { HistoricalRun } from './trace-to-timeline';
|
|
|
24
24
|
// Types
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
|
|
27
|
-
type TaskStatus = 'open' | '
|
|
27
|
+
type TaskStatus = 'open' | 'in-progress' | 'done' | 'cancelled';
|
|
28
28
|
|
|
29
29
|
interface TaskContext {
|
|
30
30
|
files: string[];
|
|
31
31
|
notes: string;
|
|
32
|
-
|
|
32
|
+
runHistory: Array<{
|
|
33
33
|
runId: string;
|
|
34
34
|
botId: string;
|
|
35
|
+
profileId: string;
|
|
35
36
|
outcome: string;
|
|
36
37
|
summary: string;
|
|
38
|
+
filesCreated: string[];
|
|
37
39
|
filesModified: string[];
|
|
38
|
-
|
|
40
|
+
remainingWork?: string;
|
|
41
|
+
checks?: Record<string, string>;
|
|
39
42
|
durationMs: number;
|
|
40
43
|
tokensUsed: number;
|
|
41
44
|
cost: number;
|
|
45
|
+
endedAt: string;
|
|
42
46
|
}>;
|
|
43
|
-
|
|
47
|
+
stagnationCount: number;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
interface Task {
|
|
@@ -52,13 +56,8 @@ interface Task {
|
|
|
52
56
|
isParent: boolean;
|
|
53
57
|
parentId?: string;
|
|
54
58
|
dependsOn: string[];
|
|
55
|
-
|
|
56
|
-
currentRunId?: string;
|
|
59
|
+
activeRunId?: string;
|
|
57
60
|
context: TaskContext;
|
|
58
|
-
runs: string[];
|
|
59
|
-
attempt: number;
|
|
60
|
-
maxAttempts: number;
|
|
61
|
-
budgetTokens?: number;
|
|
62
61
|
budgetCost?: number;
|
|
63
62
|
tokensUsed: number;
|
|
64
63
|
costUsed: number;
|
|
@@ -75,7 +74,6 @@ interface Subtask {
|
|
|
75
74
|
status: TaskStatus;
|
|
76
75
|
priority: number;
|
|
77
76
|
assignedProfile?: string;
|
|
78
|
-
attempt: number;
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
interface TaskDetailViewProps {
|
|
@@ -90,19 +88,15 @@ interface TaskDetailViewProps {
|
|
|
90
88
|
|
|
91
89
|
const statusToIcon: Record<TaskStatus, string> = {
|
|
92
90
|
'open': 'pending',
|
|
93
|
-
'pending': 'pending',
|
|
94
91
|
'in-progress': 'running',
|
|
95
92
|
'done': 'completed',
|
|
96
|
-
'blocked': 'pending',
|
|
97
93
|
'cancelled': 'failed',
|
|
98
94
|
};
|
|
99
95
|
|
|
100
96
|
const statusToLabel: Record<TaskStatus, string> = {
|
|
101
97
|
'open': 'Open',
|
|
102
|
-
'pending': 'Queued',
|
|
103
98
|
'in-progress': 'Running',
|
|
104
99
|
'done': 'Done',
|
|
105
|
-
'blocked': 'Blocked',
|
|
106
100
|
'cancelled': 'Cancelled',
|
|
107
101
|
};
|
|
108
102
|
|
|
@@ -191,10 +185,6 @@ function SubtaskRowItem({ sub, onBack }: { sub: Subtask; onBack: () => void }) {
|
|
|
191
185
|
sub.assignedProfile && React.createElement(Chip, {
|
|
192
186
|
label: sub.assignedProfile, size: 'small', color: 'color-status-info',
|
|
193
187
|
}),
|
|
194
|
-
React.createElement(Typography, {
|
|
195
|
-
variant: 'caption-regular',
|
|
196
|
-
color: 'color-text-subtle',
|
|
197
|
-
}, `#${sub.attempt}`),
|
|
198
188
|
);
|
|
199
189
|
}
|
|
200
190
|
|
|
@@ -223,9 +213,9 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
|
|
|
223
213
|
const t = (data.task ?? data) as Task;
|
|
224
214
|
setTask(t);
|
|
225
215
|
|
|
226
|
-
// Populate run history from task's
|
|
216
|
+
// Populate run history from task's runHistory (RunStore may be empty for swarm tasks)
|
|
227
217
|
const rs = (t as unknown as Record<string, unknown>).context as Record<string, unknown> | undefined;
|
|
228
|
-
const summaries = rs?.
|
|
218
|
+
const summaries = rs?.runHistory as Array<Record<string, unknown>> | undefined;
|
|
229
219
|
if (summaries?.length) {
|
|
230
220
|
setHistory(prev => {
|
|
231
221
|
if (prev.length > 0) return prev; // Don't overwrite if RunStore data exists
|
|
@@ -307,14 +297,14 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
|
|
|
307
297
|
report: liveReport,
|
|
308
298
|
} = useStreamTimeline(stream.events, stream.isDone);
|
|
309
299
|
|
|
310
|
-
const
|
|
311
|
-
const isLive = task?.status === 'in-progress' && !!
|
|
300
|
+
const activeRunId = task?.activeRunId;
|
|
301
|
+
const isLive = task?.status === 'in-progress' && !!activeRunId;
|
|
312
302
|
|
|
313
303
|
useEffect(() => {
|
|
314
|
-
if (!isLive || !
|
|
315
|
-
stream.start(packId, 'fw_weaver_events',
|
|
304
|
+
if (!isLive || !activeRunId) return;
|
|
305
|
+
stream.start(packId, 'fw_weaver_events', activeRunId);
|
|
316
306
|
return () => stream.stop();
|
|
317
|
-
}, [isLive,
|
|
307
|
+
}, [isLive, activeRunId, packId]);
|
|
318
308
|
|
|
319
309
|
// ── Detail tab state ──
|
|
320
310
|
const [detailTab, setDetailTab] = useState('runs');
|
|
@@ -573,7 +563,6 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
|
|
|
573
563
|
color: task.status === 'done' ? 'color-status-positive'
|
|
574
564
|
: task.status === 'cancelled' ? 'color-status-negative'
|
|
575
565
|
: task.status === 'in-progress' ? 'color-status-info'
|
|
576
|
-
: task.status === 'blocked' ? 'color-status-caution'
|
|
577
566
|
: 'color-brand-alt',
|
|
578
567
|
}),
|
|
579
568
|
|
|
@@ -583,10 +572,10 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
|
|
|
583
572
|
label: `P${task.priority}`, size: 'small', color: task.priority >= 3 ? 'color-status-caution' : 'color-status-info',
|
|
584
573
|
}),
|
|
585
574
|
|
|
586
|
-
task.
|
|
575
|
+
(task.context?.runHistory?.length ?? 0) > 0 && React.createElement(Typography, {
|
|
587
576
|
variant: 'smallCaption-regular',
|
|
588
577
|
color: 'color-text-subtle',
|
|
589
|
-
}, `${task.
|
|
578
|
+
}, `${task.context.runHistory.length} run${task.context.runHistory.length !== 1 ? 's' : ''}`),
|
|
590
579
|
),
|
|
591
580
|
|
|
592
581
|
// Description
|
|
@@ -628,7 +617,7 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
|
|
|
628
617
|
React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8', style: { padding: '12px 16px' } },
|
|
629
618
|
|
|
630
619
|
// ── Runs tab ──
|
|
631
|
-
detailTab === 'runs' && (hasRuns || (task.
|
|
620
|
+
detailTab === 'runs' && (hasRuns || (task.context?.runHistory?.length ?? 0) > 0)
|
|
632
621
|
? React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
|
|
633
622
|
// Historical runs
|
|
634
623
|
...runItems.map(({ run, runTimeline }: { run: HistoricalRun; runTimeline: Array<Record<string, unknown>> }) => {
|
|
@@ -696,14 +685,14 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
|
|
|
696
685
|
React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
|
|
697
686
|
React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Status'),
|
|
698
687
|
React.createElement(Flex, { variant: 'row-center-start-nowrap-6' },
|
|
699
|
-
(task.status === 'open' && (task.
|
|
688
|
+
(task.status === 'open' && (task.context?.runHistory?.length ?? 0) > 0) && React.createElement(Button, {
|
|
700
689
|
size: 'xs', variant: 'fill', color: 'primary',
|
|
701
690
|
onClick: handleRetry,
|
|
702
691
|
loading: actionLoading === 'retry',
|
|
703
692
|
disabled: !!actionLoading,
|
|
704
693
|
}, 'Retry Task'),
|
|
705
694
|
|
|
706
|
-
(task.status === 'open' || task.status === '
|
|
695
|
+
(task.status === 'open' || task.status === 'in-progress') && React.createElement(Button, {
|
|
707
696
|
size: 'xs', variant: 'outlined', color: 'danger',
|
|
708
697
|
onClick: handleCancel,
|
|
709
698
|
loading: actionLoading === 'cancel',
|
|
@@ -822,24 +811,12 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
|
|
|
822
811
|
}, task.context.notes),
|
|
823
812
|
),
|
|
824
813
|
|
|
825
|
-
//
|
|
826
|
-
task.context?.
|
|
827
|
-
variant: 'column-stretch-start-nowrap-4',
|
|
828
|
-
},
|
|
829
|
-
React.createElement(Typography, { variant: 'caption-thick', color: 'color-status-negative' }, 'Last Error'),
|
|
830
|
-
React.createElement(Typography, {
|
|
831
|
-
variant: 'smallCaption-regular',
|
|
832
|
-
color: 'color-text-high',
|
|
833
|
-
style: { fontFamily: 'var(--font-mono, monospace)', whiteSpace: 'pre-wrap' },
|
|
834
|
-
}, task.context.lastError),
|
|
835
|
-
),
|
|
836
|
-
|
|
837
|
-
// Run summaries (accumulated context)
|
|
838
|
-
(task.context?.runSummaries?.length ?? 0) > 0 && React.createElement(Flex, {
|
|
814
|
+
// Run history (accumulated context)
|
|
815
|
+
(task.context?.runHistory?.length ?? 0) > 0 && React.createElement(Flex, {
|
|
839
816
|
variant: 'column-stretch-start-nowrap-6',
|
|
840
817
|
},
|
|
841
|
-
React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Run
|
|
842
|
-
...(task.context?.
|
|
818
|
+
React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Run History'),
|
|
819
|
+
...(task.context?.runHistory ?? []).map((rs: Record<string, unknown>, i: number) =>
|
|
843
820
|
React.createElement(Card, {
|
|
844
821
|
key: `rs-${i}`,
|
|
845
822
|
variant: 'bordered',
|
|
@@ -862,9 +839,9 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
|
|
|
862
839
|
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
863
840
|
style: { fontFamily: 'var(--font-mono, monospace)' },
|
|
864
841
|
}, `Files: ${(rs.filesModified as string[]).join(', ')}`),
|
|
865
|
-
rs.
|
|
866
|
-
variant: 'smallCaption-regular', color: 'color-status-
|
|
867
|
-
}, `
|
|
842
|
+
rs.remainingWork && React.createElement(Typography, {
|
|
843
|
+
variant: 'smallCaption-regular', color: 'color-status-caution',
|
|
844
|
+
}, `Remaining: ${rs.remainingWork}`),
|
|
868
845
|
),
|
|
869
846
|
),
|
|
870
847
|
),
|
|
@@ -876,7 +853,7 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
|
|
|
876
853
|
React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Budget'),
|
|
877
854
|
React.createElement(Flex, { variant: 'row-center-start-nowrap-16' },
|
|
878
855
|
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
|
|
879
|
-
`Tokens: ${task.tokensUsed?.toLocaleString() ?? 0}
|
|
856
|
+
`Tokens: ${task.tokensUsed?.toLocaleString() ?? 0}`),
|
|
880
857
|
React.createElement(Typography, { variant: 'smallCaption-regular', color: 'color-text-high' },
|
|
881
858
|
`Cost: $${(task.costUsed ?? 0).toFixed(3)}${task.budgetCost ? ` / $${task.budgetCost.toFixed(2)}` : ''}`),
|
|
882
859
|
),
|