@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.
Files changed (217) 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 +351 -335
  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/assistant-tools.d.ts.map +1 -1
  14. package/dist/bot/assistant-tools.js +49 -33
  15. package/dist/bot/assistant-tools.js.map +1 -1
  16. package/dist/bot/async-mutex.d.ts +13 -0
  17. package/dist/bot/async-mutex.d.ts.map +1 -0
  18. package/dist/bot/async-mutex.js +37 -0
  19. package/dist/bot/async-mutex.js.map +1 -0
  20. package/dist/bot/bot-manager.d.ts +2 -2
  21. package/dist/bot/bot-manager.d.ts.map +1 -1
  22. package/dist/bot/bot-manager.js +3 -3
  23. package/dist/bot/bot-manager.js.map +1 -1
  24. package/dist/bot/bot-registry.js +2 -2
  25. package/dist/bot/bot-registry.js.map +1 -1
  26. package/dist/bot/conversation-store.d.ts +1 -0
  27. package/dist/bot/conversation-store.d.ts.map +1 -1
  28. package/dist/bot/conversation-store.js.map +1 -1
  29. package/dist/bot/dashboard.d.ts.map +1 -1
  30. package/dist/bot/dashboard.js +17 -8
  31. package/dist/bot/dashboard.js.map +1 -1
  32. package/dist/bot/improve-loop.js.map +1 -1
  33. package/dist/bot/index.d.ts +2 -4
  34. package/dist/bot/index.d.ts.map +1 -1
  35. package/dist/bot/index.js +1 -2
  36. package/dist/bot/index.js.map +1 -1
  37. package/dist/bot/instance-manager.d.ts +31 -0
  38. package/dist/bot/instance-manager.d.ts.map +1 -0
  39. package/dist/bot/instance-manager.js +115 -0
  40. package/dist/bot/instance-manager.js.map +1 -0
  41. package/dist/bot/orchestrator.d.ts +36 -0
  42. package/dist/bot/orchestrator.d.ts.map +1 -0
  43. package/dist/bot/orchestrator.js +176 -0
  44. package/dist/bot/orchestrator.js.map +1 -0
  45. package/dist/bot/profile-store.d.ts +36 -0
  46. package/dist/bot/profile-store.d.ts.map +1 -0
  47. package/dist/bot/profile-store.js +208 -0
  48. package/dist/bot/profile-store.js.map +1 -0
  49. package/dist/bot/profile-types.d.ts +126 -0
  50. package/dist/bot/profile-types.d.ts.map +1 -0
  51. package/dist/bot/profile-types.js +7 -0
  52. package/dist/bot/profile-types.js.map +1 -0
  53. package/dist/bot/run-store.d.ts.map +1 -1
  54. package/dist/bot/run-store.js +8 -0
  55. package/dist/bot/run-store.js.map +1 -1
  56. package/dist/bot/runner.d.ts +4 -0
  57. package/dist/bot/runner.d.ts.map +1 -1
  58. package/dist/bot/runner.js +5 -1
  59. package/dist/bot/runner.js.map +1 -1
  60. package/dist/bot/swarm-controller.d.ts +109 -0
  61. package/dist/bot/swarm-controller.d.ts.map +1 -0
  62. package/dist/bot/swarm-controller.js +640 -0
  63. package/dist/bot/swarm-controller.js.map +1 -0
  64. package/dist/bot/swarm-event-log.d.ts +28 -0
  65. package/dist/bot/swarm-event-log.d.ts.map +1 -0
  66. package/dist/bot/swarm-event-log.js +54 -0
  67. package/dist/bot/swarm-event-log.js.map +1 -0
  68. package/dist/bot/task-prompt-builder.d.ts +22 -0
  69. package/dist/bot/task-prompt-builder.d.ts.map +1 -0
  70. package/dist/bot/task-prompt-builder.js +240 -0
  71. package/dist/bot/task-prompt-builder.js.map +1 -0
  72. package/dist/bot/task-store.d.ts +21 -0
  73. package/dist/bot/task-store.d.ts.map +1 -0
  74. package/dist/bot/task-store.js +364 -0
  75. package/dist/bot/task-store.js.map +1 -0
  76. package/dist/bot/task-types.d.ts +79 -0
  77. package/dist/bot/task-types.d.ts.map +1 -0
  78. package/dist/bot/task-types.js +6 -0
  79. package/dist/bot/task-types.js.map +1 -0
  80. package/dist/bot/types.d.ts +8 -0
  81. package/dist/bot/types.d.ts.map +1 -1
  82. package/dist/cli-handlers.d.ts.map +1 -1
  83. package/dist/cli-handlers.js +79 -54
  84. package/dist/cli-handlers.js.map +1 -1
  85. package/dist/cli.d.ts +3 -0
  86. package/dist/cli.d.ts.map +1 -0
  87. package/dist/cli.js +749 -0
  88. package/dist/cli.js.map +1 -0
  89. package/dist/docs/docs/weaver-bot-usage.md +35 -18
  90. package/dist/docs/docs/weaver-config.md +20 -0
  91. package/dist/docs/docs/weaver-task-queue.md +31 -19
  92. package/dist/docs/weaver-config.md +15 -9
  93. package/dist/index.d.ts +2 -2
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +1 -1
  96. package/dist/index.js.map +1 -1
  97. package/dist/mcp-tools.d.ts +17 -0
  98. package/dist/mcp-tools.d.ts.map +1 -1
  99. package/dist/mcp-tools.js +98 -279
  100. package/dist/mcp-tools.js.map +1 -1
  101. package/dist/node-types/bot-report.d.ts.map +1 -1
  102. package/dist/node-types/bot-report.js +6 -24
  103. package/dist/node-types/bot-report.js.map +1 -1
  104. package/dist/node-types/orchestrator-dispatch.d.ts +17 -0
  105. package/dist/node-types/orchestrator-dispatch.d.ts.map +1 -0
  106. package/dist/node-types/orchestrator-dispatch.js +63 -0
  107. package/dist/node-types/orchestrator-dispatch.js.map +1 -0
  108. package/dist/node-types/orchestrator-load-state.d.ts +16 -0
  109. package/dist/node-types/orchestrator-load-state.d.ts.map +1 -0
  110. package/dist/node-types/orchestrator-load-state.js +60 -0
  111. package/dist/node-types/orchestrator-load-state.js.map +1 -0
  112. package/dist/node-types/orchestrator-route.d.ts +16 -0
  113. package/dist/node-types/orchestrator-route.d.ts.map +1 -0
  114. package/dist/node-types/orchestrator-route.js +28 -0
  115. package/dist/node-types/orchestrator-route.js.map +1 -0
  116. package/dist/node-types/receive-task.d.ts +2 -3
  117. package/dist/node-types/receive-task.d.ts.map +1 -1
  118. package/dist/node-types/receive-task.js +3 -48
  119. package/dist/node-types/receive-task.js.map +1 -1
  120. package/dist/templates/weaver-template.d.ts +11 -0
  121. package/dist/templates/weaver-template.d.ts.map +1 -0
  122. package/dist/templates/weaver-template.js +53 -0
  123. package/dist/templates/weaver-template.js.map +1 -0
  124. package/dist/ui/bot-activity.js +2 -2
  125. package/dist/ui/bot-constants.d.ts +14 -0
  126. package/dist/ui/bot-constants.d.ts.map +1 -0
  127. package/dist/ui/bot-constants.js +189 -0
  128. package/dist/ui/bot-constants.js.map +1 -0
  129. package/dist/ui/bot-panel.js +207 -245
  130. package/dist/ui/bot-slot-card.js +141 -0
  131. package/dist/ui/budget-bar.js +59 -0
  132. package/dist/ui/chat-task-result.js +178 -0
  133. package/dist/ui/decision-log.js +136 -0
  134. package/dist/ui/profile-card.js +158 -0
  135. package/dist/ui/profile-editor.js +597 -0
  136. package/dist/ui/swarm-controls.js +245 -0
  137. package/dist/ui/swarm-dashboard.js +3012 -0
  138. package/dist/ui/task-create-form.js +98 -0
  139. package/dist/ui/task-detail-view.js +1044 -0
  140. package/dist/ui/task-pool-list.js +156 -0
  141. package/dist/workflows/orchestrator.d.ts +21 -0
  142. package/dist/workflows/orchestrator.d.ts.map +1 -0
  143. package/dist/workflows/orchestrator.js +281 -0
  144. package/dist/workflows/orchestrator.js.map +1 -0
  145. package/dist/workflows/weaver-bot-session.d.ts +65 -0
  146. package/dist/workflows/weaver-bot-session.d.ts.map +1 -0
  147. package/dist/workflows/weaver-bot-session.js +68 -0
  148. package/dist/workflows/weaver-bot-session.js.map +1 -0
  149. package/dist/workflows/weaver.d.ts +24 -0
  150. package/dist/workflows/weaver.d.ts.map +1 -0
  151. package/dist/workflows/weaver.js +28 -0
  152. package/dist/workflows/weaver.js.map +1 -0
  153. package/flowweaver.manifest.json +547 -133
  154. package/package.json +1 -1
  155. package/src/ai-chat-provider.ts +378 -371
  156. package/src/bot/ai-router.ts +132 -0
  157. package/src/bot/assistant-tools.ts +47 -29
  158. package/src/bot/async-mutex.ts +37 -0
  159. package/src/bot/bot-manager.ts +3 -3
  160. package/src/bot/bot-registry.ts +2 -2
  161. package/src/bot/conversation-store.ts +2 -1
  162. package/src/bot/dashboard.ts +17 -8
  163. package/src/bot/improve-loop.ts +6 -6
  164. package/src/bot/index.ts +2 -4
  165. package/src/bot/instance-manager.ts +128 -0
  166. package/src/bot/orchestrator.ts +244 -0
  167. package/src/bot/profile-store.ts +225 -0
  168. package/src/bot/profile-types.ts +141 -0
  169. package/src/bot/run-store.ts +8 -0
  170. package/src/bot/runner.ts +9 -1
  171. package/src/bot/swarm-controller.ts +780 -0
  172. package/src/bot/swarm-event-log.ts +57 -0
  173. package/src/bot/task-prompt-builder.ts +309 -0
  174. package/src/bot/task-store.ts +407 -0
  175. package/src/bot/task-types.ts +100 -0
  176. package/src/bot/types.ts +8 -0
  177. package/src/cli-handlers.ts +78 -53
  178. package/src/docs/weaver-bot-usage.md +35 -18
  179. package/src/docs/weaver-config.md +20 -0
  180. package/src/docs/weaver-task-queue.md +31 -19
  181. package/src/index.ts +5 -4
  182. package/src/mcp-tools.ts +129 -372
  183. package/src/node-types/bot-report.ts +6 -24
  184. package/src/node-types/orchestrator-dispatch.ts +71 -0
  185. package/src/node-types/orchestrator-load-state.ts +66 -0
  186. package/src/node-types/orchestrator-route.ts +33 -0
  187. package/src/node-types/receive-task.ts +3 -57
  188. package/src/ui/bot-activity.tsx +2 -2
  189. package/src/ui/bot-constants.ts +192 -0
  190. package/src/ui/bot-panel.tsx +213 -247
  191. package/src/ui/bot-slot-card.tsx +139 -0
  192. package/src/ui/budget-bar.tsx +30 -0
  193. package/src/ui/chat-task-result.tsx +236 -0
  194. package/src/ui/decision-log.tsx +148 -0
  195. package/src/ui/profile-card.tsx +157 -0
  196. package/src/ui/profile-editor.tsx +384 -0
  197. package/src/ui/swarm-controls.tsx +260 -0
  198. package/src/ui/swarm-dashboard.tsx +647 -0
  199. package/src/ui/task-create-form.tsx +87 -0
  200. package/src/ui/task-detail-view.tsx +841 -0
  201. package/src/ui/task-pool-list.tsx +187 -0
  202. package/src/workflows/orchestrator.ts +302 -0
  203. package/dist/docs/weaver-bot-usage.md +0 -34
  204. package/dist/docs/weaver-genesis.md +0 -32
  205. package/dist/docs/weaver-task-queue.md +0 -34
  206. package/dist/ui/bot-workspace.js +0 -1015
  207. package/dist/ui/chat-bot-result.js +0 -71
  208. package/dist/ui/queue-input.js +0 -82
  209. package/dist/ui/session-bar.js +0 -174
  210. package/src/bot/error-guide.ts +0 -4
  211. package/src/bot/retry-utils.ts +0 -4
  212. package/src/bot/session-state.ts +0 -116
  213. package/src/bot/task-queue.ts +0 -262
  214. package/src/ui/bot-workspace.tsx +0 -442
  215. package/src/ui/chat-bot-result.tsx +0 -81
  216. package/src/ui/queue-input.tsx +0 -56
  217. package/src/ui/session-bar.tsx +0 -157
