@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
package/dist/ui/task-editor.js
CHANGED
|
@@ -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 ||
|
|
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 || "") ||
|
|
150
|
-
}, [mode, title, description, notes, files, dependsOn, priority, complexity, assignedProfile,
|
|
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
|
-
|
|
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
|
-
|
|
235
|
-
|
|
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,
|
|
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?.
|
|
582
|
+
taskData.context?.runHistory?.slice(-1)?.[0]?.remainingWork && React.createElement(
|
|
620
583
|
Field,
|
|
621
|
-
{ label: "
|
|
584
|
+
{ label: "Remaining work", align: "start" },
|
|
622
585
|
React.createElement(Typography, {
|
|
623
586
|
variant: "smallCaption-regular",
|
|
624
|
-
color: "color-status-
|
|
587
|
+
color: "color-status-caution",
|
|
625
588
|
style: { fontFamily: "var(--font-mono, monospace)", whiteSpace: "pre-wrap" }
|
|
626
|
-
}, taskData.context.
|
|
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 = {
|
package/flowweaver.manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifestVersion": 2,
|
|
3
3
|
"name": "@synergenius/flow-weaver-pack-weaver",
|
|
4
|
-
"version": "0.9.
|
|
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
package/src/ai-chat-provider.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
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: '
|
|
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},
|
|
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
|
}
|
package/src/bot/ai-router.ts
CHANGED
|
@@ -114,14 +114,14 @@ export class AIRouterImpl implements AIRouter {
|
|
|
114
114
|
}),
|
|
115
115
|
];
|
|
116
116
|
|
|
117
|
-
// Add previous
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
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
|
|
122
|
+
`Previous runs: ${task.context.runHistory.length} (${nonCompleted.length} not completed)`,
|
|
123
123
|
);
|
|
124
|
-
for (const f of
|
|
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
|
|
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, ${
|
|
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: '
|
|
121
|
+
await store.update(t.id, { status: 'open' });
|
|
123
122
|
count++;
|
|
124
123
|
}
|
|
125
|
-
return { result: `Reset ${count} cancelled task(s) to
|
|
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
|
|
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 (${
|
|
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
|
-
###
|
|
92
|
-
Your LAST subtask MUST be a "
|
|
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
|
-
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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",
|
|
104
|
-
{ operation: "task_create", args: { title: "
|
|
105
|
-
{ operation: "task_create", args: { title: "
|
|
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 = {
|
package/src/bot/dashboard.ts
CHANGED
|
@@ -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 === '
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
155
|
+
inst.runsIncomplete++;
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
package/src/bot/profile-store.ts
CHANGED
|
@@ -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.
|
|
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
|
|
262
|
-
- The ONLY exception: your final "
|
|
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
|
-
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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: {
|
package/src/bot/profile-types.ts
CHANGED
|
@@ -183,7 +183,7 @@ export interface BotInstance {
|
|
|
183
183
|
tokensUsed: number;
|
|
184
184
|
cost: number;
|
|
185
185
|
tasksCompleted: number;
|
|
186
|
-
|
|
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
|
-
|
|
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,
|
package/src/bot/step-executor.ts
CHANGED
|
@@ -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
|
}
|