@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.
Files changed (102) hide show
  1. package/dist/ai-chat-provider.d.ts.map +1 -1
  2. package/dist/ai-chat-provider.js +17 -11
  3. package/dist/ai-chat-provider.js.map +1 -1
  4. package/dist/bot/ai-router.js +5 -5
  5. package/dist/bot/ai-router.js.map +1 -1
  6. package/dist/bot/assistant-tools.d.ts.map +1 -1
  7. package/dist/bot/assistant-tools.js +6 -7
  8. package/dist/bot/assistant-tools.js.map +1 -1
  9. package/dist/bot/capability-registry.d.ts.map +1 -1
  10. package/dist/bot/capability-registry.js +37 -14
  11. package/dist/bot/capability-registry.js.map +1 -1
  12. package/dist/bot/dashboard.js +1 -1
  13. package/dist/bot/dashboard.js.map +1 -1
  14. package/dist/bot/index.d.ts +1 -1
  15. package/dist/bot/index.d.ts.map +1 -1
  16. package/dist/bot/index.js.map +1 -1
  17. package/dist/bot/instance-manager.js +3 -3
  18. package/dist/bot/instance-manager.js.map +1 -1
  19. package/dist/bot/profile-store.d.ts.map +1 -1
  20. package/dist/bot/profile-store.js +11 -9
  21. package/dist/bot/profile-store.js.map +1 -1
  22. package/dist/bot/profile-types.d.ts +2 -2
  23. package/dist/bot/profile-types.d.ts.map +1 -1
  24. package/dist/bot/runner.d.ts +1 -0
  25. package/dist/bot/runner.d.ts.map +1 -1
  26. package/dist/bot/runner.js +6 -2
  27. package/dist/bot/runner.js.map +1 -1
  28. package/dist/bot/step-executor.d.ts.map +1 -1
  29. package/dist/bot/step-executor.js +10 -0
  30. package/dist/bot/step-executor.js.map +1 -1
  31. package/dist/bot/swarm-controller.d.ts +3 -5
  32. package/dist/bot/swarm-controller.d.ts.map +1 -1
  33. package/dist/bot/swarm-controller.js +157 -74
  34. package/dist/bot/swarm-controller.js.map +1 -1
  35. package/dist/bot/task-prompt-builder.d.ts +2 -3
  36. package/dist/bot/task-prompt-builder.d.ts.map +1 -1
  37. package/dist/bot/task-prompt-builder.js +81 -67
  38. package/dist/bot/task-prompt-builder.js.map +1 -1
  39. package/dist/bot/task-store.d.ts +3 -3
  40. package/dist/bot/task-store.d.ts.map +1 -1
  41. package/dist/bot/task-store.js +89 -75
  42. package/dist/bot/task-store.js.map +1 -1
  43. package/dist/bot/task-types.d.ts +54 -26
  44. package/dist/bot/task-types.d.ts.map +1 -1
  45. package/dist/bot/task-types.js +6 -2
  46. package/dist/bot/task-types.js.map +1 -1
  47. package/dist/bot/types.d.ts +2 -0
  48. package/dist/bot/types.d.ts.map +1 -1
  49. package/dist/bot/weaver-tools.d.ts.map +1 -1
  50. package/dist/bot/weaver-tools.js +10 -0
  51. package/dist/bot/weaver-tools.js.map +1 -1
  52. package/dist/cli-handlers.d.ts +0 -1
  53. package/dist/cli-handlers.d.ts.map +1 -1
  54. package/dist/cli-handlers.js +5 -9
  55. package/dist/cli-handlers.js.map +1 -1
  56. package/dist/index.d.ts +1 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/node-types/agent-execute.d.ts.map +1 -1
  60. package/dist/node-types/agent-execute.js +95 -63
  61. package/dist/node-types/agent-execute.js.map +1 -1
  62. package/dist/node-types/plan-task.js +8 -8
  63. package/dist/node-types/plan-task.js.map +1 -1
  64. package/dist/ui/bot-panel.js +1 -1
  65. package/dist/ui/capability-editor.js +37 -14
  66. package/dist/ui/chat-task-result.js +1 -7
  67. package/dist/ui/profile-editor.js +37 -14
  68. package/dist/ui/swarm-controls.js +2 -2
  69. package/dist/ui/swarm-dashboard.js +72 -109
  70. package/dist/ui/task-detail-view.js +21 -42
  71. package/dist/ui/task-editor.js +13 -50
  72. package/dist/ui/task-pool-list.js +0 -2
  73. package/flowweaver.manifest.json +1 -1
  74. package/package.json +1 -1
  75. package/src/ai-chat-provider.ts +15 -11
  76. package/src/bot/ai-router.ts +5 -5
  77. package/src/bot/assistant-tools.ts +6 -7
  78. package/src/bot/capability-registry.ts +37 -14
  79. package/src/bot/dashboard.ts +1 -1
  80. package/src/bot/index.ts +5 -1
  81. package/src/bot/instance-manager.ts +3 -3
  82. package/src/bot/profile-store.ts +12 -10
  83. package/src/bot/profile-types.ts +2 -2
  84. package/src/bot/runner.ts +6 -2
  85. package/src/bot/step-executor.ts +11 -0
  86. package/src/bot/swarm-controller.ts +164 -78
  87. package/src/bot/task-prompt-builder.ts +86 -71
  88. package/src/bot/task-store.ts +101 -78
  89. package/src/bot/task-types.ts +81 -36
  90. package/src/bot/types.ts +2 -0
  91. package/src/bot/weaver-tools.ts +11 -0
  92. package/src/cli-handlers.ts +5 -9
  93. package/src/index.ts +6 -0
  94. package/src/node-types/agent-execute.ts +99 -62
  95. package/src/node-types/plan-task.ts +8 -8
  96. package/src/ui/bot-panel.tsx +3 -3
  97. package/src/ui/chat-task-result.tsx +5 -14
  98. package/src/ui/swarm-controls.tsx +3 -3
  99. package/src/ui/swarm-dashboard.tsx +3 -3
  100. package/src/ui/task-detail-view.tsx +29 -52
  101. package/src/ui/task-editor.tsx +14 -51
  102. 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
