@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
@@ -52,9 +52,7 @@ var COMPLEXITY_OPTIONS = [
52
52
  ];
53
53
  var STATUS_COLOR = {
54
54
  "open": "color-brand-alt",
55
- "pending": "color-brand-alt",
56
55
  "in-progress": "color-status-info",
57
- "blocked": "color-status-caution",
58
56
  "done": "color-status-positive",
59
57
  "cancelled": "color-status-negative"
60
58
  };
@@ -66,8 +64,6 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
66
64
  const [priority, setPriority] = useState("0");
67
65
  const [complexity, setComplexity] = useState("");
68
66
  const [assignedProfile, setAssignedProfile] = useState("");
69
- const [maxAttempts, setMaxAttempts] = useState("3");
70
- const [budgetTokens, setBudgetTokens] = useState("");
71
67
  const [budgetCost, setBudgetCost] = useState("");
72
68
  const [notes, setNotes] = useState("");
73
69
  const [files, setFiles] = useState([]);
@@ -124,8 +120,6 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
124
120
  setPriority(String(t.priority ?? 0));
125
121
  setComplexity(t.complexity || "");
126
122
  setAssignedProfile(t.assignedProfile || "");
127
- setMaxAttempts(String(t.maxAttempts ?? 3));
128
- setBudgetTokens(t.budgetTokens != null ? String(t.budgetTokens) : "");
129
123
  setBudgetCost(t.budgetCost != null ? String(t.budgetCost) : "");
130
124
  setNotes(t.context?.notes || "");
131
125
  setFiles(t.context?.files || []);
@@ -143,11 +137,11 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
143
137
  }, [mode, taskId, callTool]);
