@synergenius/flow-weaver-pack-weaver 0.9.59 → 0.9.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-chat-provider.d.ts +12 -0
- package/dist/ai-chat-provider.d.ts.map +1 -1
- package/dist/ai-chat-provider.js +351 -335
- package/dist/ai-chat-provider.js.map +1 -1
- package/dist/bot/agent-loop.d.ts +20 -0
- package/dist/bot/agent-loop.d.ts.map +1 -0
- package/dist/bot/agent-loop.js +331 -0
- package/dist/bot/agent-loop.js.map +1 -0
- package/dist/bot/ai-router.d.ts +19 -0
- package/dist/bot/ai-router.d.ts.map +1 -0
- package/dist/bot/ai-router.js +104 -0
- package/dist/bot/ai-router.js.map +1 -0
- package/dist/bot/assistant-tools.d.ts.map +1 -1
- package/dist/bot/assistant-tools.js +49 -33
- package/dist/bot/assistant-tools.js.map +1 -1
- package/dist/bot/async-mutex.d.ts +13 -0
- package/dist/bot/async-mutex.d.ts.map +1 -0
- package/dist/bot/async-mutex.js +37 -0
- package/dist/bot/async-mutex.js.map +1 -0
- package/dist/bot/bot-manager.d.ts +2 -2
- package/dist/bot/bot-manager.d.ts.map +1 -1
- package/dist/bot/bot-manager.js +3 -3
- package/dist/bot/bot-manager.js.map +1 -1
- package/dist/bot/bot-registry.js +2 -2
- package/dist/bot/bot-registry.js.map +1 -1
- package/dist/bot/conversation-store.d.ts +1 -0
- package/dist/bot/conversation-store.d.ts.map +1 -1
- package/dist/bot/conversation-store.js.map +1 -1
- package/dist/bot/dashboard.d.ts.map +1 -1
- package/dist/bot/dashboard.js +17 -8
- package/dist/bot/dashboard.js.map +1 -1
- package/dist/bot/improve-loop.js.map +1 -1
- package/dist/bot/index.d.ts +2 -4
- package/dist/bot/index.d.ts.map +1 -1
- package/dist/bot/index.js +1 -2
- package/dist/bot/index.js.map +1 -1
- package/dist/bot/instance-manager.d.ts +31 -0
- package/dist/bot/instance-manager.d.ts.map +1 -0
- package/dist/bot/instance-manager.js +115 -0
- package/dist/bot/instance-manager.js.map +1 -0
- package/dist/bot/orchestrator.d.ts +36 -0
- package/dist/bot/orchestrator.d.ts.map +1 -0
- package/dist/bot/orchestrator.js +176 -0
- package/dist/bot/orchestrator.js.map +1 -0
- package/dist/bot/profile-store.d.ts +36 -0
- package/dist/bot/profile-store.d.ts.map +1 -0
- package/dist/bot/profile-store.js +208 -0
- package/dist/bot/profile-store.js.map +1 -0
- package/dist/bot/profile-types.d.ts +126 -0
- package/dist/bot/profile-types.d.ts.map +1 -0
- package/dist/bot/profile-types.js +7 -0
- package/dist/bot/profile-types.js.map +1 -0
- package/dist/bot/run-store.d.ts.map +1 -1
- package/dist/bot/run-store.js +8 -0
- package/dist/bot/run-store.js.map +1 -1
- package/dist/bot/runner.d.ts +4 -0
- package/dist/bot/runner.d.ts.map +1 -1
- package/dist/bot/runner.js +5 -1
- package/dist/bot/runner.js.map +1 -1
- package/dist/bot/swarm-controller.d.ts +109 -0
- package/dist/bot/swarm-controller.d.ts.map +1 -0
- package/dist/bot/swarm-controller.js +640 -0
- package/dist/bot/swarm-controller.js.map +1 -0
- package/dist/bot/swarm-event-log.d.ts +28 -0
- package/dist/bot/swarm-event-log.d.ts.map +1 -0
- package/dist/bot/swarm-event-log.js +54 -0
- package/dist/bot/swarm-event-log.js.map +1 -0
- package/dist/bot/task-prompt-builder.d.ts +22 -0
- package/dist/bot/task-prompt-builder.d.ts.map +1 -0
- package/dist/bot/task-prompt-builder.js +240 -0
- package/dist/bot/task-prompt-builder.js.map +1 -0
- package/dist/bot/task-store.d.ts +21 -0
- package/dist/bot/task-store.d.ts.map +1 -0
- package/dist/bot/task-store.js +364 -0
- package/dist/bot/task-store.js.map +1 -0
- package/dist/bot/task-types.d.ts +79 -0
- package/dist/bot/task-types.d.ts.map +1 -0
- package/dist/bot/task-types.js +6 -0
- package/dist/bot/task-types.js.map +1 -0
- package/dist/bot/types.d.ts +8 -0
- package/dist/bot/types.d.ts.map +1 -1
- package/dist/cli-handlers.d.ts.map +1 -1
- package/dist/cli-handlers.js +79 -54
- package/dist/cli-handlers.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +749 -0
- package/dist/cli.js.map +1 -0
- package/dist/docs/docs/weaver-bot-usage.md +35 -18
- package/dist/docs/docs/weaver-config.md +20 -0
- package/dist/docs/docs/weaver-task-queue.md +31 -19
- package/dist/docs/weaver-config.md +15 -9
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-tools.d.ts +17 -0
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +98 -279
- package/dist/mcp-tools.js.map +1 -1
- package/dist/node-types/bot-report.d.ts.map +1 -1
- package/dist/node-types/bot-report.js +6 -24
- package/dist/node-types/bot-report.js.map +1 -1
- package/dist/node-types/orchestrator-dispatch.d.ts +17 -0
- package/dist/node-types/orchestrator-dispatch.d.ts.map +1 -0
- package/dist/node-types/orchestrator-dispatch.js +63 -0
- package/dist/node-types/orchestrator-dispatch.js.map +1 -0
- package/dist/node-types/orchestrator-load-state.d.ts +16 -0
- package/dist/node-types/orchestrator-load-state.d.ts.map +1 -0
- package/dist/node-types/orchestrator-load-state.js +60 -0
- package/dist/node-types/orchestrator-load-state.js.map +1 -0
- package/dist/node-types/orchestrator-route.d.ts +16 -0
- package/dist/node-types/orchestrator-route.d.ts.map +1 -0
- package/dist/node-types/orchestrator-route.js +28 -0
- package/dist/node-types/orchestrator-route.js.map +1 -0
- package/dist/node-types/receive-task.d.ts +2 -3
- package/dist/node-types/receive-task.d.ts.map +1 -1
- package/dist/node-types/receive-task.js +3 -48
- package/dist/node-types/receive-task.js.map +1 -1
- package/dist/templates/weaver-template.d.ts +11 -0
- package/dist/templates/weaver-template.d.ts.map +1 -0
- package/dist/templates/weaver-template.js +53 -0
- package/dist/templates/weaver-template.js.map +1 -0
- package/dist/ui/bot-activity.js +2 -2
- package/dist/ui/bot-constants.d.ts +14 -0
- package/dist/ui/bot-constants.d.ts.map +1 -0
- package/dist/ui/bot-constants.js +189 -0
- package/dist/ui/bot-constants.js.map +1 -0
- package/dist/ui/bot-panel.js +207 -245
- package/dist/ui/bot-slot-card.js +141 -0
- package/dist/ui/budget-bar.js +59 -0
- package/dist/ui/chat-task-result.js +178 -0
- package/dist/ui/decision-log.js +136 -0
- package/dist/ui/profile-card.js +158 -0
- package/dist/ui/profile-editor.js +597 -0
- package/dist/ui/swarm-controls.js +245 -0
- package/dist/ui/swarm-dashboard.js +3012 -0
- package/dist/ui/task-create-form.js +98 -0
- package/dist/ui/task-detail-view.js +1044 -0
- package/dist/ui/task-pool-list.js +156 -0
- package/dist/workflows/orchestrator.d.ts +21 -0
- package/dist/workflows/orchestrator.d.ts.map +1 -0
- package/dist/workflows/orchestrator.js +281 -0
- package/dist/workflows/orchestrator.js.map +1 -0
- package/dist/workflows/weaver-bot-session.d.ts +65 -0
- package/dist/workflows/weaver-bot-session.d.ts.map +1 -0
- package/dist/workflows/weaver-bot-session.js +68 -0
- package/dist/workflows/weaver-bot-session.js.map +1 -0
- package/dist/workflows/weaver.d.ts +24 -0
- package/dist/workflows/weaver.d.ts.map +1 -0
- package/dist/workflows/weaver.js +28 -0
- package/dist/workflows/weaver.js.map +1 -0
- package/flowweaver.manifest.json +547 -133
- package/package.json +1 -1
- package/src/ai-chat-provider.ts +378 -371
- package/src/bot/ai-router.ts +132 -0
- package/src/bot/assistant-tools.ts +47 -29
- package/src/bot/async-mutex.ts +37 -0
- package/src/bot/bot-manager.ts +3 -3
- package/src/bot/bot-registry.ts +2 -2
- package/src/bot/conversation-store.ts +2 -1
- package/src/bot/dashboard.ts +17 -8
- package/src/bot/improve-loop.ts +6 -6
- package/src/bot/index.ts +2 -4
- package/src/bot/instance-manager.ts +128 -0
- package/src/bot/orchestrator.ts +244 -0
- package/src/bot/profile-store.ts +225 -0
- package/src/bot/profile-types.ts +141 -0
- package/src/bot/run-store.ts +8 -0
- package/src/bot/runner.ts +9 -1
- package/src/bot/swarm-controller.ts +780 -0
- package/src/bot/swarm-event-log.ts +57 -0
- package/src/bot/task-prompt-builder.ts +309 -0
- package/src/bot/task-store.ts +407 -0
- package/src/bot/task-types.ts +100 -0
- package/src/bot/types.ts +8 -0
- package/src/cli-handlers.ts +78 -53
- package/src/docs/weaver-bot-usage.md +35 -18
- package/src/docs/weaver-config.md +20 -0
- package/src/docs/weaver-task-queue.md +31 -19
- package/src/index.ts +5 -4
- package/src/mcp-tools.ts +129 -372
- package/src/node-types/bot-report.ts +6 -24
- package/src/node-types/orchestrator-dispatch.ts +71 -0
- package/src/node-types/orchestrator-load-state.ts +66 -0
- package/src/node-types/orchestrator-route.ts +33 -0
- package/src/node-types/receive-task.ts +3 -57
- package/src/ui/bot-activity.tsx +2 -2
- package/src/ui/bot-constants.ts +192 -0
- package/src/ui/bot-panel.tsx +213 -247
- package/src/ui/bot-slot-card.tsx +139 -0
- package/src/ui/budget-bar.tsx +30 -0
- package/src/ui/chat-task-result.tsx +236 -0
- package/src/ui/decision-log.tsx +148 -0
- package/src/ui/profile-card.tsx +157 -0
- package/src/ui/profile-editor.tsx +384 -0
- package/src/ui/swarm-controls.tsx +260 -0
- package/src/ui/swarm-dashboard.tsx +647 -0
- package/src/ui/task-create-form.tsx +87 -0
- package/src/ui/task-detail-view.tsx +841 -0
- package/src/ui/task-pool-list.tsx +187 -0
- package/src/workflows/orchestrator.ts +302 -0
- package/dist/docs/weaver-bot-usage.md +0 -34
- package/dist/docs/weaver-genesis.md +0 -32
- package/dist/docs/weaver-task-queue.md +0 -34
- package/dist/ui/bot-workspace.js +0 -1015
- package/dist/ui/chat-bot-result.js +0 -71
- package/dist/ui/queue-input.js +0 -82
- package/dist/ui/session-bar.js +0 -174
- package/src/bot/error-guide.ts +0 -4
- package/src/bot/retry-utils.ts +0 -4
- package/src/bot/session-state.ts +0 -116
- package/src/bot/task-queue.ts +0 -262
- package/src/ui/bot-workspace.tsx +0 -442
- package/src/ui/chat-bot-result.tsx +0 -81
- package/src/ui/queue-input.tsx +0 -56
- package/src/ui/session-bar.tsx +0 -157
|
@@ -0,0 +1,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
|
|
57
|
-
const tasks = await
|
|
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
|
|
60
|
-
const
|
|
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: ${
|
|
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.
|
|
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
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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
|
|
102
|
+
const store = mgr.getTaskStore(String(args.bot));
|
|
103
103
|
const tasks = args.tasks as Array<{ instruction: string; targets?: string[] }>;
|
|
104
|
-
let added = 0
|
|
104
|
+
let added = 0;
|
|
105
105
|
for (const t of tasks) {
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
await store.create({
|
|
107
|
+
title: t.instruction,
|
|
108
|
+
description: t.instruction,
|
|
109
|
+
priority: 0,
|
|
110
|
+
createdBy: 'ai',
|
|
111
|
+
});
|
|
112
|
+
added++;
|
|
108
113
|
}
|
|
109
|
-
|
|
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
|
|
114
|
-
const tasks = await
|
|
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.
|
|
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
|
|
121
|
-
const
|
|
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
|
|
326
|
-
const projects = dirs.map(d => {
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
334
|
+
// Each dir is a project — check 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
|
+
}
|
package/src/bot/bot-manager.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
178
|
+
return new TaskStore(bot.meta.botDir);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
getSteering(name: string): SteeringController {
|
package/src/bot/bot-registry.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bot registry — manages .
|
|
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 = '.
|
|
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:
|
|
225
|
+
conversationId: conversation.cloudConversationId,
|
|
225
226
|
}),
|
|
226
227
|
}).catch(() => {}); // fire-and-forget
|
|
227
228
|
} catch { /* sync not available */ }
|
package/src/bot/dashboard.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
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
|
|
332
|
-
const
|
|
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
|
}
|
package/src/bot/improve-loop.ts
CHANGED
|
@@ -526,9 +526,9 @@ function emptyResult(startedAt: string, branch: string, worktreePath: string, re
|
|
|
526
526
|
|
|
527
527
|
// --- Helpers ---
|
|
528
528
|
|
|
529
|
-
let cachedProvider:
|
|
530
|
-
let cachedTools:
|
|
531
|
-
let cachedExecutor:
|
|
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
|
|
566
|
-
cachedTools
|
|
567
|
-
cachedExecutor
|
|
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 {
|
|
132
|
-
export type {
|
|
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
|
+
}
|