- if ((event.name === 'patch_file' || event.name === 'write_file') && !event.isError && event.args?.file) {
199
- filesModified.push(event.args.file as string);
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
- // Post-agent validation gate: don't trust the AI's self-assessment
231
- const uniqueFiles = [...new Set(filesModified)];
232
- let validationPassed = result.success;
233
- if (uniqueFiles.length > 0) {
234
- try {
235
- const { weaverValidateGate } = await import('./validate-gate.js');
236
- const gateCtx: WeaverContext = { ...context, filesModified: JSON.stringify(uniqueFiles) };
237
- const gateResult = weaverValidateGate(JSON.stringify(gateCtx));
238
- const gateData = JSON.parse(gateResult.ctx) as WeaverContext;
239
- if (!gateData.allValid) {
240
- validationPassed = false;
241
- renderer.warn('Post-agent validation found errors — task marked as failed');
242
- }
243
- context.validationResultJson = gateData.validationResultJson;
244
- } catch (err) { if (process.env.WEAVER_VERBOSE) console.error('[agent-execute] validate-gate unavailable, skipping:', err); }
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
- // Step-log error-rate gate: don't trust the AI's success claim
248
- // when the majority of its tool calls failed.
249
- if (validationPassed && stepLog.length >= 4) {
250
- const errorCount = stepLog.filter(s => s.status === 'error').length;
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
- // Deliverable-based gate: if the task description specifies expected files
261
- // (via FILES: or "You may ONLY create/modify:"), verify they exist on disk.
262
- if (validationPassed && task.instruction) {
263
- const filesMatch = task.instruction.match(/FILES?:\s*([^\n]+)/i)
264
- ?? task.instruction.match(/ONLY create(?:\/modify)?[^:]*:\s*([^\n]+)/i);
265
- if (filesMatch) {
266
- const expectedFiles = filesMatch[1]
267
- .split(/[,;]/)
268
- .map((f: string) => f.trim().replace(/`/g, ''))
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: validationPassed,
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(uniqueFiles);
303
+ context.filesModified = JSON.stringify([...uniqueCreated, ...uniqueModified]);
287
304
  context.stepLogJson = JSON.stringify(stepLog);
288
- context.allValid = validationPassed;
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: validationPassed,
310
+ success: outcome === 'completed',
311
+ outcome,
292
312
  toolCalls: result.toolCallCount,
293
- filesModified: uniqueFiles.length,
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(validationPassed, {
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: uniqueFiles.length,
304
- elapsed: Date.now() - taskStart,
324
+ filesModified: uniqueCreated.length + uniqueModified.length,
325
+ elapsed: durationMs,
305
326
  });
306
327
 
307
- return { onSuccess: validationPassed, onFailure: !validationPassed, ctx: JSON.stringify(context) };
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
- context.resultJson = JSON.stringify({ success: false, error: msg });
316
- context.filesModified = '[]';
317
- context.stepLogJson = '[]';
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 retry attempt (attempt > 0)
218
- const attempt = task.attempt ?? 0;
219
- if (attempt > 0) {
220
- const retryParts: string[] = ['\n\n--- RETRY CONTEXT (attempt ' + (attempt + 1) + ') ---'];
221
- if (task.lastError) {
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);
@@ -32,7 +32,7 @@ interface InstanceInfo {
32
32
  tokensUsed: number;
33
33
  cost: number;
34
34
  tasksCompleted: number;
35
- tasksFailed: number;
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
- tasksFailed: number;
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?.tasksFailed ?? 0),
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' | 'pending' | 'in-progress' | 'blocked' | 'done' | 'cancelled';
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' | 'pending' | 'in-progress' | 'blocked' | 'done' | 'cancelled';
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.currentBotId
163
- ? `Bot: ${task.currentBotId}`
164
- : task.assignedProfile
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
- tasksFailed: number;
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.tasksFailed > 0 && React.createElement(Typography, {
208
+ swarmStatus && swarmStatus.runsIncomplete > 0 && React.createElement(Typography, {
209
209
  variant: 'caption-regular',
210
210
  color: 'color-status-negative',
211
- }, `${swarmStatus.tasksFailed} failed`),
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
- tasksFailed: number;
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
- tasksFailed: number;
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' | 'pending' | 'in-progress' | 'blocked' | 'done' | 'cancelled';
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' | 'pending' | 'in-progress' | 'blocked' | 'done' | 'cancelled';
27
+ type TaskStatus = 'open' | 'in-progress' | 'done' | 'cancelled';
28
28
 
29
29
  interface TaskContext {
30
30
  files: string[];
31
31
  notes: string;
32
- runSummaries: Array<{
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
- error?: string;
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
- lastError?: string;
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
- currentBotId?: string;
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 runSummaries (RunStore may be empty for swarm tasks)
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?.runSummaries as Array<Record<string, unknown>> | undefined;
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 currentRunId = task?.currentRunId;
311
- const isLive = task?.status === 'in-progress' && !!currentRunId;
300
+ const activeRunId = task?.activeRunId;
301
+ const isLive = task?.status === 'in-progress' && !!activeRunId;
312
302
 
313
303
  useEffect(() => {
314
- if (!isLive || !currentRunId) return;
315
- stream.start(packId, 'fw_weaver_events', currentRunId);
304
+ if (!isLive || !activeRunId) return;
305
+ stream.start(packId, 'fw_weaver_events', activeRunId);
316
306
  return () => stream.stop();
317
- }, [isLive, currentRunId, packId]);
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.maxAttempts != null && task.maxAttempts > 1 && React.createElement(Typography, {
575
+ (task.context?.runHistory?.length ?? 0) > 0 && React.createElement(Typography, {
587
576
  variant: 'smallCaption-regular',
588
577
  color: 'color-text-subtle',
589
- }, `${task.runs?.length ?? 0}/${task.maxAttempts} runs`),
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.runs?.length ?? 0) > 0)
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.attempt ?? 0) > 0) && React.createElement(Button, {
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 === 'pending' || task.status === 'in-progress' || task.status === 'blocked') && React.createElement(Button, {
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
- // Last error
826
- task.context?.lastError && React.createElement(Flex, {
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 Summaries'),
842
- ...(task.context?.runSummaries ?? []).map((rs: Record<string, unknown>, i: number) =>
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.error && React.createElement(Typography, {
866
- variant: 'smallCaption-regular', color: 'color-status-negative',
867
- }, `Error: ${rs.error}`),
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}${task.budgetTokens ? ` / ${task.budgetTokens.toLocaleString()}` : ''}`),
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
  ),