@@ -0,0 +1,132 @@
1
+ /**
2
+ * AIRouterImpl — LLM-powered routing for ambiguous profile selection.
3
+ *
4
+ * When multiple bot profiles match a task, the orchestrator delegates
5
+ * to this router to pick the best one.
6
+ * Uses the project's configured AI provider via the callAI infrastructure.
7
+ */
8
+
9
+ import type { AIRouter, AIRouterResult } from './orchestrator.js';
10
+ import type { BotProfile, OrchestratorInput } from './profile-types.js';
11
+ import type { ProviderInfo } from './types.js';
12
+ import { callAI, parseJsonResponse } from './ai-client.js';
13
+ import { resolveProviderConfig, detectProvider } from './agent-provider.js';
14
+
15
+ type Task = OrchestratorInput['pendingTasks'][number];
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Provider resolution — map BotProviderConfig.name to ProviderInfo.type
19
+ // ---------------------------------------------------------------------------
20
+
21
+ const NAME_TO_TYPE: Record<string, ProviderInfo['type']> = {
22
+ anthropic: 'anthropic',
23
+ 'claude-cli': 'claude-cli',
24
+ 'copilot-cli': 'copilot-cli',
25
+ platform: 'platform',
26
+ };
27
+
28
+ function resolveProviderInfo(projectDir: string): Pick<ProviderInfo, 'type' | 'apiKey' | 'model' | 'maxTokens'> {
29
+ // Try reading .weaver.json from the project directory
30
+ let providerInput: string | { name: string; model?: string; maxTokens?: number } = 'auto';
31
+ try {
32
+ const fs = require('node:fs');
33
+ const path = require('node:path');
34
+ const configPath = path.join(projectDir, '.weaver.json');
35
+ if (fs.existsSync(configPath)) {
36
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
37
+ if (config.provider) providerInput = config.provider;
38
+ }
39
+ } catch { /* use auto detection */ }
40
+
41
+ const resolved = resolveProviderConfig(providerInput);
42
+ const type = NAME_TO_TYPE[resolved.name] ?? 'claude-cli';
43
+
44
+ return {
45
+ type,
46
+ model: resolved.model,
47
+ maxTokens: resolved.maxTokens,
48
+ apiKey: type === 'anthropic' ? process.env.ANTHROPIC_API_KEY : undefined,
49
+ };
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // AIRouterImpl
54
+ // ---------------------------------------------------------------------------
55
+
56
+ const SYSTEM_PROMPT =
57
+ 'You are a task routing orchestrator. Given a task and candidate bot profiles, ' +
58
+ 'select the best profile to handle the task. Respond with ONLY valid JSON: ' +
59
+ '{ "profileId": "...", "reason": "...", "confidence": 0.0-1.0 }';
60
+
61
+ export class AIRouterImpl implements AIRouter {
62
+ private projectDir: string;
63
+
64
+ constructor(projectDir: string) {
65
+ this.projectDir = projectDir;
66
+ }
67
+
68
+ async route(task: Task, candidates: BotProfile[]): Promise<AIRouterResult> {
69
+ const prompt = this._buildPrompt(task, candidates);
70
+ const providerInfo = resolveProviderInfo(this.projectDir);
71
+
72
+ // Use a low max-tokens since this is just a routing decision
73
+ const response = await callAI(providerInfo, SYSTEM_PROMPT, prompt, 256);
74
+
75
+ const result = parseJsonResponse(response) as {
76
+ profileId?: string;
77
+ reason?: string;
78
+ confidence?: number;
79
+ };
80
+
81
+ if (!result.profileId) {
82
+ throw new Error('AI router returned no profileId');
83
+ }
84
+
85
+ return {
86
+ profileId: result.profileId,
87
+ reason: result.reason || 'AI-selected',
88
+ confidence: typeof result.confidence === 'number' ? result.confidence : 0.5,
89
+ };
90
+ }
91
+
92
+ /** @internal — exposed for testing */
93
+ _buildPrompt(task: Task, candidates: BotProfile[]): string {
94
+ const lines = [
95
+ `Task: "${task.title}"`,
96
+ task.description ? `Description: ${task.description}` : '',
97
+ task.complexity ? `Complexity: ${task.complexity}` : '',
98
+ '',
99
+ 'Candidate profiles:',
100
+ ...candidates.map((p) => {
101
+ const parts = [`- ${p.id} (${p.name}):`];
102
+ if (p.capabilities.length > 0) {
103
+ parts.push(' Capabilities:');
104
+ for (const cap of p.capabilities) {
105
+ parts.push(` - ${cap.name}: ${cap.description}`);
106
+ }
107
+ }
108
+ parts.push(` Cost strategy: ${p.preferences.costStrategy}`);
109
+ if (p.preferences.instructions) {
110
+ parts.push(` Instructions: ${p.preferences.instructions}`);
111
+ }
112
+ parts.push(` maxInstances=${p.maxInstances}`);
113
+ return parts.join('\n');
114
+ }),
115
+ ];
116
+
117
+ // Add previous attempt info if available
118
+ const failures = task.context.runSummaries.filter((r) => r.outcome !== 'success');
119
+ if (failures.length > 0) {
120
+ lines.push(
121
+ '',
122
+ `Previous attempts: ${task.context.runSummaries.length} (${failures.length} failed)`,
123
+ );
124
+ for (const f of failures.slice(-3)) {
125
+ lines.push(` - ${f.botId}: ${f.outcome}`);
126
+ }
127
+ }
128
+
129
+ lines.push('', 'Select the best profile. Respond with JSON only.');
130
+ return lines.filter(Boolean).join('\n');
131
+ }
132
+ }
@@ -53,19 +53,19 @@ export function createAssistantExecutor(projectDir: string, steeringEngine?: imp
53
53
  const botName = String(args.name);
54
54
  const bot = mgr.get(botName);
55
55
  if (!bot) return { result: `Bot "${botName}" not found.`, isError: true };
56
- const queue = mgr.getQueue(botName);
57
- const tasks = await queue.list();
56
+ const store = mgr.getTaskStore(botName);
57
+ const tasks = await store.list();
58
58
  const pending = tasks.filter(t => t.status === 'pending').length;
59
- const running = tasks.filter(t => t.status === 'running').length;
60
- const completed = tasks.filter(t => t.status === 'completed').length;
59
+ const inProgress = tasks.filter(t => t.status === 'in-progress').length;
60
+ const done = tasks.filter(t => t.status === 'done').length;
61
61
  const failed = tasks.filter(t => t.status === 'failed').length;
62
62
  const failedTasks = tasks.filter(t => t.status === 'failed');
63
63
  let result = `Bot "${botName}": ${bot.status}\n`;
64
- result += `Tasks: ${completed} completed, ${failed} failed, ${running} running, ${pending} pending\n`;
64
+ result += `Tasks: ${done} done, ${failed} failed, ${inProgress} in-progress, ${pending} pending\n`;
65
65
  if (failedTasks.length > 0) {
66
66
  result += `\nFailed tasks:\n`;
67
67
  for (const t of failedTasks) {
68
- result += ` - ${t.instruction.slice(0, 120)}\n`;
68
+ result += ` - ${t.title.slice(0, 120)}\n`;
69
69
  }
70
70
  }
71
71
  return { result, isError: false };
@@ -89,36 +89,45 @@ export function createAssistantExecutor(projectDir: string, steeringEngine?: imp
89
89
 
90
90
  // Queue management
91
91
  case 'queue_add': {
92
- const queue = mgr.getQueue(String(args.bot));
93
- const { id, duplicate } = await queue.add({
94
- instruction: String(args.instruction),
95
- targets: args.targets as string[] | undefined,
92
+ const store = mgr.getTaskStore(String(args.bot));
93
+ const task = await store.create({
94
+ title: String(args.instruction),
95
+ description: String(args.instruction),
96
96
  priority: 0,
97
+ createdBy: 'ai',
97
98
  });
98
- if (duplicate) return { result: `Skipped: similar task already exists (${id}).`, isError: false };
99
- return { result: `Added task ${id} to "${args.bot}" queue.`, isError: false };
99
+ return { result: `Added task ${task.id} to "${args.bot}" queue.`, isError: false };
100
100
  }
101
101
  case 'queue_add_batch': {
102
- const queue = mgr.getQueue(String(args.bot));
102
+ const store = mgr.getTaskStore(String(args.bot));
103
103
  const tasks = args.tasks as Array<{ instruction: string; targets?: string[] }>;
104
- let added = 0, skipped = 0;
104
+ let added = 0;
105
105
  for (const t of tasks) {
106
- const { duplicate } = await queue.add({ instruction: t.instruction, targets: t.targets, priority: 0 });
107
- if (duplicate) skipped++; else added++;
106
+ await store.create({
107
+ title: t.instruction,
108
+ description: t.instruction,
109
+ priority: 0,
110
+ createdBy: 'ai',
111
+ });
112
+ added++;
108
113
  }
109
- const msg = skipped > 0 ? `Added ${added} tasks, ${skipped} duplicates skipped.` : `Added ${added} tasks to "${args.bot}" queue.`;
110
- return { result: msg, isError: false };
114
+ return { result: `Added ${added} tasks to "${args.bot}" queue.`, isError: false };
111
115
  }
112
116
  case 'queue_list': {
113
- const queue = mgr.getQueue(String(args.bot));
114
- const tasks = await queue.list();
117
+ const store = mgr.getTaskStore(String(args.bot));
118
+ const tasks = await store.list();
115
119
  if (tasks.length === 0) return { result: 'Queue is empty.', isError: false };
116
- const lines = tasks.map(t => `[${t.status}] ${t.instruction.slice(0, 120)}`);
120
+ const lines = tasks.map(t => `[${t.status}] ${t.title.slice(0, 120)}`);
117
121
  return { result: lines.join('\n'), isError: false };
118
122
  }
119
123
  case 'queue_retry': {
120
- const queue = mgr.getQueue(String(args.bot));
121
- const count = await queue.retryAll();
124
+ const store = mgr.getTaskStore(String(args.bot));
125
+ const tasks = await store.list({ status: 'failed' });
126
+ let count = 0;
127
+ for (const t of tasks) {
128
+ await store.update(t.id, { status: 'pending' });
129
+ count++;
130
+ }
122
131
  return { result: `Reset ${count} failed task(s) to pending.`, isError: false };
123
132
  }
124
133
 
@@ -322,12 +331,21 @@ export function createAssistantExecutor(projectDir: string, steeringEngine?: imp
322
331
  const projectsDir = path.join(os.homedir(), '.weaver', 'projects');
323
332
  if (!fs.existsSync(projectsDir)) return { result: 'No projects found.', isError: false };
324
333
  const dirs = fs.readdirSync(projectsDir);
325
- // Each dir is a hashtry to find meta or queue files
326
- const projects = dirs.map(d => {
327
- const queuePath = path.join(projectsDir, d, 'task-queue.ndjson');
328
- const exists = fs.existsSync(queuePath);
329
- return `${d}: ${exists ? 'has queue' : 'empty'}`;
330
- });
334
+ // Each dir is a projectcheck for tasks via TaskStore
335
+ const projects = await Promise.all(dirs.map(async d => {
336
+ const projPath = path.join(projectsDir, d);
337
+ try {
338
+ const { TaskStore } = await import('./task-store.js');
339
+ const store = new TaskStore(projPath);
340
+ const tasks = await store.list();
341
+ const pending = tasks.filter(t => t.status === 'pending').length;
342
+ const inProgress = tasks.filter(t => t.status === 'in-progress').length;
343
+ if (tasks.length === 0) return `${d}: empty`;
344
+ return `${d}: ${tasks.length} tasks (${pending} pending, ${inProgress} in-progress)`;
345
+ } catch {
346
+ return `${d}: empty`;
347
+ }
348
+ }));
331
349
  return { result: projects.join('\n') || 'No projects found.', isError: false };
332
350
  }
333
351
 
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Simple async mutex for serializing access to shared resources
3
+ * within the same Node.js process. Used by TaskStore to prevent
4
+ * concurrent bot loops from corrupting task state.
5
+ */
6
+ export class AsyncMutex {
7
+ private _queue: Array<() => void> = [];
8
+ private _locked = false;
9
+
10
+ async runExclusive<T>(fn: () => Promise<T>): Promise<T> {
11
+ await this._acquire();
12
+ try {
13
+ return await fn();
14
+ } finally {
15
+ this._release();
16
+ }
17
+ }
18
+
19
+ private _acquire(): Promise<void> {
20
+ if (!this._locked) {
21
+ this._locked = true;
22
+ return Promise.resolve();
23
+ }
24
+ return new Promise<void>((resolve) => {
25
+ this._queue.push(resolve);
26
+ });
27
+ }
28
+
29
+ private _release(): void {
30
+ const next = this._queue.shift();
31
+ if (next) {
32
+ next();
33
+ } else {
34
+ this._locked = false;
35
+ }
36
+ }
37
+ }
@@ -8,7 +8,7 @@ import { spawn, execFileSync, type ChildProcess } from 'node:child_process';
8
8
  import * as fs from 'node:fs';
9
9
  import * as path from 'node:path';
10
10
  import * as os from 'node:os';
11
- import { TaskQueue } from './task-queue.js';
11
+ import { TaskStore } from './task-store.js';
12
12
  import { SteeringController } from './steering.js';
13
13
 
14
14
  export interface ManagedBot {
@@ -172,10 +172,10 @@ export class BotManager {
172
172
  try { process.kill(bot.pid, 0); return true; } catch { return false; }
173
173
  }
174
174
 
175
- getQueue(name: string): TaskQueue {
175
+ getTaskStore(name: string): TaskStore {
176
176
  const bot = this.resolve(name);
177
177
  if (!bot) throw new Error(`Bot "${name}" not found.`);
178
- return new TaskQueue(bot.meta.botDir);
178
+ return new TaskStore(bot.meta.botDir);
179
179
  }
180
180
 
181
181
  getSteering(name: string): SteeringController {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Bot registry — manages .fw/bots.json in the user's workspace.
2
+ * Bot registry — manages .weaver/bots.json in the user's workspace.
3
3
  * Reads, writes, validates bot registrations.
4
4
  */
5
5
  import * as fs from 'node:fs';
@@ -19,7 +19,7 @@ export interface BotRegistration {
19
19
  ejected?: boolean;
20
20
  }
21
21
 
22
- const BOTS_FILE = '.fw/bots.json';
22
+ const BOTS_FILE = '.weaver/bots.json';
23
23
 
24
24
  export class BotRegistry {
25
25
  private _cache: BotRegistration[] | null = null;
@@ -26,6 +26,7 @@ export interface ConversationRecord {
26
26
  createdAt: number;
27
27
  lastMessageAt: number;
28
28
  botIds: string[];
29
+ cloudConversationId?: string;
29
30
  }
30
31
 
31
32
  interface StoredMessage {
@@ -221,7 +222,7 @@ export class ConversationStore {
221
222
  },
222
223
  body: JSON.stringify({
223
224
  message: `[Synced from CLI] ${message}`,
224
- conversationId: (conversation as any).cloudConversationId,
225
+ conversationId: conversation.cloudConversationId,
225
226
  }),
226
227
  }).catch(() => {}); // fire-and-forget
227
228
  } catch { /* sync not available */ }
@@ -10,8 +10,7 @@ import type {
10
10
  } from './types.js';
11
11
  import type { ApprovalResult } from './approvals.js';
12
12
  import { SteeringController } from './steering.js';
13
- import { SessionStore } from './session-state.js';
14
- import { TaskQueue } from './task-queue.js';
13
+ import { TaskStore } from './task-store.js';
15
14
 
16
15
  interface RunState {
17
16
  workflowFile: string;
@@ -321,15 +320,25 @@ export class DashboardServer {
321
320
  }
322
321
 
323
322
  private handleBotStatus(res: http.ServerResponse): void {
324
- const store = new SessionStore();
325
- const session = store.load();
326
- res.writeHead(200, { 'Content-Type': 'application/json' });
327
- res.end(JSON.stringify(session ?? { status: 'idle', currentTask: null, completedTasks: 0 }));
323
+ const projectDir = process.env.WEAVER_PROJECT_DIR ?? process.cwd();
324
+ const store = new TaskStore(projectDir);
325
+ store.list().then(tasks => {
326
+ const pending = tasks.filter(t => t.status === 'pending').length;
327
+ const inProgress = tasks.filter(t => t.status === 'in-progress').length;
328
+ const done = tasks.filter(t => t.status === 'done').length;
329
+ const failed = tasks.filter(t => t.status === 'failed').length;
330
+ res.writeHead(200, { 'Content-Type': 'application/json' });
331
+ res.end(JSON.stringify({ status: inProgress > 0 ? 'executing' : 'idle', pending, inProgress, done, failed }));
332
+ }).catch(() => {
333
+ res.writeHead(200, { 'Content-Type': 'application/json' });
334
+ res.end(JSON.stringify({ status: 'idle', pending: 0, inProgress: 0, done: 0, failed: 0 }));
335
+ });
328
336
  }
329
337
 
330
338
  private async handleBotQueue(res: http.ServerResponse): Promise<void> {
331
- const queue = new TaskQueue();
332
- const tasks = await queue.list();
339
+ const projectDir = process.env.WEAVER_PROJECT_DIR ?? process.cwd();
340
+ const store = new TaskStore(projectDir);
341
+ const tasks = await store.list();
333
342
  res.writeHead(200, { 'Content-Type': 'application/json' });
334
343
  res.end(JSON.stringify(tasks));
335
344
  }
@@ -526,9 +526,9 @@ function emptyResult(startedAt: string, branch: string, worktreePath: string, re
526
526
 
527
527
  // --- Helpers ---
528
528
 
529
- let cachedProvider: unknown = null;
530
- let cachedTools: unknown[] = [];
531
- let cachedExecutor: unknown = null;
529
+ let cachedProvider: import('@synergenius/flow-weaver/agent').AgentProvider | null = null;
530
+ let cachedTools: import('@synergenius/flow-weaver/agent').ToolDefinition[] = [];
531
+ let cachedExecutor: import('@synergenius/flow-weaver/agent').ToolExecutor | null = null;
532
532
 
533
533
  async function runAssistantInDir(worktreeDir: string, message: string, _conversationId: string, steeringEngine?: import('./steering-engine.js').SteeringEngine): Promise<string> {
534
534
  const originalCwd = process.cwd();
@@ -562,9 +562,9 @@ async function runAssistantInDir(worktreeDir: string, message: string, _conversa
562
562
  const toolCalls: Array<{ name: string; isError: boolean }> = [];
563
563
 
564
564
  const result = await runAgentLoop(
565
- cachedProvider as any,
566
- cachedTools as any,
567
- cachedExecutor as any,
565
+ cachedProvider!,
566
+ cachedTools,
567
+ cachedExecutor!,
568
568
  [{ role: 'user' as const, content: message }],
569
569
  {
570
570
  maxIterations: 20,
package/src/bot/index.ts CHANGED
@@ -128,10 +128,8 @@ export { validateFiles } from './file-validator.js';
128
128
  // Bot infrastructure
129
129
  export { SteeringController } from './steering.js';
130
130
  export type { SteeringCommand } from './steering.js';
131
- export { TaskQueue } from './task-queue.js';
132
- export type { QueuedTask } from './task-queue.js';
133
- export { SessionStore } from './session-state.js';
134
- export type { SessionState } from './session-state.js';
131
+ export { TaskStore } from './task-store.js';
132
+ export type { Task, TaskFilter, CreateTaskInput, TaskStatus } from './task-types.js';
135
133
  export { buildBotSystemPrompt } from './system-prompt.js';
136
134
 
137
135
  // Device bridge handlers
@@ -0,0 +1,128 @@
1
+ /**
2
+ * InstanceManager — manages in-memory bot instances (workers) per profile.
3
+ * Instances represent running workers that don't survive process restart.
4
+ */
5
+
6
+ import type { BotProfile, BotInstance } from './profile-types.js';
7
+
8
+ export class InstanceManager {
9
+ private instances = new Map<string, BotInstance>();
10
+
11
+ /** Spawn a new instance for the given profile. Throws if maxInstances reached. */
12
+ spawn(profile: BotProfile): BotInstance {
13
+ const existing = this.listByProfile(profile.id);
14
+ if (existing.length >= profile.maxInstances) {
15
+ throw new Error(
16
+ `Cannot spawn: profile "${profile.id}" already at max instances (${profile.maxInstances})`,
17
+ );
18
+ }
19
+
20
+ const usedIndices = new Set(existing.map((i) => i.index));
21
+ let index = 0;
22
+ while (usedIndices.has(index)) index++;
23
+
24
+ const instance: BotInstance = {
25
+ instanceId: `${profile.id}-${index}`,
26
+ profileId: profile.id,
27
+ index,
28
+ status: 'idle',
29
+ tokensUsed: 0,
30
+ cost: 0,
31
+ tasksCompleted: 0,
32
+ tasksFailed: 0,
33
+ };
34
+
35
+ this.instances.set(instance.instanceId, instance);
36
+ return { ...instance };
37
+ }
38
+
39
+ /** Get instance by ID (returns a copy), or null if not found. */
40
+ get(instanceId: string): BotInstance | null {
41
+ const inst = this.instances.get(instanceId);
42
+ return inst ? { ...inst } : null;
43
+ }
44
+
45
+ /** Return copies of all instances. */
46
+ listAll(): BotInstance[] {
47
+ return [...this.instances.values()].map((i) => ({ ...i }));
48
+ }
49
+
50
+ /** Return copies of instances belonging to a specific profile. */
51
+ listByProfile(profileId: string): BotInstance[] {
52
+ return [...this.instances.values()]
53
+ .filter((i) => i.profileId === profileId)
54
+ .map((i) => ({ ...i }));
55
+ }
56
+
57
+ /** Return idle instances for a given profile. */
58
+ findIdle(profileId: string): BotInstance[] {
59
+ return [...this.instances.values()]
60
+ .filter((i) => i.profileId === profileId && i.status === 'idle')
61
+ .map((i) => ({ ...i }));
62
+ }
63
+
64
+ /** Mark an instance as executing a specific task/run. */
65
+ markExecuting(instanceId: string, taskId: string, runId: string): void {
66
+ const inst = this.instances.get(instanceId);
67
+ if (!inst) throw new Error(`Instance not found: ${instanceId}`);
68
+ inst.status = 'executing';
69
+ inst.currentTaskId = taskId;
70
+ inst.currentRunId = runId;
71
+ inst.startedAt = new Date().toISOString();
72
+ }
73
+
74
+ /** Mark an instance as idle after task completion/failure. */
75
+ markIdle(instanceId: string, success: boolean): void {
76
+ const inst = this.instances.get(instanceId);
77
+ if (!inst) throw new Error(`Instance not found: ${instanceId}`);
78
+ inst.status = 'idle';
79
+ inst.currentTaskId = undefined;
80
+ inst.currentRunId = undefined;
81
+ inst.startedAt = undefined;
82
+ if (success) {
83
+ inst.tasksCompleted++;
84
+ } else {
85
+ inst.tasksFailed++;
86
+ }
87
+ }
88
+
89
+ /** Record token/cost usage for an instance. */
90
+ recordUsage(instanceId: string, tokens: number, cost: number): void {
91
+ const inst = this.instances.get(instanceId);
92
+ if (!inst) throw new Error(`Instance not found: ${instanceId}`);
93
+ inst.tokensUsed += tokens;
94
+ inst.cost += cost;
95
+ }
96
+
97
+ /** Stop and remove an instance. Returns true if it existed. */
98
+ stop(instanceId: string): boolean {
99
+ return this.instances.delete(instanceId);
100
+ }
101
+
102
+ /** Stop and remove all instances. */
103
+ stopAll(): void {
104
+ this.instances.clear();
105
+ }
106
+
107
+ /** Scale instances for a profile to the target count. Clamps to [0, maxInstances]. */
108
+ scaleTo(profile: BotProfile, target: number): void {
109
+ const clamped = Math.max(0, Math.min(target, profile.maxInstances));
110
+ const current = this.listByProfile(profile.id);
111
+
112
+ if (current.length < clamped) {
113
+ // Spawn more
114
+ const toSpawn = clamped - current.length;
115
+ for (let i = 0; i < toSpawn; i++) {
116
+ this.spawn(profile);
117
+ }
118
+ } else if (current.length > clamped) {
119
+ // Stop idle instances first
120
+ const idle = current.filter((i) => i.status === 'idle');
121
+ const toStop = current.length - clamped;
122
+ const stopped = idle.slice(0, toStop);
123
+ for (const inst of stopped) {
124
+ this.stop(inst.instanceId);
125
+ }
126
+ }
127
+ }
128
+ }