144
138
  const isDirty = useCallback(() => {
145
139
  if (mode === "create") {
146
- return !!(title.trim() || description.trim() || notes.trim() || files.length > 0 || dependsOn.length > 0 || priority !== "0" || complexity || assignedProfile || maxAttempts !== "3" || budgetTokens || budgetCost);
140
+ return !!(title.trim() || description.trim() || notes.trim() || files.length > 0 || dependsOn.length > 0 || priority !== "0" || complexity || assignedProfile || budgetCost);
147
141
  }
148
142
  if (!taskData) return false;
149
- return title.trim() !== (taskData.title || "") || description.trim() !== (taskData.description || "") || notes.trim() !== (taskData.context?.notes || "") || JSON.stringify(files) !== JSON.stringify(taskData.context?.files || []) || priority !== String(taskData.priority ?? 0) || complexity !== (taskData.complexity || "") || assignedProfile !== (taskData.assignedProfile || "") || maxAttempts !== String(taskData.maxAttempts ?? 3) || budgetTokens !== (taskData.budgetTokens != null ? String(taskData.budgetTokens) : "") || budgetCost !== (taskData.budgetCost != null ? String(taskData.budgetCost) : "");
150
- }, [mode, title, description, notes, files, dependsOn, priority, complexity, assignedProfile, maxAttempts, budgetTokens, budgetCost, taskData]);
143
+ return title.trim() !== (taskData.title || "") || description.trim() !== (taskData.description || "") || notes.trim() !== (taskData.context?.notes || "") || JSON.stringify(files) !== JSON.stringify(taskData.context?.files || []) || priority !== String(taskData.priority ?? 0) || complexity !== (taskData.complexity || "") || assignedProfile !== (taskData.assignedProfile || "") || budgetCost !== (taskData.budgetCost != null ? String(taskData.budgetCost) : "");
144
+ }, [mode, title, description, notes, files, dependsOn, priority, complexity, assignedProfile, budgetCost, taskData]);
151
145
  const handleBack = useCallback(async () => {
152
146
  if (isDirty()) {
153
147
  const ok = await ctx.confirm("Discard unsaved changes?", {
@@ -186,12 +180,10 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
186
180
  const args = {
187
181
  title: title.trim(),
188
182
  description: description.trim(),
189
- priority: parseInt(priority, 10) || 0,
190
- maxAttempts: parseInt(maxAttempts, 10) || 3
183
+ priority: parseInt(priority, 10) || 0
191
184
  };
192
185
  if (complexity) args.complexity = complexity;
193
186
  if (assignedProfile) args.assignedProfile = assignedProfile;
194
- if (budgetTokens) args.budgetTokens = parseInt(budgetTokens, 10);
195
187
  if (budgetCost) args.budgetCost = parseFloat(budgetCost);
196
188
  if (dependsOn.length > 0) args.dependsOn = dependsOn;
197
189
  const result = await callTool("fw_weaver_task_create", args);
@@ -203,7 +195,8 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
203
195
  context: {
204
196
  notes: notes.trim(),
205
197
  files,
206
- runSummaries: []
198
+ runHistory: [],
199
+ stagnationCount: 0
207
200
  }
208
201
  });
209
202
  }
@@ -214,15 +207,9 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
214
207
  title: title.trim(),
215
208
  description: description.trim(),
216
209
  priority: parseInt(priority, 10) || 0,
217
- maxAttempts: parseInt(maxAttempts, 10) || 3,
218
210
  complexity: complexity || void 0,
219
211
  assignedProfile: assignedProfile || null
220
212
  };
221
- if (budgetTokens) {
222
- patch.budgetTokens = parseInt(budgetTokens, 10);
223
- } else {
224
- patch.budgetTokens = void 0;
225
- }
226
213
  if (budgetCost) {
227
214
  patch.budgetCost = parseFloat(budgetCost);
228
215
  } else {
@@ -231,8 +218,8 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
231
218
  patch.context = {
232
219
  files,
233
220
  notes: notes.trim(),
234
- runSummaries: taskData?.context?.runSummaries || [],
235
- lastError: taskData?.context?.lastError
221
+ runHistory: taskData?.context?.runHistory || [],
222
+ stagnationCount: taskData?.context?.stagnationCount ?? 0
236
223
  };
237
224
  await callTool("fw_weaver_task_update", patch);
238
225
  toast("Task updated", { type: "success" });
@@ -241,7 +228,7 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
241
228
  } catch (err) {
242
229
  toast(err instanceof Error ? err.message : `Failed to ${mode} task`, { type: "error" });
243
230
  }
244
- }, [mode, taskId, title, description, priority, complexity, assignedProfile, maxAttempts, budgetTokens, budgetCost, notes, files, dependsOn, taskData, callTool, onSave]);
231
+ }, [mode, taskId, title, description, priority, complexity, assignedProfile, budgetCost, notes, files, dependsOn, taskData, callTool, onSave]);
245
232
  const handleDelete = useCallback(async () => {
246
233
  if (!taskId) return;
247
234
  const ok = await ctx.confirm("Are you sure you want to cancel this task?", {
@@ -395,30 +382,6 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
395
382
  defaultBoxStyle: { flex: 1, minWidth: 0 }
396
383
  })
397
384
  ),
398
- // -- Max Attempts --
399
- React.createElement(
400
- Field,
401
- { label: "Max Attempts" },
402
- React.createElement(Input, {
403
- type: "number",
404
- size: "small",
405
- placeholder: "3",
406
- value: maxAttempts,
407
- onChange: (v) => setMaxAttempts(v)
408
- })
409
- ),
410
- // -- Budget Tokens --
411
- React.createElement(
412
- Field,
413
- { label: "Budget Tokens" },
414
- React.createElement(Input, {
415
- type: "number",
416
- size: "small",
417
- placeholder: "Optional token limit",
418
- value: budgetTokens,
419
- onChange: (v) => setBudgetTokens(v)
420
- })
421
- ),
422
385
  // -- Budget Cost --
423
386
  React.createElement(
424
387
  Field,
@@ -616,14 +579,14 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
616
579
  `$${(taskData.costUsed ?? 0).toFixed(3)}`
617
580
  )
618
581
  ),
619
- taskData.context?.lastError && React.createElement(
582
+ taskData.context?.runHistory?.slice(-1)?.[0]?.remainingWork && React.createElement(
620
583
  Field,
621
- { label: "Last error", align: "start" },
584
+ { label: "Remaining work", align: "start" },
622
585
  React.createElement(Typography, {
623
586
  variant: "smallCaption-regular",
624
- color: "color-status-negative",
587
+ color: "color-status-caution",
625
588
  style: { fontFamily: "var(--font-mono, monospace)", whiteSpace: "pre-wrap" }
626
- }, taskData.context.lastError)
589
+ }, taskData.context.runHistory.slice(-1)[0]?.remainingWork)
627
590
  )
628
591
  )
