@synergenius/flow-weaver-pack-weaver 0.9.59 → 0.9.62
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 +186 -324
- package/dist/ai-chat-provider.js.map +1 -1
- package/dist/bot/assistant-tools.d.ts.map +1 -1
- package/dist/bot/assistant-tools.js +49 -33
- package/dist/bot/assistant-tools.js.map +1 -1
- package/dist/bot/async-mutex.d.ts +13 -0
- package/dist/bot/async-mutex.d.ts.map +1 -0
- package/dist/bot/async-mutex.js +37 -0
- package/dist/bot/async-mutex.js.map +1 -0
- package/dist/bot/bot-manager.d.ts +2 -2
- package/dist/bot/bot-manager.d.ts.map +1 -1
- package/dist/bot/bot-manager.js +3 -3
- package/dist/bot/bot-manager.js.map +1 -1
- package/dist/bot/dashboard.d.ts.map +1 -1
- package/dist/bot/dashboard.js +17 -8
- package/dist/bot/dashboard.js.map +1 -1
- package/dist/bot/index.d.ts +2 -4
- package/dist/bot/index.d.ts.map +1 -1
- package/dist/bot/index.js +1 -2
- package/dist/bot/index.js.map +1 -1
- package/dist/bot/run-store.d.ts.map +1 -1
- package/dist/bot/run-store.js +8 -0
- package/dist/bot/run-store.js.map +1 -1
- package/dist/bot/runner.d.ts +4 -0
- package/dist/bot/runner.d.ts.map +1 -1
- package/dist/bot/runner.js +5 -1
- package/dist/bot/runner.js.map +1 -1
- package/dist/bot/swarm-controller.d.ts +93 -0
- package/dist/bot/swarm-controller.d.ts.map +1 -0
- package/dist/bot/swarm-controller.js +459 -0
- package/dist/bot/swarm-controller.js.map +1 -0
- package/dist/bot/swarm-event-log.d.ts +28 -0
- package/dist/bot/swarm-event-log.d.ts.map +1 -0
- package/dist/bot/swarm-event-log.js +54 -0
- package/dist/bot/swarm-event-log.js.map +1 -0
- package/dist/bot/task-prompt-builder.d.ts +21 -0
- package/dist/bot/task-prompt-builder.d.ts.map +1 -0
- package/dist/bot/task-prompt-builder.js +217 -0
- package/dist/bot/task-prompt-builder.js.map +1 -0
- package/dist/bot/task-store.d.ts +26 -0
- package/dist/bot/task-store.d.ts.map +1 -0
- package/dist/bot/task-store.js +415 -0
- package/dist/bot/task-store.js.map +1 -0
- package/dist/bot/task-types.d.ts +75 -0
- package/dist/bot/task-types.d.ts.map +1 -0
- package/dist/bot/task-types.js +6 -0
- package/dist/bot/task-types.js.map +1 -0
- package/dist/bot/types.d.ts +8 -0
- package/dist/bot/types.d.ts.map +1 -1
- package/dist/cli-handlers.d.ts.map +1 -1
- package/dist/cli-handlers.js +80 -54
- package/dist/cli-handlers.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +54 -101
- package/dist/mcp-tools.js.map +1 -1
- package/dist/node-types/bot-report.d.ts.map +1 -1
- package/dist/node-types/bot-report.js +6 -24
- package/dist/node-types/bot-report.js.map +1 -1
- package/dist/node-types/receive-task.d.ts.map +1 -1
- package/dist/node-types/receive-task.js +9 -29
- package/dist/node-types/receive-task.js.map +1 -1
- package/dist/ui/bot-activity.js +2 -2
- package/dist/ui/bot-panel.js +217 -216
- package/dist/ui/bot-slot-card.js +176 -0
- package/dist/ui/budget-bar.js +57 -0
- package/dist/ui/chat-task-result.js +181 -0
- package/dist/ui/swarm-controls.js +236 -0
- package/dist/ui/swarm-dashboard.js +1714 -0
- package/dist/ui/task-create-form.js +175 -0
- package/dist/ui/task-detail-view.js +793 -0
- package/dist/ui/task-pool-list.js +181 -0
- package/flowweaver.manifest.json +316 -89
- package/package.json +1 -1
- package/src/ai-chat-provider.ts +202 -361
- package/src/bot/assistant-tools.ts +47 -29
- package/src/bot/async-mutex.ts +37 -0
- package/src/bot/bot-manager.ts +3 -3
- package/src/bot/dashboard.ts +17 -8
- package/src/bot/index.ts +2 -4
- package/src/bot/run-store.ts +8 -0
- package/src/bot/runner.ts +9 -1
- package/src/bot/swarm-controller.ts +581 -0
- package/src/bot/swarm-event-log.ts +57 -0
- package/src/bot/task-prompt-builder.ts +278 -0
- package/src/bot/task-store.ts +468 -0
- package/src/bot/task-types.ts +94 -0
- package/src/bot/types.ts +8 -0
- package/src/cli-handlers.ts +79 -53
- package/src/index.ts +5 -4
- package/src/mcp-tools.ts +67 -119
- package/src/node-types/bot-report.ts +6 -24
- package/src/node-types/receive-task.ts +9 -40
- package/src/ui/bot-activity.tsx +2 -2
- package/src/ui/bot-panel.tsx +217 -227
- package/src/ui/bot-slot-card.tsx +187 -0
- package/src/ui/budget-bar.tsx +28 -0
- package/src/ui/chat-task-result.tsx +239 -0
- package/src/ui/swarm-controls.tsx +256 -0
- package/src/ui/swarm-dashboard.tsx +318 -0
- package/src/ui/task-create-form.tsx +177 -0
- package/src/ui/task-detail-view.tsx +595 -0
- package/src/ui/task-pool-list.tsx +208 -0
- package/dist/bot/session-state.d.ts +0 -25
- package/dist/bot/session-state.d.ts.map +0 -1
- package/dist/bot/session-state.js +0 -110
- package/dist/bot/session-state.js.map +0 -1
- package/dist/bot/task-queue.d.ts +0 -46
- package/dist/bot/task-queue.d.ts.map +0 -1
- package/dist/bot/task-queue.js +0 -237
- package/dist/bot/task-queue.js.map +0 -1
- package/dist/ui/bot-workspace.js +0 -1015
- package/dist/ui/chat-bot-result.js +0 -71
- package/dist/ui/queue-input.js +0 -82
- package/dist/ui/session-bar.js +0 -174
- package/src/bot/session-state.ts +0 -116
- package/src/bot/task-queue.ts +0 -262
- package/src/ui/bot-workspace.tsx +0 -442
- package/src/ui/chat-bot-result.tsx +0 -81
- package/src/ui/queue-input.tsx +0 -56
- package/src/ui/session-bar.tsx +0 -157
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-chat-provider.d.ts","sourceRoot":"","sources":["../src/ai-chat-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"ai-chat-provider.d.ts","sourceRoot":"","sources":["../src/ai-chat-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAqBH,UAAU,iBAAiB;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,mBAAmB;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,mBAAmB;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;;sBAsYS,MAAM,QACN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpB,iBAAiB,GACzB,OAAO,CAAC,gBAAgB,CAAC;qCAcjB,mBAAmB,GAC3B,OAAO,CAAC,mBAAmB,EAAE,CAAC;;AApBnC,wBAoKE"}
|
package/dist/ai-chat-provider.js
CHANGED
|
@@ -15,17 +15,15 @@ import { runWorkflow } from './bot/runner.js';
|
|
|
15
15
|
import { RunStore } from './bot/run-store.js';
|
|
16
16
|
import { CostStore } from './bot/cost-store.js';
|
|
17
17
|
import { EventLog } from './bot/event-log.js';
|
|
18
|
-
import { SessionStore } from './bot/session-state.js';
|
|
19
|
-
import { TaskQueue } from './bot/task-queue.js';
|
|
20
|
-
import { SteeringController } from './bot/steering.js';
|
|
21
18
|
import { defaultRegistry, discoverProviders } from './bot/provider-registry.js';
|
|
22
19
|
import { runRegistry } from './bot/run-registry.js';
|
|
23
20
|
import { ProjectModelStore } from './bot/project-model.js';
|
|
24
21
|
import { InsightEngine } from './bot/insight-engine.js';
|
|
25
22
|
import { buildSystemPrompt } from './bot/system-prompt.js';
|
|
26
23
|
import { BotRegistry } from './bot/bot-registry.js';
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
import { TaskStore } from './bot/task-store.js';
|
|
25
|
+
import { SwarmController } from './bot/swarm-controller.js';
|
|
26
|
+
import { SwarmEventLog } from './bot/swarm-event-log.js';
|
|
29
27
|
// ---------------------------------------------------------------------------
|
|
30
28
|
// Tool handlers — reuse the same logic as MCP tools
|
|
31
29
|
// ---------------------------------------------------------------------------
|
|
@@ -48,15 +46,21 @@ const toolHandlers = {
|
|
|
48
46
|
const record = store.get(args.id);
|
|
49
47
|
return record ? JSON.stringify(record, null, 2) : `No run found matching "${args.id}"`;
|
|
50
48
|
}
|
|
49
|
+
const taskId = args.taskId;
|
|
50
|
+
const botId = args.botId;
|
|
51
51
|
const records = store.list({
|
|
52
52
|
outcome: args.outcome,
|
|
53
53
|
workflowFile: args.workflowFile,
|
|
54
54
|
limit: args.limit ?? 20,
|
|
55
|
+
taskId,
|
|
56
|
+
botId,
|
|
55
57
|
});
|
|
56
58
|
return records.length === 0 ? 'No runs recorded yet.' : JSON.stringify(records, null, 2);
|
|
57
59
|
},
|
|
58
60
|
async fw_weaver_costs(args) {
|
|
59
61
|
const store = new CostStore();
|
|
62
|
+
const _taskId = args.taskId;
|
|
63
|
+
const _botId = args.botId;
|
|
60
64
|
let sinceTs;
|
|
61
65
|
if (args.since) {
|
|
62
66
|
const spec = args.since;
|
|
@@ -73,6 +77,7 @@ const toolHandlers = {
|
|
|
73
77
|
sinceTs = ts;
|
|
74
78
|
}
|
|
75
79
|
}
|
|
80
|
+
// TODO: pass taskId/botId to CostStore once it supports per-task/per-bot filtering
|
|
76
81
|
const summary = store.summarize({ since: sinceTs, model: args.model });
|
|
77
82
|
return JSON.stringify(summary, null, 2);
|
|
78
83
|
},
|
|
@@ -89,170 +94,33 @@ const toolHandlers = {
|
|
|
89
94
|
}));
|
|
90
95
|
return JSON.stringify(result, null, 2);
|
|
91
96
|
},
|
|
92
|
-
async
|
|
93
|
-
// Resolve bot from registry if botId is provided
|
|
97
|
+
async fw_weaver_steer(args, ctx) {
|
|
94
98
|
const botId = args.botId;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
const validation = registry.validate(botId);
|
|
103
|
-
if (!validation.valid) {
|
|
104
|
-
return JSON.stringify({ error: `Bot '${botId}' is invalid: ${validation.error}` });
|
|
105
|
-
}
|
|
106
|
-
registryWorkflowFile = path.isAbsolute(bot.filePath)
|
|
107
|
-
? bot.filePath
|
|
108
|
-
: path.join(ctx.workspacePath, bot.filePath);
|
|
109
|
-
}
|
|
110
|
-
const task = {
|
|
111
|
-
instruction: args.task,
|
|
112
|
-
mode: args.mode ?? 'create',
|
|
113
|
-
targets: args.targets,
|
|
114
|
-
options: {
|
|
115
|
-
template: args.template,
|
|
116
|
-
dryRun: args.dryRun,
|
|
117
|
-
autoApprove: args.autoApprove ?? false,
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
const packRoot = new URL('..', import.meta.url);
|
|
121
|
-
let workflowPath;
|
|
122
|
-
if (registryWorkflowFile) {
|
|
123
|
-
workflowPath = registryWorkflowFile;
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
workflowPath = fileURLToPath(new URL('src/workflows/weaver-bot.ts', packRoot));
|
|
127
|
-
if (!fs.existsSync(workflowPath)) {
|
|
128
|
-
workflowPath = fileURLToPath(new URL('dist/workflows/weaver-bot.js', packRoot));
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
const projectDir = ctx.workspacePath || process.cwd();
|
|
132
|
-
const runId = RunStore.newId();
|
|
133
|
-
const eventLog = new EventLog(runId);
|
|
134
|
-
// Clean up old event logs
|
|
135
|
-
EventLog.cleanup();
|
|
136
|
-
// Fire-and-forget: start workflow in background, events stream via EventLog
|
|
137
|
-
eventLog.emit({ type: 'bot-started', timestamp: Date.now(), data: { instruction: task.instruction, mode: task.mode, runId } });
|
|
138
|
-
// Register in run registry BEFORE calling runWorkflow to prevent the race condition
|
|
139
|
-
// where fw_weaver_events self-healing sees the run as dead and writes .done prematurely.
|
|
140
|
-
// The runner's finally block will remove it when execution actually completes.
|
|
141
|
-
const earlyPromise = new Promise(() => { }); // placeholder, replaced by runner
|
|
142
|
-
runRegistry.register(runId, { file: workflowPath, startedAt: new Date().toISOString(), promise: earlyPromise });
|
|
143
|
-
// Use timeout-auto approval so the UI can show the approval card
|
|
144
|
-
// and the file-based decision flow works
|
|
145
|
-
const useAutoApprove = task.options?.autoApprove === true;
|
|
146
|
-
const runPromise = runWorkflow(workflowPath, {
|
|
147
|
-
runId,
|
|
148
|
-
params: { taskJson: JSON.stringify(task), projectDir },
|
|
149
|
-
dryRun: args.dryRun,
|
|
150
|
-
config: useAutoApprove ? undefined : { provider: 'auto', approval: { mode: 'timeout-auto', timeoutSeconds: 300 } },
|
|
151
|
-
eventLog,
|
|
152
|
-
}).catch((err) => {
|
|
153
|
-
try {
|
|
154
|
-
eventLog.done();
|
|
155
|
-
}
|
|
156
|
-
catch { /* already done */ }
|
|
157
|
-
console.error('[weaver-bot] Background execution failed:', err);
|
|
158
|
-
}).finally(() => {
|
|
159
|
-
// Ensure registry is cleaned up even if runner's finally didn't run
|
|
160
|
-
runRegistry.remove(runId);
|
|
161
|
-
});
|
|
162
|
-
void runPromise;
|
|
163
|
-
return JSON.stringify({ runId, instruction: task.instruction, status: 'started' });
|
|
164
|
-
},
|
|
165
|
-
async fw_weaver_steer(args) {
|
|
166
|
-
const controller = new SteeringController();
|
|
167
|
-
await controller.write({
|
|
99
|
+
if (!botId)
|
|
100
|
+
return JSON.stringify({ error: 'botId required' });
|
|
101
|
+
const weaverDir = path.join(ctx.workspacePath, '.weaver');
|
|
102
|
+
fs.mkdirSync(weaverDir, { recursive: true });
|
|
103
|
+
const steerPath = path.join(weaverDir, `steer-${botId}.json`);
|
|
104
|
+
fs.writeFileSync(steerPath, JSON.stringify({
|
|
168
105
|
command: args.command,
|
|
169
106
|
payload: args.payload,
|
|
170
107
|
timestamp: Date.now(),
|
|
171
|
-
});
|
|
172
|
-
return
|
|
108
|
+
}, null, 2), 'utf-8');
|
|
109
|
+
return JSON.stringify({ sent: true, botId, command: args.command });
|
|
173
110
|
},
|
|
174
|
-
async
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return `Cleared ${count} task(s)`;
|
|
188
|
-
}
|
|
189
|
-
case 'remove': {
|
|
190
|
-
if (!args.id)
|
|
191
|
-
return 'Error: task ID required';
|
|
192
|
-
const removed = await queue.remove(args.id);
|
|
193
|
-
return removed ? `Removed ${args.id}` : `Not found: ${args.id}`;
|
|
194
|
-
}
|
|
195
|
-
case 'reorder': {
|
|
196
|
-
if (!args.id)
|
|
197
|
-
return 'Error: task ID required';
|
|
198
|
-
if (args.priority === undefined)
|
|
199
|
-
return 'Error: priority required';
|
|
200
|
-
const reordered = await queue.reorder(args.id, args.priority);
|
|
201
|
-
return reordered ? `Reordered ${args.id} to priority ${args.priority}` : `Not found: ${args.id}`;
|
|
202
|
-
}
|
|
203
|
-
default:
|
|
204
|
-
return `Unknown action: ${args.action}`;
|
|
205
|
-
}
|
|
206
|
-
},
|
|
207
|
-
async fw_weaver_status() {
|
|
208
|
-
const store = new SessionStore();
|
|
209
|
-
const state = store.load();
|
|
210
|
-
if (!state) {
|
|
211
|
-
return JSON.stringify({ status: 'no active session' }, null, 2);
|
|
212
|
-
}
|
|
213
|
-
// Startup cleanup: on first call after process restart, the registry is empty.
|
|
214
|
-
// If session.json claims 'executing' but nothing is in the registry, heal it.
|
|
215
|
-
if (!runRegistry.startupCleanupDone) {
|
|
216
|
-
runRegistry.markStartupCleanupDone();
|
|
217
|
-
if (state.status !== 'idle' && runRegistry.size === 0) {
|
|
218
|
-
// Orphaned session from a previous process — heal it
|
|
219
|
-
const orphanRunId = state.currentRunId;
|
|
220
|
-
if (orphanRunId) {
|
|
221
|
-
try {
|
|
222
|
-
new EventLog(orphanRunId).done();
|
|
223
|
-
}
|
|
224
|
-
catch { /* non-fatal */ }
|
|
225
|
-
}
|
|
226
|
-
await store.update({ status: 'idle', currentTask: null, currentRunId: null });
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
// Self-heal orphaned queue tasks: whenever session is idle and no runs are active,
|
|
230
|
-
// reset any 'running' queue tasks back to 'pending'.
|
|
231
|
-
// Throttled to max once per 30s to avoid excessive file I/O from polling.
|
|
232
|
-
if (state.status === 'idle' && runRegistry.size === 0) {
|
|
233
|
-
const now = Date.now();
|
|
234
|
-
if (now - lastOrphanRecoveryTime > 30_000) {
|
|
235
|
-
lastOrphanRecoveryTime = now;
|
|
236
|
-
try {
|
|
237
|
-
const queue = new TaskQueue();
|
|
238
|
-
await queue.recoverOrphans(new Set(runRegistry.activeRunIds()));
|
|
239
|
-
}
|
|
240
|
-
catch { /* non-fatal */ }
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
// Check if the current run is actually alive in the registry
|
|
244
|
-
const currentRunId = state.currentRunId ?? null;
|
|
245
|
-
const alive = currentRunId ? runRegistry.isAlive(currentRunId) : state.status === 'idle' ? null : runRegistry.size > 0;
|
|
246
|
-
// Self-heal: if status says executing but run is dead, clean up
|
|
247
|
-
if (currentRunId && alive === false && state.status !== 'idle') {
|
|
248
|
-
try {
|
|
249
|
-
new EventLog(currentRunId).done();
|
|
250
|
-
}
|
|
251
|
-
catch { /* non-fatal */ }
|
|
252
|
-
await store.update({ status: 'idle', currentTask: null, currentRunId: null });
|
|
253
|
-
return JSON.stringify({ ...state, status: 'idle', currentTask: null, currentRunId: null, alive: false }, null, 2);
|
|
254
|
-
}
|
|
255
|
-
return JSON.stringify({ ...state, alive }, null, 2);
|
|
111
|
+
async fw_weaver_status(_args, ctx) {
|
|
112
|
+
const controller = SwarmController.getInstance(ctx.workspacePath);
|
|
113
|
+
const state = controller.getStatus();
|
|
114
|
+
return JSON.stringify({
|
|
115
|
+
swarmStatus: state.status,
|
|
116
|
+
activeBots: Object.values(state.bots).filter(b => b.status === 'executing').length,
|
|
117
|
+
totalBots: Object.keys(state.bots).length,
|
|
118
|
+
tasksCompleted: state.tasksCompleted,
|
|
119
|
+
tasksFailed: state.tasksFailed,
|
|
120
|
+
totalTokensUsed: state.totalTokensUsed,
|
|
121
|
+
totalCost: state.totalCost,
|
|
122
|
+
budgets: state.budgets,
|
|
123
|
+
}, null, 2);
|
|
256
124
|
},
|
|
257
125
|
async fw_weaver_events(args) {
|
|
258
126
|
const runId = args.runId;
|
|
@@ -324,156 +192,6 @@ const toolHandlers = {
|
|
|
324
192
|
});
|
|
325
193
|
return JSON.stringify(result, null, 2);
|
|
326
194
|
},
|
|
327
|
-
async fw_weaver_session(args, ctx) {
|
|
328
|
-
const action = args.action;
|
|
329
|
-
const store = new SessionStore();
|
|
330
|
-
if (action === 'status') {
|
|
331
|
-
const state = store.load();
|
|
332
|
-
if (!state)
|
|
333
|
-
return JSON.stringify({ status: 'no active session' });
|
|
334
|
-
const crid = state.currentRunId ?? null;
|
|
335
|
-
const alive = crid ? runRegistry.isAlive(crid) : null;
|
|
336
|
-
return JSON.stringify({ ...state, alive }, null, 2);
|
|
337
|
-
}
|
|
338
|
-
if (action === 'stop') {
|
|
339
|
-
const controller = new SteeringController();
|
|
340
|
-
await controller.write({ command: 'cancel', timestamp: Date.now() });
|
|
341
|
-
await store.update({ status: 'idle', currentTask: null });
|
|
342
|
-
return JSON.stringify({ stopped: true });
|
|
343
|
-
}
|
|
344
|
-
if (action === 'start') {
|
|
345
|
-
const queue = new TaskQueue();
|
|
346
|
-
const tasks = await queue.list();
|
|
347
|
-
const pending = tasks.filter(t => t.status === 'pending');
|
|
348
|
-
if (pending.length === 0) {
|
|
349
|
-
return JSON.stringify({ error: 'No pending tasks in queue' });
|
|
350
|
-
}
|
|
351
|
-
const maxTasks = args.maxTasks ?? pending.length;
|
|
352
|
-
const sessionId = RunStore.newId().slice(0, 8);
|
|
353
|
-
const sessionLog = new EventLog(`session-${sessionId}`);
|
|
354
|
-
// Create session state (save, not update — update requires existing file)
|
|
355
|
-
await store.save({
|
|
356
|
-
sessionId,
|
|
357
|
-
status: 'executing',
|
|
358
|
-
currentTask: null,
|
|
359
|
-
completedTasks: 0,
|
|
360
|
-
totalCost: 0,
|
|
361
|
-
startedAt: Date.now(),
|
|
362
|
-
lastActivity: Date.now(),
|
|
363
|
-
});
|
|
364
|
-
sessionLog.emit({ type: 'session:started', timestamp: Date.now(), data: {
|
|
365
|
-
sessionId, queueSize: pending.length, maxTasks,
|
|
366
|
-
} });
|
|
367
|
-
const projectDir = ctx.workspacePath || process.cwd();
|
|
368
|
-
const packRoot = new URL('..', import.meta.url);
|
|
369
|
-
let workflowPath = fileURLToPath(new URL('src/workflows/weaver-bot.ts', packRoot));
|
|
370
|
-
if (!fs.existsSync(workflowPath)) {
|
|
371
|
-
workflowPath = fileURLToPath(new URL('dist/workflows/weaver-bot.js', packRoot));
|
|
372
|
-
}
|
|
373
|
-
// Process tasks sequentially in background
|
|
374
|
-
(async () => {
|
|
375
|
-
let completed = 0;
|
|
376
|
-
let failed = 0;
|
|
377
|
-
try {
|
|
378
|
-
for (let i = 0; i < Math.min(maxTasks, pending.length); i++) {
|
|
379
|
-
const task = pending[i];
|
|
380
|
-
// Check for cancel signal
|
|
381
|
-
try {
|
|
382
|
-
const ctrl = new SteeringController();
|
|
383
|
-
const signal = await ctrl.check();
|
|
384
|
-
if (signal?.command === 'cancel') {
|
|
385
|
-
sessionLog.emit({ type: 'session:cancelled', timestamp: Date.now() });
|
|
386
|
-
break;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
catch { /* non-fatal */ }
|
|
390
|
-
const runId = RunStore.newId();
|
|
391
|
-
const taskLog = new EventLog(runId);
|
|
392
|
-
// Mark task as running in the queue
|
|
393
|
-
try {
|
|
394
|
-
await queue.markRunning(task.id);
|
|
395
|
-
}
|
|
396
|
-
catch { /* non-fatal */ }
|
|
397
|
-
await store.update({
|
|
398
|
-
status: 'executing',
|
|
399
|
-
currentTask: task.instruction,
|
|
400
|
-
currentRunId: runId,
|
|
401
|
-
lastActivity: Date.now(),
|
|
402
|
-
});
|
|
403
|
-
sessionLog.emit({ type: 'session:task-start', timestamp: Date.now(), data: {
|
|
404
|
-
taskId: runId, instruction: task.instruction, index: i, total: Math.min(maxTasks, pending.length),
|
|
405
|
-
} });
|
|
406
|
-
taskLog.emit({ type: 'bot-started', timestamp: Date.now(), data: {
|
|
407
|
-
instruction: task.instruction, mode: 'create', runId,
|
|
408
|
-
} });
|
|
409
|
-
// Register in run registry BEFORE calling runWorkflow to prevent
|
|
410
|
-
// fw_weaver_events self-healing from writing premature .done
|
|
411
|
-
runRegistry.register(runId, { file: workflowPath, startedAt: new Date().toISOString(), promise: Promise.resolve() });
|
|
412
|
-
try {
|
|
413
|
-
const result = await runWorkflow(workflowPath, {
|
|
414
|
-
runId,
|
|
415
|
-
params: {
|
|
416
|
-
taskJson: JSON.stringify({
|
|
417
|
-
instruction: task.instruction,
|
|
418
|
-
mode: 'create',
|
|
419
|
-
options: { autoApprove: false },
|
|
420
|
-
}),
|
|
421
|
-
projectDir,
|
|
422
|
-
},
|
|
423
|
-
config: { provider: 'auto', approval: { mode: 'timeout-auto', timeoutSeconds: 300 } },
|
|
424
|
-
eventLog: taskLog,
|
|
425
|
-
});
|
|
426
|
-
const success = result.success;
|
|
427
|
-
completed += success ? 1 : 0;
|
|
428
|
-
failed += success ? 0 : 1;
|
|
429
|
-
sessionLog.emit({ type: 'session:task-complete', timestamp: Date.now(), data: {
|
|
430
|
-
taskId: runId, outcome: result.outcome, duration: result.executionTime,
|
|
431
|
-
cost: result.cost?.totalCost,
|
|
432
|
-
} });
|
|
433
|
-
// Mark task in queue
|
|
434
|
-
try {
|
|
435
|
-
await queue.remove(task.id);
|
|
436
|
-
}
|
|
437
|
-
catch { /* non-fatal */ }
|
|
438
|
-
}
|
|
439
|
-
catch (err) {
|
|
440
|
-
failed++;
|
|
441
|
-
sessionLog.emit({ type: 'session:task-complete', timestamp: Date.now(), data: {
|
|
442
|
-
taskId: runId, outcome: 'error', error: String(err),
|
|
443
|
-
} });
|
|
444
|
-
}
|
|
445
|
-
await store.update({
|
|
446
|
-
completedTasks: completed + failed,
|
|
447
|
-
currentRunId: null,
|
|
448
|
-
lastActivity: Date.now(),
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
sessionLog.emit({ type: 'session:ended', timestamp: Date.now(), data: {
|
|
452
|
-
reason: 'complete', completed, failed, total: completed + failed,
|
|
453
|
-
} });
|
|
454
|
-
}
|
|
455
|
-
catch (err) {
|
|
456
|
-
console.error('[weaver-session] Session failed:', err);
|
|
457
|
-
sessionLog.emit({ type: 'session:ended', timestamp: Date.now(), data: {
|
|
458
|
-
reason: 'error', error: String(err),
|
|
459
|
-
} });
|
|
460
|
-
}
|
|
461
|
-
finally {
|
|
462
|
-
// GUARANTEED: session log finalized and state reset, even on crash
|
|
463
|
-
try {
|
|
464
|
-
sessionLog.done();
|
|
465
|
-
}
|
|
466
|
-
catch { /* already done */ }
|
|
467
|
-
try {
|
|
468
|
-
await store.update({ status: 'idle', currentTask: null, currentRunId: null });
|
|
469
|
-
}
|
|
470
|
-
catch { /* non-fatal */ }
|
|
471
|
-
}
|
|
472
|
-
})();
|
|
473
|
-
return JSON.stringify({ started: true, sessionId, taskCount: Math.min(maxTasks, pending.length) });
|
|
474
|
-
}
|
|
475
|
-
return JSON.stringify({ error: `Unknown action: ${action}` });
|
|
476
|
-
},
|
|
477
195
|
async fw_weaver_insights(args, ctx) {
|
|
478
196
|
const projectDir = args.projectDir || ctx.workspacePath;
|
|
479
197
|
const pms = new ProjectModelStore(projectDir);
|
|
@@ -539,6 +257,116 @@ const toolHandlers = {
|
|
|
539
257
|
const ejected = registry.eject(id, newFilePath);
|
|
540
258
|
return JSON.stringify({ ejected, id, filePath: newFilePath });
|
|
541
259
|
},
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// Task CRUD tools (Swarm)
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
async fw_weaver_task_create(args, ctx) {
|
|
264
|
+
const store = new TaskStore(ctx.workspacePath);
|
|
265
|
+
const input = {
|
|
266
|
+
title: args.title,
|
|
267
|
+
description: args.description ?? '',
|
|
268
|
+
assignedBots: args.assignedBots,
|
|
269
|
+
priority: args.priority,
|
|
270
|
+
parentId: args.parentId,
|
|
271
|
+
dependsOn: args.dependsOn,
|
|
272
|
+
budgetTokens: args.budgetTokens,
|
|
273
|
+
budgetCost: args.budgetCost,
|
|
274
|
+
timeoutMs: args.timeoutMs,
|
|
275
|
+
maxAttempts: args.maxAttempts,
|
|
276
|
+
createdBy: 'ai',
|
|
277
|
+
subtasks: args.subtasks,
|
|
278
|
+
};
|
|
279
|
+
const task = await store.create(input);
|
|
280
|
+
const subtasks = task.isParent ? await store.getSubtasks(task.id) : [];
|
|
281
|
+
return JSON.stringify({ task, subtasks }, null, 2);
|
|
282
|
+
},
|
|
283
|
+
async fw_weaver_task_list(args, ctx) {
|
|
284
|
+
const store = new TaskStore(ctx.workspacePath);
|
|
285
|
+
const tasks = await store.list({
|
|
286
|
+
status: args.status,
|
|
287
|
+
parentId: args.parentId,
|
|
288
|
+
botId: args.botId,
|
|
289
|
+
limit: args.limit,
|
|
290
|
+
});
|
|
291
|
+
return JSON.stringify(tasks, null, 2);
|
|
292
|
+
},
|
|
293
|
+
async fw_weaver_task_get(args, ctx) {
|
|
294
|
+
const store = new TaskStore(ctx.workspacePath);
|
|
295
|
+
const task = await store.get(args.id);
|
|
296
|
+
if (!task)
|
|
297
|
+
return JSON.stringify({ error: 'Task not found' });
|
|
298
|
+
const subtasks = task.isParent ? await store.getSubtasks(task.id) : [];
|
|
299
|
+
return JSON.stringify({ task, subtasks }, null, 2);
|
|
300
|
+
},
|
|
301
|
+
async fw_weaver_task_update(args, ctx) {
|
|
302
|
+
const store = new TaskStore(ctx.workspacePath);
|
|
303
|
+
const { id, ...patch } = args;
|
|
304
|
+
const task = await store.update(id, patch);
|
|
305
|
+
return JSON.stringify(task, null, 2);
|
|
306
|
+
},
|
|
307
|
+
async fw_weaver_task_cancel(args, ctx) {
|
|
308
|
+
const store = new TaskStore(ctx.workspacePath);
|
|
309
|
+
const task = await store.update(args.id, { status: 'cancelled' });
|
|
310
|
+
return JSON.stringify({ cancelled: true, task });
|
|
311
|
+
},
|
|
312
|
+
async fw_weaver_task_retry(args, ctx) {
|
|
313
|
+
const store = new TaskStore(ctx.workspacePath);
|
|
314
|
+
const task = await store.get(args.id);
|
|
315
|
+
if (!task)
|
|
316
|
+
return JSON.stringify({ error: 'Task not found' });
|
|
317
|
+
const updated = await store.update(args.id, {
|
|
318
|
+
status: 'pending',
|
|
319
|
+
attempt: 0,
|
|
320
|
+
});
|
|
321
|
+
return JSON.stringify({ retried: true, task: updated });
|
|
322
|
+
},
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
// Swarm control tools
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
async fw_weaver_swarm_start(args, ctx) {
|
|
327
|
+
const controller = SwarmController.getInstance(ctx.workspacePath);
|
|
328
|
+
await controller.start({
|
|
329
|
+
maxConcurrent: args.maxConcurrent,
|
|
330
|
+
sessionBudgetTokens: args.sessionBudgetTokens,
|
|
331
|
+
sessionBudgetCost: args.sessionBudgetCost,
|
|
332
|
+
});
|
|
333
|
+
return JSON.stringify(controller.getStatus(), null, 2);
|
|
334
|
+
},
|
|
335
|
+
async fw_weaver_swarm_pause(_args, ctx) {
|
|
336
|
+
const controller = SwarmController.getInstance(ctx.workspacePath);
|
|
337
|
+
await controller.pause();
|
|
338
|
+
return JSON.stringify(controller.getStatus(), null, 2);
|
|
339
|
+
},
|
|
340
|
+
async fw_weaver_swarm_stop(_args, ctx) {
|
|
341
|
+
const controller = SwarmController.getInstance(ctx.workspacePath);
|
|
342
|
+
await controller.stop();
|
|
343
|
+
return JSON.stringify(controller.getStatus(), null, 2);
|
|
344
|
+
},
|
|
345
|
+
async fw_weaver_swarm_status(_args, ctx) {
|
|
346
|
+
const controller = SwarmController.getInstance(ctx.workspacePath);
|
|
347
|
+
return JSON.stringify(controller.getStatus(), null, 2);
|
|
348
|
+
},
|
|
349
|
+
async fw_weaver_swarm_config(args, ctx) {
|
|
350
|
+
const controller = SwarmController.getInstance(ctx.workspacePath);
|
|
351
|
+
controller.configure({
|
|
352
|
+
maxConcurrent: args.maxConcurrent,
|
|
353
|
+
workspaceBudgetTokens: args.workspaceBudgetTokens,
|
|
354
|
+
workspaceBudgetCost: args.workspaceBudgetCost,
|
|
355
|
+
sessionBudgetTokens: args.sessionBudgetTokens,
|
|
356
|
+
sessionBudgetCost: args.sessionBudgetCost,
|
|
357
|
+
autoRetry: args.autoRetry,
|
|
358
|
+
maxAttemptsDefault: args.maxAttemptsDefault,
|
|
359
|
+
});
|
|
360
|
+
return JSON.stringify(controller.getStatus(), null, 2);
|
|
361
|
+
},
|
|
362
|
+
async fw_weaver_swarm_events(args, ctx) {
|
|
363
|
+
const log = new SwarmEventLog(ctx.workspacePath);
|
|
364
|
+
const offset = args.offset ?? 0;
|
|
365
|
+
const events = log.tail(offset);
|
|
366
|
+
const controller = SwarmController.getInstance(ctx.workspacePath);
|
|
367
|
+
const done = controller.getStatus().status === 'idle';
|
|
368
|
+
return JSON.stringify({ events, done });
|
|
369
|
+
},
|
|
542
370
|
};
|
|
543
371
|
// ---------------------------------------------------------------------------
|
|
544
372
|
// Provider implementation
|
|
@@ -575,20 +403,35 @@ export default {
|
|
|
575
403
|
catch {
|
|
576
404
|
// system-prompt module not available — skip
|
|
577
405
|
}
|
|
578
|
-
// Weaver-specific capabilities
|
|
406
|
+
// Weaver-specific capabilities — swarm-aware tool listing
|
|
579
407
|
sections.push({
|
|
580
408
|
id: 'weaver-capabilities',
|
|
581
409
|
title: 'Weaver Capabilities',
|
|
582
|
-
content: `Beyond standard Flow Weaver tools, you
|
|
410
|
+
content: `Beyond standard Flow Weaver tools, you have swarm-based task and bot management:
|
|
411
|
+
|
|
412
|
+
**Task Management:**
|
|
413
|
+
- \`fw_weaver_task_create\`: Create tasks (with optional subtasks via \`subtasks\` array, \`^prev\` shorthand for dependency chaining, \`assignedBots\` to target specific bots)
|
|
414
|
+
- \`fw_weaver_task_list\`: List tasks (filter by status, parentId, botId)
|
|
415
|
+
- \`fw_weaver_task_get\`: Get task details including subtasks and context
|
|
416
|
+
- \`fw_weaver_task_update\`: Update task fields (title, description, priority, status)
|
|
417
|
+
- \`fw_weaver_task_cancel\`: Cancel a pending or running task
|
|
418
|
+
- \`fw_weaver_task_retry\`: Reset a failed task back to pending for retry
|
|
583
419
|
|
|
584
|
-
|
|
585
|
-
- \`
|
|
586
|
-
- \`
|
|
587
|
-
- \`
|
|
420
|
+
**Swarm Control:**
|
|
421
|
+
- \`fw_weaver_swarm_start\`: Start the swarm (spawns bot loops that claim and execute tasks)
|
|
422
|
+
- \`fw_weaver_swarm_pause\`: Pause all bot loops (tasks finish current step, then wait)
|
|
423
|
+
- \`fw_weaver_swarm_stop\`: Stop the swarm gracefully
|
|
424
|
+
- \`fw_weaver_swarm_status\`: Full swarm state with per-bot details
|
|
425
|
+
- \`fw_weaver_swarm_config\`: Configure max concurrency, budgets, auto-retry
|
|
426
|
+
- \`fw_weaver_swarm_events\`: Stream swarm-level events for live updates
|
|
427
|
+
|
|
428
|
+
**Bot & Workflow:**
|
|
429
|
+
- \`fw_weaver_steer\`: Send steering command to a specific bot (requires \`botId\`) — pause, resume, cancel
|
|
430
|
+
- \`fw_weaver_status\`: Quick swarm status summary
|
|
588
431
|
- \`fw_weaver_run\`: Execute any workflow directly
|
|
589
432
|
- \`fw_weaver_insights\`: Project health, trust level, cost summary, recommendations
|
|
590
|
-
- \`fw_weaver_costs\`: Token usage and cost breakdown
|
|
591
|
-
- \`fw_weaver_history\`: Past workflow execution history
|
|
433
|
+
- \`fw_weaver_costs\`: Token usage and cost breakdown (supports \`taskId\`/\`botId\` filters)
|
|
434
|
+
- \`fw_weaver_history\`: Past workflow execution history (supports \`taskId\`/\`botId\` filters)
|
|
592
435
|
- \`fw_weaver_providers\`: Available AI providers
|
|
593
436
|
- \`fw_weaver_genesis\`: Self-evolution cycle for workflows
|
|
594
437
|
- \`fw_weaver_list_bots\`: List all registered bots with validation status
|
|
@@ -597,6 +440,8 @@ export default {
|
|
|
597
440
|
- \`fw_weaver_validate_bot\`: Validate a bot's file and export
|
|
598
441
|
- \`fw_weaver_eject_bot\`: Mark a bot as ejected with a new file path
|
|
599
442
|
|
|
443
|
+
**Workflow:** To delegate work, create tasks with \`fw_weaver_task_create\`, then start the swarm with \`fw_weaver_swarm_start\`. Bots will automatically claim and execute tasks. Use \`fw_weaver_steer\` with a \`botId\` to control individual bots.
|
|
444
|
+
|
|
600
445
|
Proactively offer these when relevant.`,
|
|
601
446
|
priority: 15,
|
|
602
447
|
});
|
|
@@ -641,7 +486,7 @@ Proactively offer these when relevant.`,
|
|
|
641
486
|
// InsightEngine not available — skip
|
|
642
487
|
}
|
|
643
488
|
}
|
|
644
|
-
// Registered bots
|
|
489
|
+
// Registered bots — swarm-aware: instruct AI to use task_create, not fw_weaver_bot
|
|
645
490
|
if (context.workspacePath) {
|
|
646
491
|
try {
|
|
647
492
|
const registry = new BotRegistry(context.workspacePath);
|
|
@@ -651,13 +496,30 @@ Proactively offer these when relevant.`,
|
|
|
651
496
|
sections.push({
|
|
652
497
|
id: 'registered-bots',
|
|
653
498
|
title: 'Registered Bots',
|
|
654
|
-
content: `The user has registered bot workflows. To
|
|
499
|
+
content: `The user has registered bot workflows available for swarm execution. To assign work to a bot, use \`fw_weaver_task_create\` with \`assignedBots: ["${bots[0].id}"]\` — do NOT use the old \`fw_weaver_bot\` tool (it has been removed).\n\n${botLines}`,
|
|
655
500
|
priority: 25,
|
|
656
501
|
});
|
|
657
502
|
}
|
|
658
503
|
}
|
|
659
504
|
catch { /* non-fatal */ }
|
|
660
505
|
}
|
|
506
|
+
// Swarm status — include if swarm is active
|
|
507
|
+
if (context.workspacePath) {
|
|
508
|
+
try {
|
|
509
|
+
const controller = SwarmController.getInstance(context.workspacePath);
|
|
510
|
+
const swarmState = controller.getStatus();
|
|
511
|
+
if (swarmState.status !== 'idle') {
|
|
512
|
+
const activeBots = Object.values(swarmState.bots).filter(b => b.status === 'executing').length;
|
|
513
|
+
sections.push({
|
|
514
|
+
id: 'swarm-status',
|
|
515
|
+
title: 'Swarm Status',
|
|
516
|
+
content: `The swarm is currently **${swarmState.status}** with ${activeBots} active bot(s) out of ${Object.keys(swarmState.bots).length} total. Tasks completed: ${swarmState.tasksCompleted}, failed: ${swarmState.tasksFailed}. Total cost: $${swarmState.totalCost.toFixed(4)}, tokens: ${swarmState.totalTokensUsed}.`,
|
|
517
|
+
priority: 12,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
catch { /* non-fatal */ }
|
|
522
|
+
}
|
|
661
523
|
return sections;
|
|
662
524
|
},
|
|
663
525
|
};
|