@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.
Files changed (162) hide show
  1. package/dist/ai-chat-provider.d.ts +12 -0
  2. package/dist/ai-chat-provider.d.ts.map +1 -1
  3. package/dist/ai-chat-provider.js +173 -19
  4. package/dist/ai-chat-provider.js.map +1 -1
  5. package/dist/bot/agent-loop.d.ts +20 -0
  6. package/dist/bot/agent-loop.d.ts.map +1 -0
  7. package/dist/bot/agent-loop.js +331 -0
  8. package/dist/bot/agent-loop.js.map +1 -0
  9. package/dist/bot/ai-router.d.ts +19 -0
  10. package/dist/bot/ai-router.d.ts.map +1 -0
  11. package/dist/bot/ai-router.js +104 -0
  12. package/dist/bot/ai-router.js.map +1 -0
  13. package/dist/bot/bot-registry.js +2 -2
  14. package/dist/bot/bot-registry.js.map +1 -1
  15. package/dist/bot/conversation-store.d.ts +1 -0
  16. package/dist/bot/conversation-store.d.ts.map +1 -1
  17. package/dist/bot/conversation-store.js.map +1 -1
  18. package/dist/bot/improve-loop.js.map +1 -1
  19. package/dist/bot/instance-manager.d.ts +31 -0
  20. package/dist/bot/instance-manager.d.ts.map +1 -0
  21. package/dist/bot/instance-manager.js +115 -0
  22. package/dist/bot/instance-manager.js.map +1 -0
  23. package/dist/bot/orchestrator.d.ts +36 -0
  24. package/dist/bot/orchestrator.d.ts.map +1 -0
  25. package/dist/bot/orchestrator.js +176 -0
  26. package/dist/bot/orchestrator.js.map +1 -0
  27. package/dist/bot/profile-store.d.ts +36 -0
  28. package/dist/bot/profile-store.d.ts.map +1 -0
  29. package/dist/bot/profile-store.js +208 -0
  30. package/dist/bot/profile-store.js.map +1 -0
  31. package/dist/bot/profile-types.d.ts +126 -0
  32. package/dist/bot/profile-types.d.ts.map +1 -0
  33. package/dist/bot/profile-types.js +7 -0
  34. package/dist/bot/profile-types.js.map +1 -0
  35. package/dist/bot/session-state.d.ts +25 -0
  36. package/dist/bot/session-state.d.ts.map +1 -0
  37. package/dist/bot/session-state.js +110 -0
  38. package/dist/bot/session-state.js.map +1 -0
  39. package/dist/bot/swarm-controller.d.ts +37 -21
  40. package/dist/bot/swarm-controller.d.ts.map +1 -1
  41. package/dist/bot/swarm-controller.js +344 -163
  42. package/dist/bot/swarm-controller.js.map +1 -1
  43. package/dist/bot/task-prompt-builder.d.ts +2 -1
  44. package/dist/bot/task-prompt-builder.d.ts.map +1 -1
  45. package/dist/bot/task-prompt-builder.js +33 -10
  46. package/dist/bot/task-prompt-builder.js.map +1 -1
  47. package/dist/bot/task-queue.d.ts +46 -0
  48. package/dist/bot/task-queue.d.ts.map +1 -0
  49. package/dist/bot/task-queue.js +237 -0
  50. package/dist/bot/task-queue.js.map +1 -0
  51. package/dist/bot/task-store.d.ts +1 -6
  52. package/dist/bot/task-store.d.ts.map +1 -1
  53. package/dist/bot/task-store.js +27 -78
  54. package/dist/bot/task-store.js.map +1 -1
  55. package/dist/bot/task-types.d.ts +8 -4
  56. package/dist/bot/task-types.d.ts.map +1 -1
  57. package/dist/cli-handlers.d.ts.map +1 -1
  58. package/dist/cli-handlers.js +2 -3
  59. package/dist/cli-handlers.js.map +1 -1
  60. package/dist/cli.d.ts +3 -0
  61. package/dist/cli.d.ts.map +1 -0
  62. package/dist/cli.js +749 -0
  63. package/dist/cli.js.map +1 -0
  64. package/dist/docs/docs/weaver-bot-usage.md +35 -18
  65. package/dist/docs/docs/weaver-config.md +20 -0
  66. package/dist/docs/docs/weaver-task-queue.md +31 -19
  67. package/dist/docs/weaver-config.md +15 -9
  68. package/dist/mcp-tools.d.ts +17 -0
  69. package/dist/mcp-tools.d.ts.map +1 -1
  70. package/dist/mcp-tools.js +98 -232
  71. package/dist/mcp-tools.js.map +1 -1
  72. package/dist/node-types/orchestrator-dispatch.d.ts +17 -0
  73. package/dist/node-types/orchestrator-dispatch.d.ts.map +1 -0
  74. package/dist/node-types/orchestrator-dispatch.js +63 -0
  75. package/dist/node-types/orchestrator-dispatch.js.map +1 -0
  76. package/dist/node-types/orchestrator-load-state.d.ts +16 -0
  77. package/dist/node-types/orchestrator-load-state.d.ts.map +1 -0
  78. package/dist/node-types/orchestrator-load-state.js +60 -0
  79. package/dist/node-types/orchestrator-load-state.js.map +1 -0
  80. package/dist/node-types/orchestrator-route.d.ts +16 -0
  81. package/dist/node-types/orchestrator-route.d.ts.map +1 -0
  82. package/dist/node-types/orchestrator-route.js +28 -0
  83. package/dist/node-types/orchestrator-route.js.map +1 -0
  84. package/dist/node-types/receive-task.d.ts +2 -3
  85. package/dist/node-types/receive-task.d.ts.map +1 -1
  86. package/dist/node-types/receive-task.js +3 -28
  87. package/dist/node-types/receive-task.js.map +1 -1
  88. package/dist/templates/weaver-template.d.ts +11 -0
  89. package/dist/templates/weaver-template.d.ts.map +1 -0
  90. package/dist/templates/weaver-template.js +53 -0
  91. package/dist/templates/weaver-template.js.map +1 -0
  92. package/dist/ui/bot-constants.d.ts +14 -0
  93. package/dist/ui/bot-constants.d.ts.map +1 -0
  94. package/dist/ui/bot-constants.js +189 -0
  95. package/dist/ui/bot-constants.js.map +1 -0
  96. package/dist/ui/bot-panel.js +51 -90
  97. package/dist/ui/bot-slot-card.js +87 -122
  98. package/dist/ui/budget-bar.js +5 -3
  99. package/dist/ui/chat-task-result.js +4 -7
  100. package/dist/ui/decision-log.js +136 -0
  101. package/dist/ui/profile-card.js +158 -0
  102. package/dist/ui/profile-editor.js +597 -0
  103. package/dist/ui/swarm-controls.js +36 -27
  104. package/dist/ui/swarm-dashboard.js +2034 -736
  105. package/dist/ui/task-create-form.js +39 -116
  106. package/dist/ui/task-detail-view.js +490 -239
  107. package/dist/ui/task-pool-list.js +69 -94
  108. package/dist/workflows/orchestrator.d.ts +21 -0
  109. package/dist/workflows/orchestrator.d.ts.map +1 -0
  110. package/dist/workflows/orchestrator.js +281 -0
  111. package/dist/workflows/orchestrator.js.map +1 -0
  112. package/dist/workflows/weaver-bot-session.d.ts +65 -0
  113. package/dist/workflows/weaver-bot-session.d.ts.map +1 -0
  114. package/dist/workflows/weaver-bot-session.js +68 -0
  115. package/dist/workflows/weaver-bot-session.js.map +1 -0
  116. package/dist/workflows/weaver.d.ts +24 -0
  117. package/dist/workflows/weaver.d.ts.map +1 -0
  118. package/dist/workflows/weaver.js +28 -0
  119. package/dist/workflows/weaver.js.map +1 -0
  120. package/flowweaver.manifest.json +253 -66
  121. package/package.json +1 -1
  122. package/src/ai-chat-provider.ts +184 -18
  123. package/src/bot/ai-router.ts +132 -0
  124. package/src/bot/bot-registry.ts +2 -2
  125. package/src/bot/conversation-store.ts +2 -1
  126. package/src/bot/improve-loop.ts +6 -6
  127. package/src/bot/instance-manager.ts +128 -0
  128. package/src/bot/orchestrator.ts +244 -0
  129. package/src/bot/profile-store.ts +225 -0
  130. package/src/bot/profile-types.ts +141 -0
  131. package/src/bot/swarm-controller.ts +385 -186
  132. package/src/bot/task-prompt-builder.ts +37 -6
  133. package/src/bot/task-store.ts +28 -89
  134. package/src/bot/task-types.ts +10 -4
  135. package/src/cli-handlers.ts +2 -3
  136. package/src/docs/weaver-bot-usage.md +35 -18
  137. package/src/docs/weaver-config.md +20 -0
  138. package/src/docs/weaver-task-queue.md +31 -19
  139. package/src/mcp-tools.ts +129 -320
  140. package/src/node-types/orchestrator-dispatch.ts +71 -0
  141. package/src/node-types/orchestrator-load-state.ts +66 -0
  142. package/src/node-types/orchestrator-route.ts +33 -0
  143. package/src/node-types/receive-task.ts +3 -26
  144. package/src/ui/bot-constants.ts +192 -0
  145. package/src/ui/bot-panel.tsx +55 -79
  146. package/src/ui/bot-slot-card.tsx +69 -117
  147. package/src/ui/budget-bar.tsx +5 -3
  148. package/src/ui/chat-task-result.tsx +6 -9
  149. package/src/ui/decision-log.tsx +148 -0
  150. package/src/ui/profile-card.tsx +157 -0
  151. package/src/ui/profile-editor.tsx +384 -0
  152. package/src/ui/swarm-controls.tsx +35 -31
  153. package/src/ui/swarm-dashboard.tsx +409 -80
  154. package/src/ui/task-create-form.tsx +29 -119
  155. package/src/ui/task-detail-view.tsx +461 -215
  156. package/src/ui/task-pool-list.tsx +74 -95
  157. package/src/workflows/orchestrator.ts +302 -0
  158. package/dist/docs/weaver-bot-usage.md +0 -34
  159. package/dist/docs/weaver-genesis.md +0 -32
  160. package/dist/docs/weaver-task-queue.md +0 -34
  161. package/src/bot/error-guide.ts +0 -4
  162. 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
+ }