@synergenius/flow-weaver-pack-weaver 0.9.59 → 0.9.77
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 +12 -0
- package/dist/ai-chat-provider.d.ts.map +1 -1
- package/dist/ai-chat-provider.js +351 -335
- package/dist/ai-chat-provider.js.map +1 -1
- package/dist/bot/agent-loop.d.ts +20 -0
- package/dist/bot/agent-loop.d.ts.map +1 -0
- package/dist/bot/agent-loop.js +331 -0
- package/dist/bot/agent-loop.js.map +1 -0
- package/dist/bot/ai-router.d.ts +19 -0
- package/dist/bot/ai-router.d.ts.map +1 -0
- package/dist/bot/ai-router.js +104 -0
- package/dist/bot/ai-router.js.map +1 -0
- 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/bot-registry.js +2 -2
- package/dist/bot/bot-registry.js.map +1 -1
- package/dist/bot/conversation-store.d.ts +1 -0
- package/dist/bot/conversation-store.d.ts.map +1 -1
- package/dist/bot/conversation-store.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/improve-loop.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/instance-manager.d.ts +31 -0
- package/dist/bot/instance-manager.d.ts.map +1 -0
- package/dist/bot/instance-manager.js +115 -0
- package/dist/bot/instance-manager.js.map +1 -0
- package/dist/bot/orchestrator.d.ts +36 -0
- package/dist/bot/orchestrator.d.ts.map +1 -0
- package/dist/bot/orchestrator.js +176 -0
- package/dist/bot/orchestrator.js.map +1 -0
- package/dist/bot/profile-store.d.ts +36 -0
- package/dist/bot/profile-store.d.ts.map +1 -0
- package/dist/bot/profile-store.js +208 -0
- package/dist/bot/profile-store.js.map +1 -0
- package/dist/bot/profile-types.d.ts +126 -0
- package/dist/bot/profile-types.d.ts.map +1 -0
- package/dist/bot/profile-types.js +7 -0
- package/dist/bot/profile-types.js.map +1 -0
- 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 +109 -0
- package/dist/bot/swarm-controller.d.ts.map +1 -0
- package/dist/bot/swarm-controller.js +640 -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 +22 -0
- package/dist/bot/task-prompt-builder.d.ts.map +1 -0
- package/dist/bot/task-prompt-builder.js +240 -0
- package/dist/bot/task-prompt-builder.js.map +1 -0
- package/dist/bot/task-store.d.ts +21 -0
- package/dist/bot/task-store.d.ts.map +1 -0
- package/dist/bot/task-store.js +364 -0
- package/dist/bot/task-store.js.map +1 -0
- package/dist/bot/task-types.d.ts +79 -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 +79 -54
- package/dist/cli-handlers.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +749 -0
- package/dist/cli.js.map +1 -0
- package/dist/docs/docs/weaver-bot-usage.md +35 -18
- package/dist/docs/docs/weaver-config.md +20 -0
- package/dist/docs/docs/weaver-task-queue.md +31 -19
- package/dist/docs/weaver-config.md +15 -9
- 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 +17 -0
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +98 -279
- 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/orchestrator-dispatch.d.ts +17 -0
- package/dist/node-types/orchestrator-dispatch.d.ts.map +1 -0
- package/dist/node-types/orchestrator-dispatch.js +63 -0
- package/dist/node-types/orchestrator-dispatch.js.map +1 -0
- package/dist/node-types/orchestrator-load-state.d.ts +16 -0
- package/dist/node-types/orchestrator-load-state.d.ts.map +1 -0
- package/dist/node-types/orchestrator-load-state.js +60 -0
- package/dist/node-types/orchestrator-load-state.js.map +1 -0
- package/dist/node-types/orchestrator-route.d.ts +16 -0
- package/dist/node-types/orchestrator-route.d.ts.map +1 -0
- package/dist/node-types/orchestrator-route.js +28 -0
- package/dist/node-types/orchestrator-route.js.map +1 -0
- package/dist/node-types/receive-task.d.ts +2 -3
- package/dist/node-types/receive-task.d.ts.map +1 -1
- package/dist/node-types/receive-task.js +3 -48
- package/dist/node-types/receive-task.js.map +1 -1
- package/dist/templates/weaver-template.d.ts +11 -0
- package/dist/templates/weaver-template.d.ts.map +1 -0
- package/dist/templates/weaver-template.js +53 -0
- package/dist/templates/weaver-template.js.map +1 -0
- package/dist/ui/bot-activity.js +2 -2
- package/dist/ui/bot-constants.d.ts +14 -0
- package/dist/ui/bot-constants.d.ts.map +1 -0
- package/dist/ui/bot-constants.js +189 -0
- package/dist/ui/bot-constants.js.map +1 -0
- package/dist/ui/bot-panel.js +207 -245
- package/dist/ui/bot-slot-card.js +141 -0
- package/dist/ui/budget-bar.js +59 -0
- package/dist/ui/chat-task-result.js +178 -0
- package/dist/ui/decision-log.js +136 -0
- package/dist/ui/profile-card.js +158 -0
- package/dist/ui/profile-editor.js +597 -0
- package/dist/ui/swarm-controls.js +245 -0
- package/dist/ui/swarm-dashboard.js +3012 -0
- package/dist/ui/task-create-form.js +98 -0
- package/dist/ui/task-detail-view.js +1044 -0
- package/dist/ui/task-pool-list.js +156 -0
- package/dist/workflows/orchestrator.d.ts +21 -0
- package/dist/workflows/orchestrator.d.ts.map +1 -0
- package/dist/workflows/orchestrator.js +281 -0
- package/dist/workflows/orchestrator.js.map +1 -0
- package/dist/workflows/weaver-bot-session.d.ts +65 -0
- package/dist/workflows/weaver-bot-session.d.ts.map +1 -0
- package/dist/workflows/weaver-bot-session.js +68 -0
- package/dist/workflows/weaver-bot-session.js.map +1 -0
- package/dist/workflows/weaver.d.ts +24 -0
- package/dist/workflows/weaver.d.ts.map +1 -0
- package/dist/workflows/weaver.js +28 -0
- package/dist/workflows/weaver.js.map +1 -0
- package/flowweaver.manifest.json +547 -133
- package/package.json +1 -1
- package/src/ai-chat-provider.ts +378 -371
- package/src/bot/ai-router.ts +132 -0
- 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/bot-registry.ts +2 -2
- package/src/bot/conversation-store.ts +2 -1
- package/src/bot/dashboard.ts +17 -8
- package/src/bot/improve-loop.ts +6 -6
- package/src/bot/index.ts +2 -4
- package/src/bot/instance-manager.ts +128 -0
- package/src/bot/orchestrator.ts +244 -0
- package/src/bot/profile-store.ts +225 -0
- package/src/bot/profile-types.ts +141 -0
- package/src/bot/run-store.ts +8 -0
- package/src/bot/runner.ts +9 -1
- package/src/bot/swarm-controller.ts +780 -0
- package/src/bot/swarm-event-log.ts +57 -0
- package/src/bot/task-prompt-builder.ts +309 -0
- package/src/bot/task-store.ts +407 -0
- package/src/bot/task-types.ts +100 -0
- package/src/bot/types.ts +8 -0
- package/src/cli-handlers.ts +78 -53
- package/src/docs/weaver-bot-usage.md +35 -18
- package/src/docs/weaver-config.md +20 -0
- package/src/docs/weaver-task-queue.md +31 -19
- package/src/index.ts +5 -4
- package/src/mcp-tools.ts +129 -372
- package/src/node-types/bot-report.ts +6 -24
- package/src/node-types/orchestrator-dispatch.ts +71 -0
- package/src/node-types/orchestrator-load-state.ts +66 -0
- package/src/node-types/orchestrator-route.ts +33 -0
- package/src/node-types/receive-task.ts +3 -57
- package/src/ui/bot-activity.tsx +2 -2
- package/src/ui/bot-constants.ts +192 -0
- package/src/ui/bot-panel.tsx +213 -247
- package/src/ui/bot-slot-card.tsx +139 -0
- package/src/ui/budget-bar.tsx +30 -0
- package/src/ui/chat-task-result.tsx +236 -0
- package/src/ui/decision-log.tsx +148 -0
- package/src/ui/profile-card.tsx +157 -0
- package/src/ui/profile-editor.tsx +384 -0
- package/src/ui/swarm-controls.tsx +260 -0
- package/src/ui/swarm-dashboard.tsx +647 -0
- package/src/ui/task-create-form.tsx +87 -0
- package/src/ui/task-detail-view.tsx +841 -0
- package/src/ui/task-pool-list.tsx +187 -0
- package/src/workflows/orchestrator.ts +302 -0
- package/dist/docs/weaver-bot-usage.md +0 -34
- package/dist/docs/weaver-genesis.md +0 -32
- package/dist/docs/weaver-task-queue.md +0 -34
- 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/error-guide.ts +0 -4
- package/src/bot/retry-utils.ts +0 -4
- 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
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as crypto from 'node:crypto';
|
|
4
|
+
import { AsyncMutex } from './async-mutex.js';
|
|
5
|
+
import type { Task, TaskFilter, CreateTaskInput, RunSummary } from './task-types.js';
|
|
6
|
+
|
|
7
|
+
/** Don't re-queue tasks completed/failed within this window (ms). */
|
|
8
|
+
const DEDUP_WINDOW_MS = 3_600_000; // 1 hour
|
|
9
|
+
|
|
10
|
+
export class TaskStore {
|
|
11
|
+
private readonly filePath: string;
|
|
12
|
+
private readonly mutex = new AsyncMutex();
|
|
13
|
+
|
|
14
|
+
constructor(private readonly projectDir: string) {
|
|
15
|
+
const dir = path.join(projectDir, '.weaver');
|
|
16
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
17
|
+
this.filePath = path.join(dir, 'tasks.json');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// CRUD
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
async create(input: CreateTaskInput): Promise<Task> {
|
|
25
|
+
return this.mutex.runExclusive(async () => {
|
|
26
|
+
const tasks = this._readAll();
|
|
27
|
+
|
|
28
|
+
// --- Subtask creation ---
|
|
29
|
+
if (input.subtasks && input.subtasks.length > 0) {
|
|
30
|
+
// Check dedup for the parent
|
|
31
|
+
const existing = this._findDuplicate(tasks, input.title, input.description);
|
|
32
|
+
if (existing) return existing;
|
|
33
|
+
|
|
34
|
+
const parentId = crypto.randomUUID().slice(0, 8);
|
|
35
|
+
const now = new Date().toISOString();
|
|
36
|
+
|
|
37
|
+
const parent: Task = {
|
|
38
|
+
id: parentId,
|
|
39
|
+
title: input.title,
|
|
40
|
+
description: input.description,
|
|
41
|
+
status: 'pending',
|
|
42
|
+
priority: input.priority ?? 0,
|
|
43
|
+
isParent: true,
|
|
44
|
+
parentId: input.parentId,
|
|
45
|
+
dependsOn: input.dependsOn ?? [],
|
|
46
|
+
currentBotId: undefined,
|
|
47
|
+
currentRunId: undefined,
|
|
48
|
+
context: { files: [], notes: '', runSummaries: [] },
|
|
49
|
+
runs: [],
|
|
50
|
+
attempt: 0,
|
|
51
|
+
maxAttempts: input.maxAttempts ?? 3,
|
|
52
|
+
budgetTokens: input.budgetTokens,
|
|
53
|
+
budgetCost: input.budgetCost,
|
|
54
|
+
tokensUsed: 0,
|
|
55
|
+
costUsed: 0,
|
|
56
|
+
timeoutMs: input.timeoutMs,
|
|
57
|
+
complexity: input.complexity,
|
|
58
|
+
assignedProfile: input.assignedProfile,
|
|
59
|
+
createdBy: input.createdBy ?? 'user',
|
|
60
|
+
createdAt: now,
|
|
61
|
+
updatedAt: now,
|
|
62
|
+
};
|
|
63
|
+
tasks.push(parent);
|
|
64
|
+
|
|
65
|
+
// Create subtasks with ^prev resolution
|
|
66
|
+
let prevSubtaskId: string | undefined;
|
|
67
|
+
for (const sub of input.subtasks) {
|
|
68
|
+
const subId = crypto.randomUUID().slice(0, 8);
|
|
69
|
+
const resolvedDeps = (sub.dependsOn ?? [])
|
|
70
|
+
.map(dep => {
|
|
71
|
+
if (dep === '^prev') return prevSubtaskId; // undefined if first
|
|
72
|
+
return dep;
|
|
73
|
+
})
|
|
74
|
+
.filter((d): d is string => d !== undefined);
|
|
75
|
+
|
|
76
|
+
const subtask: Task = {
|
|
77
|
+
id: subId,
|
|
78
|
+
title: sub.title,
|
|
79
|
+
description: sub.description ?? '',
|
|
80
|
+
status: 'pending',
|
|
81
|
+
priority: sub.priority ?? input.priority ?? 0,
|
|
82
|
+
isParent: false,
|
|
83
|
+
parentId: parentId,
|
|
84
|
+
dependsOn: resolvedDeps,
|
|
85
|
+
currentBotId: undefined,
|
|
86
|
+
currentRunId: undefined,
|
|
87
|
+
context: { files: [], notes: '', runSummaries: [] },
|
|
88
|
+
runs: [],
|
|
89
|
+
attempt: 0,
|
|
90
|
+
maxAttempts: input.maxAttempts ?? 3,
|
|
91
|
+
budgetTokens: input.budgetTokens,
|
|
92
|
+
budgetCost: input.budgetCost,
|
|
93
|
+
tokensUsed: 0,
|
|
94
|
+
costUsed: 0,
|
|
95
|
+
complexity: sub.complexity ?? input.complexity,
|
|
96
|
+
assignedProfile: sub.assignedProfile ?? input.assignedProfile,
|
|
97
|
+
createdBy: input.createdBy ?? 'user',
|
|
98
|
+
createdAt: now,
|
|
99
|
+
updatedAt: now,
|
|
100
|
+
};
|
|
101
|
+
tasks.push(subtask);
|
|
102
|
+
prevSubtaskId = subId;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this._writeAll(tasks);
|
|
106
|
+
return parent;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// --- Single task creation ---
|
|
110
|
+
const existing = this._findDuplicate(tasks, input.title, input.description);
|
|
111
|
+
if (existing) return existing;
|
|
112
|
+
|
|
113
|
+
const now = new Date().toISOString();
|
|
114
|
+
const task: Task = {
|
|
115
|
+
id: crypto.randomUUID().slice(0, 8),
|
|
116
|
+
title: input.title,
|
|
117
|
+
description: input.description,
|
|
118
|
+
status: 'pending',
|
|
119
|
+
priority: input.priority ?? 0,
|
|
120
|
+
isParent: false,
|
|
121
|
+
parentId: input.parentId,
|
|
122
|
+
dependsOn: input.dependsOn ?? [],
|
|
123
|
+
currentBotId: undefined,
|
|
124
|
+
currentRunId: undefined,
|
|
125
|
+
context: { files: [], notes: '', runSummaries: [] },
|
|
126
|
+
runs: [],
|
|
127
|
+
attempt: 0,
|
|
128
|
+
maxAttempts: input.maxAttempts ?? 3,
|
|
129
|
+
budgetTokens: input.budgetTokens,
|
|
130
|
+
budgetCost: input.budgetCost,
|
|
131
|
+
tokensUsed: 0,
|
|
132
|
+
costUsed: 0,
|
|
133
|
+
timeoutMs: input.timeoutMs,
|
|
134
|
+
complexity: input.complexity,
|
|
135
|
+
assignedProfile: input.assignedProfile,
|
|
136
|
+
createdBy: input.createdBy ?? 'user',
|
|
137
|
+
createdAt: now,
|
|
138
|
+
updatedAt: now,
|
|
139
|
+
};
|
|
140
|
+
tasks.push(task);
|
|
141
|
+
this._writeAll(tasks);
|
|
142
|
+
return task;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async get(id: string): Promise<Task | null> {
|
|
147
|
+
return this.mutex.runExclusive(async () => {
|
|
148
|
+
const tasks = this._readAll();
|
|
149
|
+
return tasks.find(t => t.id === id) ?? null;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async list(filter?: TaskFilter): Promise<Task[]> {
|
|
154
|
+
return this.mutex.runExclusive(async () => {
|
|
155
|
+
let tasks = this._readAll();
|
|
156
|
+
|
|
157
|
+
if (filter) {
|
|
158
|
+
if (filter.status !== undefined) {
|
|
159
|
+
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
160
|
+
tasks = tasks.filter(t => statuses.includes(t.status));
|
|
161
|
+
}
|
|
162
|
+
if (filter.parentId !== undefined) {
|
|
163
|
+
tasks = tasks.filter(t => t.parentId === filter.parentId);
|
|
164
|
+
}
|
|
165
|
+
if (filter.botId !== undefined) {
|
|
166
|
+
tasks = tasks.filter(t => t.currentBotId === filter.botId);
|
|
167
|
+
}
|
|
168
|
+
if (filter.limit !== undefined && filter.limit > 0) {
|
|
169
|
+
tasks = tasks.slice(0, filter.limit);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return tasks;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async update(id: string, patch: Partial<Task>): Promise<Task> {
|
|
178
|
+
return this.mutex.runExclusive(async () => {
|
|
179
|
+
const tasks = this._readAll();
|
|
180
|
+
const idx = tasks.findIndex(t => t.id === id);
|
|
181
|
+
if (idx === -1) throw new Error(`Task not found: ${id}`);
|
|
182
|
+
|
|
183
|
+
const task = tasks[idx];
|
|
184
|
+
Object.assign(task, patch, { updatedAt: new Date().toISOString() });
|
|
185
|
+
tasks[idx] = task;
|
|
186
|
+
this._writeAll(tasks);
|
|
187
|
+
return task;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async updateContext(id: string, runSummary: RunSummary): Promise<Task> {
|
|
192
|
+
return this.mutex.runExclusive(async () => {
|
|
193
|
+
const tasks = this._readAll();
|
|
194
|
+
const idx = tasks.findIndex(t => t.id === id);
|
|
195
|
+
if (idx === -1) throw new Error(`Task not found: ${id}`);
|
|
196
|
+
|
|
197
|
+
const task = tasks[idx];
|
|
198
|
+
task.context.runSummaries.push(runSummary);
|
|
199
|
+
|
|
200
|
+
if (runSummary.outcome === 'failed' || runSummary.outcome === 'error') {
|
|
201
|
+
task.context.lastError = runSummary.error;
|
|
202
|
+
} else {
|
|
203
|
+
task.context.lastError = undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
task.updatedAt = new Date().toISOString();
|
|
207
|
+
tasks[idx] = task;
|
|
208
|
+
this._writeAll(tasks);
|
|
209
|
+
return task;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
// Subtasks
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
async getSubtasks(parentId: string): Promise<Task[]> {
|
|
218
|
+
return this.mutex.runExclusive(async () => {
|
|
219
|
+
const tasks = this._readAll();
|
|
220
|
+
return tasks.filter(t => t.parentId === parentId);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
// Push-based assignment (orchestrator)
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
async assignToInstance(taskId: string, instanceId: string, profileId: string): Promise<Task> {
|
|
229
|
+
return this.mutex.runExclusive(async () => {
|
|
230
|
+
const tasks = this._readAll();
|
|
231
|
+
const idx = tasks.findIndex(t => t.id === taskId);
|
|
232
|
+
if (idx === -1) throw new Error(`Task not found: ${taskId}`);
|
|
233
|
+
|
|
234
|
+
const task = tasks[idx];
|
|
235
|
+
const assignable =
|
|
236
|
+
task.status === 'pending' ||
|
|
237
|
+
(task.status === 'failed' && task.attempt < task.maxAttempts);
|
|
238
|
+
if (!assignable) {
|
|
239
|
+
throw new Error(`Task ${taskId} is not assignable (status: ${task.status}, attempt: ${task.attempt}/${task.maxAttempts})`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
task.status = 'in-progress';
|
|
243
|
+
task.currentBotId = instanceId;
|
|
244
|
+
task.assignedProfile = profileId;
|
|
245
|
+
task.updatedAt = new Date().toISOString();
|
|
246
|
+
|
|
247
|
+
tasks[idx] = task;
|
|
248
|
+
this._writeAll(tasks);
|
|
249
|
+
return task;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// Release
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
async release(taskId: string, status: 'done' | 'failed', runSummary: RunSummary): Promise<Task> {
|
|
258
|
+
return this.mutex.runExclusive(async () => {
|
|
259
|
+
const tasks = this._readAll();
|
|
260
|
+
const idx = tasks.findIndex(t => t.id === taskId);
|
|
261
|
+
if (idx === -1) throw new Error(`Task not found: ${taskId}`);
|
|
262
|
+
|
|
263
|
+
const task = tasks[idx];
|
|
264
|
+
|
|
265
|
+
// Append run summary
|
|
266
|
+
task.context.runSummaries.push(runSummary);
|
|
267
|
+
if (runSummary.outcome === 'failed' || runSummary.outcome === 'error') {
|
|
268
|
+
task.context.lastError = runSummary.error;
|
|
269
|
+
} else {
|
|
270
|
+
task.context.lastError = undefined;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Accumulate usage
|
|
274
|
+
task.tokensUsed += runSummary.tokensUsed;
|
|
275
|
+
task.costUsed += runSummary.cost;
|
|
276
|
+
|
|
277
|
+
// Update execution tracking
|
|
278
|
+
task.attempt += 1;
|
|
279
|
+
task.status = status;
|
|
280
|
+
task.currentBotId = undefined;
|
|
281
|
+
task.currentRunId = undefined;
|
|
282
|
+
task.updatedAt = new Date().toISOString();
|
|
283
|
+
|
|
284
|
+
if (status === 'done') {
|
|
285
|
+
task.completedAt = new Date().toISOString();
|
|
286
|
+
}
|
|
287
|
+
if (status === 'failed') {
|
|
288
|
+
task.completedAt = new Date().toISOString();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
tasks[idx] = task;
|
|
292
|
+
|
|
293
|
+
// --- Post-release effects ---
|
|
294
|
+
this._handleDependencyEffects(tasks, task);
|
|
295
|
+
this._handleParentEffects(tasks, task);
|
|
296
|
+
|
|
297
|
+
this._writeAll(tasks);
|
|
298
|
+
return task;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Dependency + parent cascading effects
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
private _handleDependencyEffects(tasks: Task[], changedTask: Task): void {
|
|
307
|
+
if (changedTask.status === 'done') {
|
|
308
|
+
// Unblock dependents: set blocked tasks to pending if all deps are now done
|
|
309
|
+
const doneIds = new Set(tasks.filter(t => t.status === 'done').map(t => t.id));
|
|
310
|
+
for (const t of tasks) {
|
|
311
|
+
if (t.status === 'blocked' && t.dependsOn.includes(changedTask.id)) {
|
|
312
|
+
if (t.dependsOn.every(depId => doneIds.has(depId))) {
|
|
313
|
+
t.status = 'pending';
|
|
314
|
+
t.updatedAt = new Date().toISOString();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} else if (changedTask.status === 'failed') {
|
|
319
|
+
// Check if failure is terminal (no retries left)
|
|
320
|
+
if (changedTask.attempt >= changedTask.maxAttempts) {
|
|
321
|
+
// Block dependents
|
|
322
|
+
for (const t of tasks) {
|
|
323
|
+
if (t.status === 'pending' && t.dependsOn.includes(changedTask.id)) {
|
|
324
|
+
t.status = 'blocked';
|
|
325
|
+
t.updatedAt = new Date().toISOString();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private _handleParentEffects(tasks: Task[], changedTask: Task): void {
|
|
333
|
+
if (!changedTask.parentId) return;
|
|
334
|
+
|
|
335
|
+
const parentIdx = tasks.findIndex(t => t.id === changedTask.parentId);
|
|
336
|
+
if (parentIdx === -1) return;
|
|
337
|
+
|
|
338
|
+
const parent = tasks[parentIdx];
|
|
339
|
+
if (!parent.isParent) return;
|
|
340
|
+
|
|
341
|
+
const subtasks = tasks.filter(t => t.parentId === parent.id);
|
|
342
|
+
|
|
343
|
+
// All done => parent done
|
|
344
|
+
if (subtasks.every(s => s.status === 'done')) {
|
|
345
|
+
parent.status = 'done';
|
|
346
|
+
parent.completedAt = new Date().toISOString();
|
|
347
|
+
parent.updatedAt = new Date().toISOString();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Any terminal failure => parent failed
|
|
352
|
+
const hasTerminalFailure = subtasks.some(
|
|
353
|
+
s => s.status === 'failed' && s.attempt >= s.maxAttempts,
|
|
354
|
+
);
|
|
355
|
+
if (hasTerminalFailure) {
|
|
356
|
+
parent.status = 'failed';
|
|
357
|
+
parent.updatedAt = new Date().toISOString();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
// Deduplication
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
|
|
365
|
+
private _findDuplicate(tasks: Task[], title: string, description: string): Task | null {
|
|
366
|
+
// Check pending duplicates
|
|
367
|
+
const pendingDup = tasks.find(
|
|
368
|
+
t => t.status === 'pending' && t.title === title && t.description === description,
|
|
369
|
+
);
|
|
370
|
+
if (pendingDup) return pendingDup;
|
|
371
|
+
|
|
372
|
+
// Check recently completed/failed (within dedup window)
|
|
373
|
+
const now = Date.now();
|
|
374
|
+
const recentDup = tasks.find(
|
|
375
|
+
t =>
|
|
376
|
+
(t.status === 'done' || t.status === 'failed') &&
|
|
377
|
+
t.title === title &&
|
|
378
|
+
t.description === description &&
|
|
379
|
+
t.completedAt !== undefined &&
|
|
380
|
+
now - Date.parse(t.completedAt) < DEDUP_WINDOW_MS,
|
|
381
|
+
);
|
|
382
|
+
if (recentDup) return recentDup;
|
|
383
|
+
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
388
|
+
// File I/O — atomic write via temp + rename
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
|
|
391
|
+
private _readAll(): Task[] {
|
|
392
|
+
if (!fs.existsSync(this.filePath)) return [];
|
|
393
|
+
try {
|
|
394
|
+
const raw = fs.readFileSync(this.filePath, 'utf-8');
|
|
395
|
+
const data = JSON.parse(raw);
|
|
396
|
+
return Array.isArray(data) ? data : [];
|
|
397
|
+
} catch {
|
|
398
|
+
return [];
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private _writeAll(tasks: Task[]): void {
|
|
403
|
+
const tmpPath = this.filePath + '.tmp';
|
|
404
|
+
fs.writeFileSync(tmpPath, JSON.stringify(tasks, null, 2), 'utf-8');
|
|
405
|
+
fs.renameSync(tmpPath, this.filePath);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task entity types for the Weaver Swarm.
|
|
3
|
+
* Tasks are first-class entities with hierarchy, context, and budget.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface RunSummary {
|
|
7
|
+
runId: string;
|
|
8
|
+
botId: string;
|
|
9
|
+
outcome: 'success' | 'failed' | 'error';
|
|
10
|
+
summary: string;
|
|
11
|
+
filesModified: string[];
|
|
12
|
+
error?: string;
|
|
13
|
+
durationMs: number;
|
|
14
|
+
tokensUsed: number;
|
|
15
|
+
cost: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TaskContext {
|
|
19
|
+
files: string[];
|
|
20
|
+
notes: string;
|
|
21
|
+
runSummaries: RunSummary[];
|
|
22
|
+
lastError?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type TaskStatus = 'pending' | 'in-progress' | 'blocked' | 'done' | 'failed' | 'cancelled';
|
|
26
|
+
|
|
27
|
+
export interface Task {
|
|
28
|
+
id: string;
|
|
29
|
+
title: string;
|
|
30
|
+
description: string;
|
|
31
|
+
status: TaskStatus;
|
|
32
|
+
priority: number; // higher = higher priority
|
|
33
|
+
|
|
34
|
+
// Hierarchy
|
|
35
|
+
isParent: boolean;
|
|
36
|
+
parentId?: string;
|
|
37
|
+
dependsOn: string[];
|
|
38
|
+
|
|
39
|
+
// Assignment
|
|
40
|
+
currentBotId?: string;
|
|
41
|
+
currentRunId?: string;
|
|
42
|
+
|
|
43
|
+
// Context
|
|
44
|
+
context: TaskContext;
|
|
45
|
+
|
|
46
|
+
// Execution
|
|
47
|
+
runs: string[];
|
|
48
|
+
attempt: number;
|
|
49
|
+
maxAttempts: number;
|
|
50
|
+
|
|
51
|
+
// Budget
|
|
52
|
+
budgetTokens?: number;
|
|
53
|
+
budgetCost?: number;
|
|
54
|
+
tokensUsed: number;
|
|
55
|
+
costUsed: number;
|
|
56
|
+
|
|
57
|
+
// Timeout
|
|
58
|
+
timeoutMs?: number;
|
|
59
|
+
|
|
60
|
+
// Orchestrator routing
|
|
61
|
+
assignedProfile?: string; // profile ID assigned by orchestrator
|
|
62
|
+
routingReason?: string; // why this profile was chosen
|
|
63
|
+
complexity?: 'trivial' | 'simple' | 'moderate' | 'complex'; // task complexity estimate
|
|
64
|
+
|
|
65
|
+
// Metadata
|
|
66
|
+
createdBy: 'user' | 'ai';
|
|
67
|
+
createdAt: string;
|
|
68
|
+
updatedAt: string;
|
|
69
|
+
completedAt?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface TaskFilter {
|
|
73
|
+
status?: TaskStatus | TaskStatus[];
|
|
74
|
+
parentId?: string;
|
|
75
|
+
botId?: string;
|
|
76
|
+
limit?: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface CreateTaskInput {
|
|
80
|
+
title: string;
|
|
81
|
+
description: string;
|
|
82
|
+
priority?: number;
|
|
83
|
+
parentId?: string;
|
|
84
|
+
dependsOn?: string[];
|
|
85
|
+
budgetTokens?: number;
|
|
86
|
+
budgetCost?: number;
|
|
87
|
+
timeoutMs?: number;
|
|
88
|
+
maxAttempts?: number;
|
|
89
|
+
createdBy?: 'user' | 'ai';
|
|
90
|
+
complexity?: 'trivial' | 'simple' | 'moderate' | 'complex';
|
|
91
|
+
assignedProfile?: string;
|
|
92
|
+
subtasks?: Array<{
|
|
93
|
+
title: string;
|
|
94
|
+
description?: string;
|
|
95
|
+
dependsOn?: string[];
|
|
96
|
+
priority?: number;
|
|
97
|
+
complexity?: 'trivial' | 'simple' | 'moderate' | 'complex';
|
|
98
|
+
assignedProfile?: string;
|
|
99
|
+
}>;
|
|
100
|
+
}
|
package/src/bot/types.ts
CHANGED
|
@@ -205,6 +205,10 @@ export interface RunRecord {
|
|
|
205
205
|
cost?: RunCostSummary;
|
|
206
206
|
/** Lifecycle audit events (plan-created, approval, step-start/complete, etc.). */
|
|
207
207
|
auditTrail?: AuditEvent[];
|
|
208
|
+
/** Swarm task this run belongs to. */
|
|
209
|
+
taskId?: string;
|
|
210
|
+
/** Bot slot that executed this run. */
|
|
211
|
+
botId?: string;
|
|
208
212
|
}
|
|
209
213
|
|
|
210
214
|
export interface RunFilter {
|
|
@@ -214,6 +218,10 @@ export interface RunFilter {
|
|
|
214
218
|
since?: string;
|
|
215
219
|
before?: string;
|
|
216
220
|
limit?: number;
|
|
221
|
+
/** Filter runs belonging to a specific swarm task. */
|
|
222
|
+
taskId?: string;
|
|
223
|
+
/** Filter runs executed by a specific bot slot. */
|
|
224
|
+
botId?: string;
|
|
217
225
|
}
|
|
218
226
|
|
|
219
227
|
export interface RetentionPolicy {
|