629
592
  ),
@@ -29,10 +29,8 @@ var { useState } = React;
29
29
  var { Flex, Typography, Icon, StatusIcon, Chip, ScrollArea, Badge, EmptyState } = require("@fw/plugin-ui-kit");
30
30
  var statusToIcon = {
31
31
  "open": "pending",
32
- "pending": "pending",
33
32
  "in-progress": "running",
34
33
  "done": "completed",
35
- "blocked": "pending",
36
34
  "cancelled": "failed"
37
35
  };
38
36
  var rowBaseStyle = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifestVersion": 2,
3
3
  "name": "@synergenius/flow-weaver-pack-weaver",
4
- "version": "0.9.159",
4
+ "version": "0.9.164",
5
5
  "description": "AI bot for Flow Weaver. Execute tasks, run workflows, evolve autonomously.",
6
6
  "engineVersion": ">=0.22.10",
7
7
  "categories": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synergenius/flow-weaver-pack-weaver",
3
- "version": "0.9.159",
3
+ "version": "0.9.164",
4
4
  "description": "AI bot for Flow Weaver. Execute tasks, run workflows, evolve autonomously.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -199,7 +199,7 @@ const toolHandlers: Record<
199
199
  activeWorkers: Object.values(state.instances).filter(i => i.status === 'executing').length,
200
200
  totalWorkers: Object.keys(state.instances).length,
201
201
  tasksCompleted: state.tasksCompleted,
202
- tasksFailed: state.tasksFailed,
202
+ runsIncomplete: state.runsIncomplete,
203
203
  totalTokensUsed: state.totalTokensUsed,
204
204
  totalCost: state.totalCost,
205
205
  budgets: state.budgets,
@@ -218,7 +218,7 @@ const toolHandlers: Record<
218
218
 
219
219
  // Self-healing: if the run is not in the registry and has no .done marker,
220
220
  // it may be a zombie — OR the run hasn't registered yet (race condition).
221
- // Check the task store: if any task still has this runId as currentRunId,
221
+ // Check the task store: if any task still has this runId as activeRunId,
222
222
  // the swarm considers it active — don't kill it.
223
223
  if (!done && !runRegistry.isAlive(runId)) {
224
224
  let swarmConsidersAlive = false;
@@ -226,7 +226,7 @@ const toolHandlers: Record<
226
226
  const { TaskStore } = await import('./bot/task-store.js');
227
227
  const store = new TaskStore(ctx.workspacePath);
228
228
  const tasks = await store.list();
229
- swarmConsidersAlive = tasks.some(t => t.currentRunId === runId);
229
+ swarmConsidersAlive = tasks.some(t => t.activeRunId === runId);
230
230
  } catch { /* non-fatal */ }
231
231
 
232
232
  if (!swarmConsidersAlive) {
@@ -379,20 +379,27 @@ const toolHandlers: Record<
379
379
 
380
380
  async fw_weaver_task_create(args: Record<string, unknown>, ctx: AiChatToolContext) {
381
381
  const store = new TaskStore(ctx.workspacePath);
382
+ const parseAcceptance = (raw: unknown): CreateTaskInput['acceptance'] => {
383
+ if (!raw || typeof raw !== 'object') return undefined;
384
+ const obj = raw as Record<string, unknown>;
385
+ if (!Array.isArray(obj.checks)) return undefined;
386
+ const checks = (obj.checks as Array<Record<string, unknown>>)
387
+ .map(c => ({ name: String(c.name), command: String(c.command) }))
388
+ .filter(c => c.name && c.command);
389
+ return checks.length ? { checks } : undefined;
390
+ };
382
391
  const input: CreateTaskInput = {
383
392
  title: args.title as string,
384
393
  description: (args.description as string) ?? '',
385
394
  priority: args.priority as number | undefined,
386
395
  parentId: args.parentId as string | undefined,
387
396
  dependsOn: args.dependsOn as string[] | undefined,
388
- budgetTokens: args.budgetTokens as number | undefined,
389
397
  budgetCost: args.budgetCost as number | undefined,
390
- timeoutMs: args.timeoutMs as number | undefined,
391
- maxAttempts: args.maxAttempts as number | undefined,
392
398
  createdBy: 'ai',
393
399
  assignedProfile: args.assignedProfile as string | undefined,
394
400
  complexity: args.complexity as CreateTaskInput['complexity'],
395
401
  subtasks: args.subtasks as CreateTaskInput['subtasks'],
402
+ acceptance: parseAcceptance(args.acceptance),
396
403
  };
397
404
  const task = await store.create(input);
398
405
  const subtasks = task.isParent ? await store.getSubtasks(task.id) : [];
@@ -436,8 +443,7 @@ const toolHandlers: Record<
436
443
  const task = await store.get(args.id as string);
437
444
  if (!task) return JSON.stringify({ error: 'Task not found' });
438
445
  const updated = await store.update(args.id as string, {
439
- status: 'pending',
440
- attempt: 0,
446
+ status: 'open',
441
447
  });
442
448
  return JSON.stringify({ retried: true, task: updated });
443
449
  },
@@ -489,8 +495,6 @@ const toolHandlers: Record<
489
495
  workspaceBudgetCost: args.workspaceBudgetCost as number | undefined,
490
496
  sessionBudgetTokens: args.sessionBudgetTokens as number | undefined,
491
497
  sessionBudgetCost: args.sessionBudgetCost as number | undefined,
492
- autoRetry: args.autoRetry as boolean | undefined,
493
- maxAttemptsDefault: args.maxAttemptsDefault as number | undefined,
494
498
  });
