@synergenius/flow-weaver-pack-weaver 0.9.62 → 0.9.78
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 +173 -19
- 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/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/improve-loop.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/session-state.d.ts +25 -0
- package/dist/bot/session-state.d.ts.map +1 -0
- package/dist/bot/session-state.js +110 -0
- package/dist/bot/session-state.js.map +1 -0
- package/dist/bot/swarm-controller.d.ts +37 -21
- package/dist/bot/swarm-controller.d.ts.map +1 -1
- package/dist/bot/swarm-controller.js +344 -163
- package/dist/bot/swarm-controller.js.map +1 -1
- package/dist/bot/task-prompt-builder.d.ts +2 -1
- package/dist/bot/task-prompt-builder.d.ts.map +1 -1
- package/dist/bot/task-prompt-builder.js +33 -10
- package/dist/bot/task-prompt-builder.js.map +1 -1
- package/dist/bot/task-queue.d.ts +46 -0
- package/dist/bot/task-queue.d.ts.map +1 -0
- package/dist/bot/task-queue.js +237 -0
- package/dist/bot/task-queue.js.map +1 -0
- package/dist/bot/task-store.d.ts +1 -6
- package/dist/bot/task-store.d.ts.map +1 -1
- package/dist/bot/task-store.js +27 -78
- package/dist/bot/task-store.js.map +1 -1
- package/dist/bot/task-types.d.ts +8 -4
- package/dist/bot/task-types.d.ts.map +1 -1
- package/dist/cli-handlers.d.ts.map +1 -1
- package/dist/cli-handlers.js +2 -3
- 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/mcp-tools.d.ts +17 -0
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +98 -232
- package/dist/mcp-tools.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 -28
- 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-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 +51 -90
- package/dist/ui/bot-slot-card.js +87 -122
- package/dist/ui/budget-bar.js +5 -3
- package/dist/ui/chat-task-result.js +4 -7
- 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 +36 -27
- package/dist/ui/swarm-dashboard.js +2034 -736
- package/dist/ui/task-create-form.js +39 -116
- package/dist/ui/task-detail-view.js +490 -239
- package/dist/ui/task-pool-list.js +69 -94
- 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 +253 -66
- package/package.json +1 -1
- package/src/ai-chat-provider.ts +184 -18
- package/src/bot/ai-router.ts +132 -0
- package/src/bot/bot-registry.ts +2 -2
- package/src/bot/conversation-store.ts +2 -1
- package/src/bot/improve-loop.ts +6 -6
- 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/swarm-controller.ts +385 -186
- package/src/bot/task-prompt-builder.ts +37 -6
- package/src/bot/task-store.ts +28 -89
- package/src/bot/task-types.ts +10 -4
- package/src/cli-handlers.ts +2 -3
- 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/mcp-tools.ts +129 -320
- 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 -26
- package/src/ui/bot-constants.ts +192 -0
- package/src/ui/bot-panel.tsx +55 -79
- package/src/ui/bot-slot-card.tsx +69 -117
- package/src/ui/budget-bar.tsx +5 -3
- package/src/ui/chat-task-result.tsx +6 -9
- 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 +35 -31
- package/src/ui/swarm-dashboard.tsx +409 -80
- package/src/ui/task-create-form.tsx +29 -119
- package/src/ui/task-detail-view.tsx +461 -215
- package/src/ui/task-pool-list.tsx +74 -95
- 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/src/bot/error-guide.ts +0 -4
- package/src/bot/retry-utils.ts +0 -4
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrator — the routing brain that decides which tasks go to which bot instances.
|
|
3
|
+
*
|
|
4
|
+
* Pure routing logic: no side-effects beyond decision logging.
|
|
5
|
+
* Fast-path cascade: exact-match → single-eligible → ai-routed → round-robin.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
BotInstance,
|
|
10
|
+
BotProfile,
|
|
11
|
+
OrchestratorDecision,
|
|
12
|
+
OrchestratorInput,
|
|
13
|
+
OrchestratorOutput,
|
|
14
|
+
} from './profile-types.js';
|
|
15
|
+
|
|
16
|
+
type Task = OrchestratorInput['pendingTasks'][number];
|
|
17
|
+
type Assignment = OrchestratorOutput['assignments'][number];
|
|
18
|
+
type ScaleAction = OrchestratorOutput['scaleActions'][number];
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// AI Router types
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
export interface AIRouterResult {
|
|
25
|
+
profileId: string;
|
|
26
|
+
reason: string;
|
|
27
|
+
confidence: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AIRouter {
|
|
31
|
+
route(task: OrchestratorInput['pendingTasks'][0], candidates: BotProfile[]): Promise<AIRouterResult>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface OrchestratorOptions {
|
|
35
|
+
aiRouter?: AIRouter;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class Orchestrator {
|
|
39
|
+
private _decisionLog: OrchestratorDecision[] = [];
|
|
40
|
+
private _nextDecisionId = 1;
|
|
41
|
+
private _aiRouter?: AIRouter;
|
|
42
|
+
|
|
43
|
+
constructor(options?: OrchestratorOptions) {
|
|
44
|
+
this._aiRouter = options?.aiRouter;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Public API
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
async route(input: OrchestratorInput): Promise<OrchestratorOutput> {
|
|
52
|
+
const assignments: Assignment[] = [];
|
|
53
|
+
const scaleActions: ScaleAction[] = [];
|
|
54
|
+
const skippedTasks: OrchestratorOutput['skippedTasks'] = [];
|
|
55
|
+
|
|
56
|
+
// Track instances claimed during this routing cycle to avoid double-assignment.
|
|
57
|
+
const claimedInstanceIds = new Set<string>();
|
|
58
|
+
|
|
59
|
+
// Sort tasks by priority DESC (higher number = higher priority).
|
|
60
|
+
const sorted = [...input.pendingTasks].sort((a, b) => b.priority - a.priority);
|
|
61
|
+
|
|
62
|
+
for (const task of sorted) {
|
|
63
|
+
// Budget guard
|
|
64
|
+
if (input.budgetRemaining.tokens <= 0 || input.budgetRemaining.cost <= 0) {
|
|
65
|
+
skippedTasks.push({ taskId: task.id, reason: 'budget-exhausted' });
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const eligible = this._findEligible(task, input.profiles);
|
|
70
|
+
|
|
71
|
+
if (eligible.length === 0) {
|
|
72
|
+
skippedTasks.push({ taskId: task.id, reason: 'no-eligible-profile' });
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const result = await this._selectInstance(task, eligible, input.instances, claimedInstanceIds);
|
|
77
|
+
|
|
78
|
+
if (result) {
|
|
79
|
+
claimedInstanceIds.add(result.instanceId);
|
|
80
|
+
assignments.push({
|
|
81
|
+
taskId: task.id,
|
|
82
|
+
profileId: result.profileId,
|
|
83
|
+
instanceId: result.instanceId,
|
|
84
|
+
reason: result.reason,
|
|
85
|
+
method: result.method,
|
|
86
|
+
confidence: result.confidence,
|
|
87
|
+
});
|
|
88
|
+
this._recordDecision(task, result, eligible);
|
|
89
|
+
} else {
|
|
90
|
+
// No idle instance available — request scale-up if possible.
|
|
91
|
+
const scaleProfile = eligible[0];
|
|
92
|
+
const currentCount = input.instances.filter(
|
|
93
|
+
(i) => i.profileId === scaleProfile.id,
|
|
94
|
+
).length;
|
|
95
|
+
|
|
96
|
+
if (currentCount < scaleProfile.maxInstances) {
|
|
97
|
+
// Only add one scale-up action per profile.
|
|
98
|
+
if (!scaleActions.some((sa) => sa.profileId === scaleProfile.id)) {
|
|
99
|
+
scaleActions.push({
|
|
100
|
+
profileId: scaleProfile.id,
|
|
101
|
+
action: 'scale-up',
|
|
102
|
+
targetInstances: currentCount + 1,
|
|
103
|
+
reason: `idle instances exhausted for profile ${scaleProfile.id}`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
skippedTasks.push({ taskId: task.id, reason: 'no-idle-instance' });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { assignments, scaleActions, skippedTasks };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
getDecisionLog(limit?: number): OrchestratorDecision[] {
|
|
116
|
+
if (limit === undefined) return [...this._decisionLog];
|
|
117
|
+
return this._decisionLog.slice(-limit);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
clearDecisionLog(): void {
|
|
121
|
+
this._decisionLog = [];
|
|
122
|
+
this._nextDecisionId = 1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Internal
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
/** Find profiles eligible for a task. */
|
|
130
|
+
private _findEligible(task: Task, profiles: BotProfile[]): BotProfile[] {
|
|
131
|
+
if (task.assignedProfile) {
|
|
132
|
+
const match = profiles.filter(p => p.id === task.assignedProfile);
|
|
133
|
+
if (match.length > 0) return match;
|
|
134
|
+
}
|
|
135
|
+
return profiles;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Select the best instance for a task from eligible profiles. */
|
|
139
|
+
private async _selectInstance(
|
|
140
|
+
task: Task,
|
|
141
|
+
eligible: BotProfile[],
|
|
142
|
+
instances: BotInstance[],
|
|
143
|
+
claimed: Set<string>,
|
|
144
|
+
): Promise<{ profileId: string; instanceId: string; method: OrchestratorDecision['method']; reason: string; confidence?: number } | null> {
|
|
145
|
+
// Determine routing method
|
|
146
|
+
let method = this._routingMethod(task, eligible);
|
|
147
|
+
|
|
148
|
+
if (eligible.length === 1) {
|
|
149
|
+
const profile = eligible[0];
|
|
150
|
+
const idle = instances.find(
|
|
151
|
+
(i) => i.profileId === profile.id && i.status === 'idle' && !claimed.has(i.instanceId),
|
|
152
|
+
);
|
|
153
|
+
if (!idle) return null;
|
|
154
|
+
return {
|
|
155
|
+
profileId: profile.id,
|
|
156
|
+
instanceId: idle.instanceId,
|
|
157
|
+
method,
|
|
158
|
+
reason: `${method}: ${profile.id}`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// AI routing: when multiple eligible profiles and AI router is provided
|
|
163
|
+
if (this._aiRouter && eligible.length > 1) {
|
|
164
|
+
try {
|
|
165
|
+
const aiResult = await this._aiRouter.route(task, eligible);
|
|
166
|
+
const aiProfile = eligible.find((p) => p.id === aiResult.profileId);
|
|
167
|
+
if (aiProfile) {
|
|
168
|
+
const idle = instances.find(
|
|
169
|
+
(i) => i.profileId === aiProfile.id && i.status === 'idle' && !claimed.has(i.instanceId),
|
|
170
|
+
);
|
|
171
|
+
if (idle) {
|
|
172
|
+
return {
|
|
173
|
+
profileId: aiProfile.id,
|
|
174
|
+
instanceId: idle.instanceId,
|
|
175
|
+
method: 'ai-routed',
|
|
176
|
+
reason: `ai-routed: ${aiProfile.id} — ${aiResult.reason}`,
|
|
177
|
+
confidence: aiResult.confidence,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// AI chose a profile but no idle instance — fall through to round-robin
|
|
181
|
+
}
|
|
182
|
+
// AI returned unknown profile — fall through to round-robin
|
|
183
|
+
} catch {
|
|
184
|
+
// AI call failed — fall through to round-robin
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Round-robin: pick profile with most idle instances
|
|
189
|
+
method = 'round-robin';
|
|
190
|
+
let bestProfile: BotProfile | null = null;
|
|
191
|
+
let bestIdleCount = -1;
|
|
192
|
+
let bestInstance: BotInstance | null = null;
|
|
193
|
+
|
|
194
|
+
for (const profile of eligible) {
|
|
195
|
+
const idleInstances = instances.filter(
|
|
196
|
+
(i) => i.profileId === profile.id && i.status === 'idle' && !claimed.has(i.instanceId),
|
|
197
|
+
);
|
|
198
|
+
if (idleInstances.length > bestIdleCount) {
|
|
199
|
+
bestIdleCount = idleInstances.length;
|
|
200
|
+
bestProfile = profile;
|
|
201
|
+
bestInstance = idleInstances[0] ?? null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!bestProfile || !bestInstance) return null;
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
profileId: bestProfile.id,
|
|
209
|
+
instanceId: bestInstance.instanceId,
|
|
210
|
+
method,
|
|
211
|
+
reason: `${method}: ${bestProfile.id}`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Determine which routing method was used. */
|
|
216
|
+
private _routingMethod(
|
|
217
|
+
task: Task,
|
|
218
|
+
eligible: BotProfile[],
|
|
219
|
+
): OrchestratorDecision['method'] {
|
|
220
|
+
if (task.assignedProfile) return 'exact-match';
|
|
221
|
+
if (eligible.length === 1) return 'single-eligible';
|
|
222
|
+
return 'round-robin';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Record a decision in the log. */
|
|
226
|
+
private _recordDecision(
|
|
227
|
+
task: Task,
|
|
228
|
+
result: { profileId: string; instanceId: string; method: OrchestratorDecision['method']; reason: string; confidence?: number },
|
|
229
|
+
eligible: BotProfile[],
|
|
230
|
+
): void {
|
|
231
|
+
this._decisionLog.push({
|
|
232
|
+
id: this._nextDecisionId++,
|
|
233
|
+
timestamp: Date.now(),
|
|
234
|
+
taskId: task.id,
|
|
235
|
+
taskTitle: task.title,
|
|
236
|
+
assignedProfileId: result.profileId,
|
|
237
|
+
assignedInstanceId: result.instanceId,
|
|
238
|
+
reason: result.reason,
|
|
239
|
+
method: result.method,
|
|
240
|
+
candidateProfiles: eligible.map((p) => p.id),
|
|
241
|
+
confidence: result.confidence,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as crypto from 'node:crypto';
|
|
4
|
+
import type { BotProfile, CreateProfileInput, Capability } from './profile-types.js';
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Default profiles — enterprise-ready starting kit
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
const DEFAULT_PROFILES: Record<string, Array<Omit<CreateProfileInput, 'botId'>>> = {
|
|
11
|
+
'weaver-bot': [
|
|
12
|
+
{
|
|
13
|
+
name: 'Developer',
|
|
14
|
+
description: 'Builds features, fixes bugs, writes tests. The everyday workhorse.',
|
|
15
|
+
icon: 'code',
|
|
16
|
+
color: 'color-node-blue-icon',
|
|
17
|
+
capabilities: [
|
|
18
|
+
{ name: 'feature-development', description: 'Build new features end-to-end from specs or tickets' },
|
|
19
|
+
{ name: 'bug-fixing', description: 'Diagnose and fix bugs from error reports or failing tests' },
|
|
20
|
+
{ name: 'testing', description: 'Write and maintain unit, integration, and e2e tests' },
|
|
21
|
+
{ name: 'refactoring', description: 'Improve code structure without changing behavior' },
|
|
22
|
+
],
|
|
23
|
+
preferences: { costStrategy: 'balanced', requireApproval: false },
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'Reviewer',
|
|
27
|
+
description: 'Reviews code, validates architecture, writes documentation. Quality gate.',
|
|
28
|
+
icon: 'verified',
|
|
29
|
+
color: 'color-node-green-icon',
|
|
30
|
+
capabilities: [
|
|
31
|
+
{ name: 'code-review', description: 'Review code for correctness, security, and maintainability' },
|
|
32
|
+
{ name: 'architecture-review', description: 'Validate system design and architectural decisions' },
|
|
33
|
+
{ name: 'documentation', description: 'Write technical docs, ADRs, and changelogs' },
|
|
34
|
+
],
|
|
35
|
+
preferences: { costStrategy: 'performance', requireApproval: true, instructions: 'Be thorough. Prioritize correctness and security over speed.' },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'Ops',
|
|
39
|
+
description: 'Handles deployments, CI/CD, monitoring, and infrastructure.',
|
|
40
|
+
icon: 'rocketLaunch',
|
|
41
|
+
color: 'color-node-orange-icon',
|
|
42
|
+
capabilities: [
|
|
43
|
+
{ name: 'deployment', description: 'Deploy services, manage releases, run migrations' },
|
|
44
|
+
{ name: 'ci-cd', description: 'Configure and maintain CI/CD pipelines' },
|
|
45
|
+
{ name: 'monitoring', description: 'Set up alerts, dashboards, and health checks' },
|
|
46
|
+
],
|
|
47
|
+
preferences: { costStrategy: 'balanced', requireApproval: true, instructions: 'Production changes require care. Verify before applying.' },
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
'weaver-genesis': [
|
|
51
|
+
{
|
|
52
|
+
name: 'Evolution',
|
|
53
|
+
description: 'Self-improvement cycles. Analyzes and evolves workflows autonomously.',
|
|
54
|
+
icon: 'autoAwesome',
|
|
55
|
+
color: 'color-node-cyan-icon',
|
|
56
|
+
capabilities: [
|
|
57
|
+
{ name: 'workflow-evolution', description: 'Analyze and improve existing Flow Weaver workflows' },
|
|
58
|
+
{ name: 'self-improvement', description: 'Identify and fix issues in bot behavior and prompts' },
|
|
59
|
+
],
|
|
60
|
+
preferences: { costStrategy: 'performance', requireApproval: true, instructions: 'Evolution changes affect the system itself. Always require approval.' },
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// ProfileStore
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
export class ProfileStore {
|
|
70
|
+
private readonly filePath: string;
|
|
71
|
+
private profiles: BotProfile[];
|
|
72
|
+
|
|
73
|
+
constructor(private readonly projectDir: string) {
|
|
74
|
+
const dir = path.join(projectDir, '.weaver');
|
|
75
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
76
|
+
this.filePath = path.join(dir, 'profiles.json');
|
|
77
|
+
this.profiles = this._loadFromDisk();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// CRUD
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Create a new profile. Note: `botId` is NOT validated against BotRegistry
|
|
86
|
+
* here to avoid a circular dependency. Validation happens at dispatch time
|
|
87
|
+
* in SwarmController when building the profileBotMap.
|
|
88
|
+
*/
|
|
89
|
+
create(input: CreateProfileInput): BotProfile {
|
|
90
|
+
const now = new Date().toISOString();
|
|
91
|
+
|
|
92
|
+
let id = this._slugify(input.name);
|
|
93
|
+
if (this.profiles.some(p => p.id === id)) {
|
|
94
|
+
id = `${id}-${crypto.randomBytes(3).toString('hex')}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const profile: BotProfile = {
|
|
98
|
+
id,
|
|
99
|
+
name: input.name,
|
|
100
|
+
description: input.description ?? '',
|
|
101
|
+
icon: input.icon ?? 'smartToy',
|
|
102
|
+
color: input.color ?? 'color-node-blue-icon',
|
|
103
|
+
botId: input.botId,
|
|
104
|
+
capabilities: input.capabilities ?? [],
|
|
105
|
+
preferences: {
|
|
106
|
+
costStrategy: input.preferences?.costStrategy ?? 'balanced',
|
|
107
|
+
maxCostPerRun: input.preferences?.maxCostPerRun,
|
|
108
|
+
maxCostPerTask: input.preferences?.maxCostPerTask,
|
|
109
|
+
requireApproval: input.preferences?.requireApproval ?? false,
|
|
110
|
+
instructions: input.preferences?.instructions,
|
|
111
|
+
},
|
|
112
|
+
minInstances: input.minInstances ?? 0,
|
|
113
|
+
maxInstances: input.maxInstances ?? 1,
|
|
114
|
+
currentInstances: 0,
|
|
115
|
+
createdAt: now,
|
|
116
|
+
updatedAt: now,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
this.profiles.push(profile);
|
|
120
|
+
this._save();
|
|
121
|
+
return profile;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
list(): BotProfile[] {
|
|
125
|
+
return [...this.profiles];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
get(id: string): BotProfile | null {
|
|
129
|
+
return this.profiles.find(p => p.id === id) ?? null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
update(id: string, patch: Partial<Omit<BotProfile, 'id' | 'createdAt'>>): BotProfile {
|
|
133
|
+
const idx = this.profiles.findIndex(p => p.id === id);
|
|
134
|
+
if (idx === -1) throw new Error(`Profile not found: ${id}`);
|
|
135
|
+
|
|
136
|
+
// Prevent overriding immutable fields
|
|
137
|
+
const { id: _id, createdAt: _createdAt, ...safePatch } = patch as Record<string, unknown>;
|
|
138
|
+
|
|
139
|
+
const profile = this.profiles[idx];
|
|
140
|
+
Object.assign(profile, safePatch, { updatedAt: new Date().toISOString() });
|
|
141
|
+
this.profiles[idx] = profile;
|
|
142
|
+
this._save();
|
|
143
|
+
return profile;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
delete(id: string): boolean {
|
|
147
|
+
const idx = this.profiles.findIndex(p => p.id === id);
|
|
148
|
+
if (idx === -1) return false;
|
|
149
|
+
this.profiles.splice(idx, 1);
|
|
150
|
+
this._save();
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Capability search
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
findByCapabilities(required: string[]): BotProfile[] {
|
|
159
|
+
return this.profiles.filter(p =>
|
|
160
|
+
required.every(name => p.capabilities.some(c => c.name === name)),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// Default profiles
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Create enterprise-ready default profiles for registered bots.
|
|
170
|
+
* Only creates profiles if none exist for a given botId.
|
|
171
|
+
*
|
|
172
|
+
* For weaver-bot: Developer, Reviewer, Ops
|
|
173
|
+
* For genesis: Evolution
|
|
174
|
+
* For unknown bots: one generic profile
|
|
175
|
+
*/
|
|
176
|
+
ensureDefaultProfiles(bots: Array<{ id: string; name: string; icon?: string; color?: string }>): void {
|
|
177
|
+
for (const bot of bots) {
|
|
178
|
+
if (this.profiles.some(p => p.botId === bot.id)) continue;
|
|
179
|
+
|
|
180
|
+
const defaults = DEFAULT_PROFILES[bot.id];
|
|
181
|
+
if (defaults) {
|
|
182
|
+
for (const def of defaults) {
|
|
183
|
+
if (this.profiles.some(p => p.id === def.name.toLowerCase().replace(/[^a-z0-9]+/g, '-'))) continue;
|
|
184
|
+
this.create({ ...def, botId: bot.id });
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
this.create({
|
|
188
|
+
name: bot.name,
|
|
189
|
+
botId: bot.id,
|
|
190
|
+
icon: bot.icon,
|
|
191
|
+
color: bot.color,
|
|
192
|
+
capabilities: [{ name: 'general', description: 'General-purpose task execution' }],
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// File I/O — atomic write via temp + rename
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
private _loadFromDisk(): BotProfile[] {
|
|
203
|
+
if (!fs.existsSync(this.filePath)) return [];
|
|
204
|
+
try {
|
|
205
|
+
const raw = fs.readFileSync(this.filePath, 'utf-8');
|
|
206
|
+
const data = JSON.parse(raw);
|
|
207
|
+
return Array.isArray(data) ? data : [];
|
|
208
|
+
} catch {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private _save(): void {
|
|
214
|
+
const tmpPath = this.filePath + '.tmp';
|
|
215
|
+
fs.writeFileSync(tmpPath, JSON.stringify(this.profiles, null, 2), 'utf-8');
|
|
216
|
+
fs.renameSync(tmpPath, this.filePath);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private _slugify(name: string): string {
|
|
220
|
+
return name
|
|
221
|
+
.toLowerCase()
|
|
222
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
223
|
+
.replace(/^-|-$/g, '');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bot profile and orchestrator types for the Weaver Swarm.
|
|
3
|
+
* Profiles wrap BotRegistry entries with scaling, budget, and routing config.
|
|
4
|
+
* The orchestrator uses these types to route tasks to the best available bot.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** A free-form capability that a bot profile can declare. */
|
|
8
|
+
export interface Capability {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Intent-based cost strategy for execution preferences. */
|
|
14
|
+
export type CostStrategy = 'frugal' | 'balanced' | 'performance';
|
|
15
|
+
|
|
16
|
+
/** Execution preferences for a bot profile. */
|
|
17
|
+
export interface ProfilePreferences {
|
|
18
|
+
costStrategy: CostStrategy;
|
|
19
|
+
maxCostPerRun?: number;
|
|
20
|
+
maxCostPerTask?: number;
|
|
21
|
+
requireApproval: boolean;
|
|
22
|
+
instructions?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A bot profile wrapping a BotRegistry entry with scaling and budget config. */
|
|
26
|
+
export interface BotProfile {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
icon: string;
|
|
31
|
+
color: string;
|
|
32
|
+
|
|
33
|
+
/** References an entry in BotRegistry. */
|
|
34
|
+
botId: string;
|
|
35
|
+
|
|
36
|
+
/** Declared capabilities for routing. */
|
|
37
|
+
capabilities: Capability[];
|
|
38
|
+
|
|
39
|
+
/** Execution preferences (cost strategy, approval, instructions). */
|
|
40
|
+
preferences: ProfilePreferences;
|
|
41
|
+
|
|
42
|
+
// Scaling
|
|
43
|
+
/** Minimum running instances (0 = scale to zero). */
|
|
44
|
+
minInstances: number;
|
|
45
|
+
maxInstances: number;
|
|
46
|
+
currentInstances: number;
|
|
47
|
+
|
|
48
|
+
// Metadata
|
|
49
|
+
createdAt: string;
|
|
50
|
+
updatedAt: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A running worker instance of a bot profile. */
|
|
54
|
+
export interface BotInstance {
|
|
55
|
+
/** Format: `${profileId}-${index}` */
|
|
56
|
+
instanceId: string;
|
|
57
|
+
profileId: string;
|
|
58
|
+
index: number;
|
|
59
|
+
status: 'idle' | 'executing' | 'paused' | 'stopped';
|
|
60
|
+
|
|
61
|
+
currentTaskId?: string;
|
|
62
|
+
currentRunId?: string;
|
|
63
|
+
startedAt?: string;
|
|
64
|
+
|
|
65
|
+
// Counters
|
|
66
|
+
tokensUsed: number;
|
|
67
|
+
cost: number;
|
|
68
|
+
tasksCompleted: number;
|
|
69
|
+
tasksFailed: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A recorded routing decision made by the orchestrator. */
|
|
73
|
+
export interface OrchestratorDecision {
|
|
74
|
+
/** Sequential decision ID. */
|
|
75
|
+
id: number;
|
|
76
|
+
timestamp: number;
|
|
77
|
+
taskId: string;
|
|
78
|
+
taskTitle: string;
|
|
79
|
+
assignedProfileId: string;
|
|
80
|
+
assignedInstanceId: string;
|
|
81
|
+
reason: string;
|
|
82
|
+
method: 'exact-match' | 'single-eligible' | 'ai-routed' | 'round-robin' | 'manual';
|
|
83
|
+
candidateProfiles: string[];
|
|
84
|
+
confidence?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Input to the orchestrator routing logic. */
|
|
88
|
+
export interface OrchestratorInput {
|
|
89
|
+
pendingTasks: Array<{
|
|
90
|
+
id: string;
|
|
91
|
+
title: string;
|
|
92
|
+
description: string;
|
|
93
|
+
priority: number;
|
|
94
|
+
complexity: 'trivial' | 'simple' | 'moderate' | 'complex';
|
|
95
|
+
assignedProfile?: string;
|
|
96
|
+
context: {
|
|
97
|
+
runSummaries: Array<{ outcome: string; botId: string }>;
|
|
98
|
+
};
|
|
99
|
+
}>;
|
|
100
|
+
profiles: BotProfile[];
|
|
101
|
+
instances: BotInstance[];
|
|
102
|
+
budgetRemaining: {
|
|
103
|
+
tokens: number;
|
|
104
|
+
cost: number;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Output from the orchestrator routing logic. */
|
|
109
|
+
export interface OrchestratorOutput {
|
|
110
|
+
assignments: Array<{
|
|
111
|
+
taskId: string;
|
|
112
|
+
profileId: string;
|
|
113
|
+
instanceId: string;
|
|
114
|
+
reason: string;
|
|
115
|
+
method: OrchestratorDecision['method'];
|
|
116
|
+
confidence?: number;
|
|
117
|
+
}>;
|
|
118
|
+
scaleActions: Array<{
|
|
119
|
+
profileId: string;
|
|
120
|
+
action: 'scale-up' | 'scale-down';
|
|
121
|
+
targetInstances: number;
|
|
122
|
+
reason: string;
|
|
123
|
+
}>;
|
|
124
|
+
skippedTasks: Array<{
|
|
125
|
+
taskId: string;
|
|
126
|
+
reason: string;
|
|
127
|
+
}>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Input for creating a new bot profile. */
|
|
131
|
+
export interface CreateProfileInput {
|
|
132
|
+
name: string;
|
|
133
|
+
description?: string;
|
|
134
|
+
icon?: string;
|
|
135
|
+
color?: string;
|
|
136
|
+
botId: string;
|
|
137
|
+
capabilities?: Capability[];
|
|
138
|
+
preferences?: Partial<ProfilePreferences>;
|
|
139
|
+
minInstances?: number;
|
|
140
|
+
maxInstances?: number;
|
|
141
|
+
}
|