495
499
  return JSON.stringify(controller.getStatus(), null, 2);
496
500
  },
@@ -945,7 +949,7 @@ Proactively offer these when relevant.`,
945
949
  sections.push({
946
950
  id: 'swarm-status',
947
951
  title: 'Swarm Status',
948
- content: `The swarm is currently **${swarmState.status}** with ${activeWorkers} active worker(s) out of ${Object.keys(swarmState.instances).length} total. Tasks completed: ${swarmState.tasksCompleted}, failed: ${swarmState.tasksFailed}. Total cost: $${swarmState.totalCost.toFixed(4)}, tokens: ${swarmState.totalTokensUsed}.`,
952
+ content: `The swarm is currently **${swarmState.status}** with ${activeWorkers} active worker(s) out of ${Object.keys(swarmState.instances).length} total. Tasks completed: ${swarmState.tasksCompleted}, runs incomplete: ${swarmState.runsIncomplete}. Total cost: $${swarmState.totalCost.toFixed(4)}, tokens: ${swarmState.totalTokensUsed}.`,
949
953
  priority: 12,
950
954
  });
951
955
  }
@@ -114,14 +114,14 @@ export class AIRouterImpl implements AIRouter {
114
114
  }),
115
115
  ];
116
116
 
117
- // Add previous attempt info if available
118
- const failures = task.context.runSummaries.filter((r) => r.outcome !== 'success');
119
- if (failures.length > 0) {
117
+ // Add previous run info if available
118
+ const nonCompleted = task.context.runHistory.filter((r) => r.outcome !== 'completed');
119
+ if (nonCompleted.length > 0) {
120
120
  lines.push(
121
121
  '',
122
- `Previous attempts: ${task.context.runSummaries.length} (${failures.length} failed)`,
122
+ `Previous runs: ${task.context.runHistory.length} (${nonCompleted.length} not completed)`,
123
123
  );
124
- for (const f of failures.slice(-3)) {
124
+ for (const f of nonCompleted.slice(-3)) {
125
125
  lines.push(` - ${f.botId}: ${f.outcome}`);
126
126
  }
127
127
  }
@@ -55,13 +55,12 @@ export function createAssistantExecutor(projectDir: string, steeringEngine?: imp
55
55
  if (!bot) return { result: `Bot "${botName}" not found.`, isError: true };
56
56
  const store = mgr.getTaskStore(botName);
57
57
  const tasks = await store.list();
58
- const pending = tasks.filter(t => t.status === 'pending').length;
58
+ const open = tasks.filter(t => t.status === 'open').length;
59
59
  const inProgress = tasks.filter(t => t.status === 'in-progress').length;
60
60
  const done = tasks.filter(t => t.status === 'done').length;
61
61
  const cancelled = tasks.filter(t => t.status === 'cancelled').length;
62
- const blocked = tasks.filter(t => t.status === 'blocked').length;
63
62
  let result = `Bot "${botName}": ${bot.status}\n`;
64
- result += `Tasks: ${done} done, ${cancelled} cancelled, ${inProgress} in-progress, ${pending} pending, ${blocked} blocked\n`;
63
+ result += `Tasks: ${done} done, ${cancelled} cancelled, ${inProgress} in-progress, ${open} open\n`;
65
64
  return { result, isError: false };
66
65
  }
67
66
  case 'bot_pause': {
@@ -119,10 +118,10 @@ export function createAssistantExecutor(projectDir: string, steeringEngine?: imp
119
118
  const tasks = await store.list({ status: 'cancelled' });
120
119
  let count = 0;
121
120
  for (const t of tasks) {
122
- await store.update(t.id, { status: 'pending' });
121
+ await store.update(t.id, { status: 'open' });
123
122
  count++;
124
123
  }
125
- return { result: `Reset ${count} cancelled task(s) to pending.`, isError: false };
124
+ return { result: `Reset ${count} cancelled task(s) to open.`, isError: false };
126
125
  }
127
126
 
128
127
  // Flow-weaver tools
@@ -332,10 +331,10 @@ export function createAssistantExecutor(projectDir: string, steeringEngine?: imp
332
331
  const { TaskStore } = await import('./task-store.js');
333
332
  const store = new TaskStore(projPath);
334
333
  const tasks = await store.list();
335
- const pending = tasks.filter(t => t.status === 'pending').length;
334
+ const open = tasks.filter(t => t.status === 'open').length;
336
335
  const inProgress = tasks.filter(t => t.status === 'in-progress').length;
337
336
  if (tasks.length === 0) return `${d}: empty`;
338
- return `${d}: ${tasks.length} tasks (${pending} pending, ${inProgress} in-progress)`;
337
+ return `${d}: ${tasks.length} tasks (${open} open, ${inProgress} in-progress)`;
339
338
  } catch {
340
339
  return `${d}: empty`;
341
340
  }
@@ -88,23 +88,46 @@ Each subtask: focused (one concern), self-contained, properly routed, ordered by
88
88
  After implementation tasks, create a verification task (ops profile) that runs \`tsc --noEmit\`.
89
89
  This catches compilation errors before tests run, saving time and token spend.
90
90
 
91
- ### Verify & Iterate Loop
92
- Your LAST subtask MUST be a "Verify & Iterate" task assigned to yourself (orchestrator):
91
+ ### Review & Steer (Convergence Loop)
92
+ Your LAST subtask MUST be a "Review & Steer" task assigned to yourself (orchestrator):
93
93
  - dependsOn: ALL other subtasks
94
- - When it runs: review all task results, check for failures, gaps, or quality issues
95
- - If everything passes: signal done
96
- - If issues found: create fix tasks + another verify gate
97
- This creates a self-correcting loop: plan execute verify fix verify → done.
98
-
99
- ### Retries
100
- If your context shows "Parent Context" with existing sibling tasks, those are subtasks YOU already created in a previous attempt. Do NOT create duplicates. Check what exists and only create MISSING tasks. If all subtasks already exist, just call done.
94
+ - acceptance: include the objective's acceptance criteria
95
+
96
+ When this task runs, you are in STEERING MODE. Read your context carefully:
97
+ - Sibling tasks show their status, acceptance check results, and stagnation counts
98
+ - Your job is to decide: are we done, or do we need more work?
99
+
100
+ STEERING DECISIONS:
101
+ 1. ALL DONE: Every subtask has passing acceptance checks → call done
102
+ 2. PROGRESS: Tasks are open with recent changes → create another "Review & Steer" depending on open tasks, call done
103
+ 3. STAGNANT (stagnationCount >= 3): A task keeps failing the same way → INTERVENE:
104
+ - REASSIGN: Change the task description to suggest a different profile ("This might need ops help")
105
+ - REDEFINE: Create a new task with smaller scope or different approach, cancel the stuck one
106
+ - DROP: Cancel a non-essential task that's blocking progress
107
+ 4. FIX: Acceptance checks failing with specific errors → create targeted fix tasks
108
+
109
+ After creating fix tasks, ALWAYS create another "Review & Steer" task depending on those fixes.
110
+ This creates the convergence loop: decompose → execute → review → fix → review → done.
111
+
112
+ ### Existing Subtasks (Retries)
113
+ If your context shows "Parent Context" with existing sibling tasks, those are subtasks from a previous run. Do NOT create duplicates. Check what exists and only create MISSING tasks. If all subtasks already exist and look correct, just call done.
114
+
115
+ ### Acceptance Criteria (Shell Scripts)
116
+ Every task MUST have acceptance.checks — an array of shell commands that verify "done".
117
+ Each command must exit 0 to pass. The system runs them AUTOMATICALLY after each completed run.
118
+ If any check fails, the task stays open for another run.
119
+
120
+ Write commands relative to the workspace root. Examples:
121
+ - File exists: test -f url-shortener/src/server.ts
122
+ - Compiles: cd url-shortener && npx tsc --noEmit
123
+ - Tests pass: cd url-shortener && npx vitest run
124
+ - Export exists: grep -r "export.*startServer" url-shortener/src/
125
+ - No console.log: ! grep -r "console.log" url-shortener/src/
101
126
 
102
127
  ### Example
103
- { operation: "task_create", args: { title: "Design: Create project contract", parentId: "@self", assignedProfile: "developer", complexity: "moderate", description: "Create .design.md with interfaces and contracts.", dependsOn: [] } }
104
- { operation: "task_create", args: { title: "Setup project", parentId: "@self", assignedProfile: "ops", dependsOn: [] } }
105
- { operation: "task_create", args: { title: "Implement storage module", parentId: "@self", assignedProfile: "developer", dependsOn: ["Design: Create project contract", "Setup project"], description: "You may ONLY create: src/types.ts, src/storage.ts" } }
106
- { operation: "task_create", args: { title: "Implement HTTP server", parentId: "@self", assignedProfile: "developer", dependsOn: ["Implement storage module"], description: "You may ONLY create: src/server.ts" } }
107
- { operation: "task_create", args: { title: "Verify & Iterate", parentId: "@self", assignedProfile: "orchestrator", dependsOn: ["Implement HTTP server"], description: "Review all task results. If issues found, create fix tasks. If all good, signal done." } }`,
128
+ { operation: "task_create", args: { title: "Design: Create project contract", parentId: "@self", assignedProfile: "developer", description: "Create .design.md", acceptance: { checks: [{ name: "design exists", command: "test -f url-shortener/.design.md" }] }, dependsOn: [] } }
129
+ { operation: "task_create", args: { title: "Implement storage", parentId: "@self", assignedProfile: "developer", dependsOn: ["Design: Create project contract"], description: "You may ONLY create: src/types.ts, src/storage.ts", acceptance: { checks: [{ name: "files exist", command: "test -f url-shortener/src/types.ts && test -f url-shortener/src/storage.ts" }, { name: "compiles", command: "cd url-shortener && npx tsc --noEmit" }] } } }
130
+ { operation: "task_create", args: { title: "Review & Steer", parentId: "@self", assignedProfile: "orchestrator", dependsOn: ["Implement storage"], description: "Review subtask results. If all acceptance checks pass, signal done. If issues, create fix tasks + another Review & Steer." } }`,
108
131
  };
109
132
 
110
133
  const CAP_ROLE_DEVELOPER: CapabilityDefinition = {
@@ -323,7 +323,7 @@ export class DashboardServer {
323
323
  const projectDir = process.env.WEAVER_PROJECT_DIR ?? process.cwd();
324
324
  const store = new TaskStore(projectDir);
325
325
  store.list().then(tasks => {
326
- const pending = tasks.filter(t => t.status === 'pending').length;
326
+ const pending = tasks.filter(t => t.status === 'open').length;
327
327
  const inProgress = tasks.filter(t => t.status === 'in-progress').length;
328
328
  const done = tasks.filter(t => t.status === 'done').length;
329
329
  const cancelled = tasks.filter(t => t.status === 'cancelled').length;
package/src/bot/index.ts CHANGED
@@ -129,7 +129,11 @@ export { validateFiles } from './file-validator.js';
129
129
  export { SteeringController } from './steering.js';
130
130
  export type { SteeringCommand } from './steering.js';
131
131
  export { TaskStore } from './task-store.js';
132
- export type { Task, TaskFilter, CreateTaskInput, TaskStatus } from './task-types.js';
132
+ export type {
133
+ Task, TaskFilter, CreateTaskInput, TaskStatus,
134
+ RunProgress, RunOutcomeStatus, CompactedRun,
135
+ AcceptanceCheck, AcceptanceCriteria, AcceptanceResult,
136
+ } from './task-types.js';
133
137
  export { buildBotSystemPrompt } from './system-prompt.js';
134
138
 
135
139
  // Device bridge handlers
@@ -31,7 +31,7 @@ export class InstanceManager {
31
31
  tokensUsed: 0,
32
32
  cost: 0,
33
33
  tasksCompleted: 0,
34
- tasksFailed: 0,
34
+ runsIncomplete: 0,
35
35
  };
36
36
 
37
37
  this.instances.set(instanceId, instance);
@@ -87,7 +87,7 @@ export class InstanceManager {
87
87
  tokensUsed: 0,
88
88
  cost: 0,
89
89
  tasksCompleted: 0,
90
- tasksFailed: 0,
90
+ runsIncomplete: 0,
91
91
  };
92
92
 
93
93
  this.instances.set(instance.instanceId, instance);
@@ -152,7 +152,7 @@ export class InstanceManager {
152
152
  if (success) {
153
153
  inst.tasksCompleted++;
154
154
  } else {
155
- inst.tasksFailed++;
155
+ inst.runsIncomplete++;
156
156
  }
157
157
  }
158
158
 
@@ -254,20 +254,22 @@ const DEFAULT_PROFILES: Record<string, Array<Omit<CreateProfileInput, 'botId'>>>
254
254
  2. DECOMPOSE: Break into subtasks. Each subtask should produce 1-2 files max. Split large tasks.
255
255
  3. ASSIGN: Set assignedProfile per task (developer, reviewer, ops). Set complexity and priority.
256
256
  4. DEPENDENCIES: Minimize deps for parallelism. Tasks that don't share files should NOT depend on each other.
257
- 5. VERIFY: Your LAST subtask must be a "Verify & Iterate" task assigned to "orchestrator" that reviews results.
257
+ 5. STEER: Your LAST subtask must be a "Review & Steer" task assigned to "orchestrator" that reviews results.
258
258
 
259
259
  ## Rules
260
260
  - You do NOT write code yourself. You create tasks for other bots.
261
- - Every work subtask MUST have assignedProfile: "developer", "reviewer", or "ops".
262
- - The ONLY exception: your final "Verify & Iterate" task uses assignedProfile: "orchestrator".
261
+ - Every work subtask MUST have assignedProfile + acceptance criteria.
262
+ - The ONLY exception: your final "Review & Steer" uses assignedProfile: "orchestrator".
263
263
  - Add scope boundaries: "You may ONLY create/modify these files: [list]."
264
- - Design/architecture tasks "developer" (not ops). Ops is for infra/config only.
265
- - Aim for at least 2 tasks that can run in parallel. If all tasks are serial, reconsider.
266
-
267
- ## Verify & Iterate Loop
268
- Your LAST subtask MUST be:
269
- { title: "Verify & Iterate", assignedProfile: "orchestrator", dependsOn: [all other tasks] }
270
- When this runs: review results, check for failures/gaps, create fix tasks if needed, or signal done.`,
264
+ - Include acceptance: { checks: [{ name: "...", command: "..." }] } on every task.
265
+
266
+ ## Review & Steer (Convergence Loop)
267
+ Your LAST subtask: { title: "Review & Steer", assignedProfile: "orchestrator", dependsOn: [all other tasks] }
268
+ When this runs: read sibling task statuses and acceptance check results.
269
+ - All passing done
270
+ - Stagnant tasks (stagnationCount >= 3) intervene: reassign, redefine, or drop
271
+ - Failed checks → create fix tasks + another Review & Steer
272
+ This loop continues until all acceptance criteria are met or budget runs out.`,
271
273
  behavior: {
272
274
  capabilities: PROFILE_CAPABILITIES.orchestrator,
273
275
  phases: {
@@ -183,7 +183,7 @@ export interface BotInstance {
183
183
  tokensUsed: number;
184
184
  cost: number;
185
185
  tasksCompleted: number;
186
- tasksFailed: number;
186
+ runsIncomplete: number;
187
187
  }
188
188
 
189
189
  /** A recorded routing decision made by the orchestrator. */
@@ -211,7 +211,7 @@ export interface OrchestratorInput {
211
211
  complexity: 'trivial' | 'simple' | 'moderate' | 'complex';
212
212
  assignedProfile?: string;
213
213
  context: {
214
- runSummaries: Array<{ outcome: string; botId: string }>;
214
+ runHistory: Array<{ outcome: string; botId: string }>;
215
215
  };
216
216
  }>;
217
217
  profiles: BotProfile[];
package/src/bot/runner.ts CHANGED
@@ -165,6 +165,7 @@ export function buildReport(
165
165
  export function extractCtxData(result: Record<string, unknown> | null): {
166
166
  stepLog?: import('./types.js').StepLogEntry[];
167
167
  plan?: { summary: string; steps: Array<{ id: string; operation: string; description: string; args?: Record<string, unknown> }> };
168
+ runProgressJson?: string;
168
169
  } {
169
170
  try {
170
171
  const ctxStr = result?.ctx as string | undefined;
@@ -172,8 +173,10 @@ export function extractCtxData(result: Record<string, unknown> | null): {
172
173
  const ctx = JSON.parse(ctxStr);
173
174
  let stepLog: import('./types.js').StepLogEntry[] | undefined;
174
175
  let plan: { summary: string; steps: Array<{ id: string; operation: string; description: string; args?: Record<string, unknown> }> } | undefined;
176
+ let runProgressJson: string | undefined;
175
177
 
176
178
  if (ctx.stepLogJson) stepLog = JSON.parse(ctx.stepLogJson);
179
+ if (ctx.runProgressJson) runProgressJson = ctx.runProgressJson;
177
180
  if (ctx.planJson) {
178
181
  const parsed = JSON.parse(ctx.planJson);
179
182
  if (parsed?.steps) {
@@ -188,7 +191,7 @@ export function extractCtxData(result: Record<string, unknown> | null): {
188
191
  };
189
192
  }
190
193
  }
191
- return { stepLog, plan };
194
+ return { stepLog, plan, runProgressJson };
192
195
  } catch {
193
196
  return {};
194
197
  }
@@ -529,7 +532,7 @@ export async function runWorkflow(
529
532
  }
530
533
 
531
534
  // Extract stepLog and plan from WeaverContext if available
532
- const { stepLog, plan } = extractCtxData(result);
535
+ const { stepLog, plan, runProgressJson } = extractCtxData(result);
533
536
 
534
537
  // Build markdown report from the extracted context data
535
538
  let report = buildReport(result, success, stepLog);
@@ -574,6 +577,7 @@ export async function runWorkflow(
574
577
  summary,
575
578
  report,
576
579
  outcome,
580
+ runProgressJson,
577
581
  functionName: execResult.functionName,
578
582
  executionTime: execResult.executionTime,
579
583
  cost: costSummary,
@@ -483,6 +483,15 @@ export async function executeStep(
483
483
  ? rawDeps.map(dep => symbolicIdMap[dep] ?? dep)
484
484
  : rawDeps;
485
485
 
486
+ // Parse acceptance criteria if provided
487
+ const rawAcceptance = args.acceptance as Record<string, unknown> | undefined;
488
+ const acceptance = rawAcceptance?.checks ? {
489
+ checks: (rawAcceptance.checks as Array<{ name: string; command: string }>).map(c => ({
490
+ name: String(c.name ?? ''),
491
+ command: String(c.command ?? ''),
492
+ })).filter(c => c.name && c.command),
493
+ } : undefined;
494
+
486
495
  const input: CreateTaskInput = {
487
496
  title,
488
497
  description: (args.description as string) ?? title,
@@ -491,6 +500,7 @@ export async function executeStep(
491
500
  parentId,
492
501
  dependsOn: resolvedDeps,
493
502
  assignedProfile,
503
+ acceptance,
494
504
  };
495
505
 
496
506
  // Support inline subtasks
@@ -518,6 +528,7 @@ export async function executeStep(
518
528
  dependsOn: symbolicIdMap
519
529
  ? ((s.dependsOn as string[]) ?? []).map(dep => symbolicIdMap[dep] ?? dep)
520
530
  : (s.dependsOn as string[]) ?? [],
531
+ acceptance: s.acceptance ? { checks: ((s.acceptance as Record<string, unknown>).checks as Array<{name:string,command:string}>).map(c => ({name:String(c.name),command:String(c.command)})).filter(c=>c.name&&c.command) } : undefined,
521
532
  };
522
533
  });
523
534
  }