@synergenius/flow-weaver-pack-weaver 0.9.151 → 0.9.153
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.js +4 -4
- package/dist/ai-chat-provider.js.map +1 -1
- package/dist/bot/ai-client.d.ts +30 -0
- package/dist/bot/ai-client.d.ts.map +1 -1
- package/dist/bot/ai-client.js +37 -0
- package/dist/bot/ai-client.js.map +1 -1
- package/dist/bot/behavior-defaults.d.ts.map +1 -1
- package/dist/bot/behavior-defaults.js +7 -2
- package/dist/bot/behavior-defaults.js.map +1 -1
- package/dist/bot/capability-registry.d.ts.map +1 -1
- package/dist/bot/capability-registry.js +48 -30
- package/dist/bot/capability-registry.js.map +1 -1
- package/dist/bot/file-validator.d.ts +7 -0
- package/dist/bot/file-validator.d.ts.map +1 -1
- package/dist/bot/file-validator.js +76 -0
- package/dist/bot/file-validator.js.map +1 -1
- package/dist/bot/instance-manager.d.ts +22 -7
- package/dist/bot/instance-manager.d.ts.map +1 -1
- package/dist/bot/instance-manager.js +69 -7
- package/dist/bot/instance-manager.js.map +1 -1
- package/dist/bot/orchestrator.d.ts +11 -9
- package/dist/bot/orchestrator.d.ts.map +1 -1
- package/dist/bot/orchestrator.js +56 -107
- package/dist/bot/orchestrator.js.map +1 -1
- package/dist/bot/runner.d.ts +29 -0
- package/dist/bot/runner.d.ts.map +1 -1
- package/dist/bot/runner.js +114 -73
- package/dist/bot/runner.js.map +1 -1
- package/dist/bot/step-executor.d.ts.map +1 -1
- package/dist/bot/step-executor.js +106 -25
- package/dist/bot/step-executor.js.map +1 -1
- package/dist/bot/swarm-controller.d.ts +7 -6
- package/dist/bot/swarm-controller.d.ts.map +1 -1
- package/dist/bot/swarm-controller.js +64 -74
- package/dist/bot/swarm-controller.js.map +1 -1
- package/dist/bot/task-types.d.ts +1 -0
- package/dist/bot/task-types.d.ts.map +1 -1
- package/dist/bot/weaver-tools.d.ts +1 -1
- package/dist/bot/weaver-tools.d.ts.map +1 -1
- package/dist/bot/weaver-tools.js +6 -1
- package/dist/bot/weaver-tools.js.map +1 -1
- package/dist/node-types/agent-execute.js +2 -2
- package/dist/node-types/agent-execute.js.map +1 -1
- package/dist/node-types/bot-report.d.ts.map +1 -1
- package/dist/node-types/bot-report.js +5 -2
- package/dist/node-types/bot-report.js.map +1 -1
- package/dist/node-types/build-context.js +2 -1
- package/dist/node-types/build-context.js.map +1 -1
- package/dist/node-types/exec-validate-retry.d.ts +3 -3
- package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
- package/dist/node-types/exec-validate-retry.js +13 -184
- package/dist/node-types/exec-validate-retry.js.map +1 -1
- package/dist/node-types/load-config.d.ts +1 -0
- package/dist/node-types/load-config.d.ts.map +1 -1
- package/dist/node-types/load-config.js +1 -0
- package/dist/node-types/load-config.js.map +1 -1
- package/dist/node-types/plan-task.d.ts +7 -5
- package/dist/node-types/plan-task.d.ts.map +1 -1
- package/dist/node-types/plan-task.js +282 -83
- package/dist/node-types/plan-task.js.map +1 -1
- package/dist/ui/bot-panel.js +1 -1
- package/dist/ui/capability-editor.js +48 -30
- package/dist/ui/profile-editor.js +46 -28
- package/dist/ui/swarm-dashboard.js +71 -33
- package/dist/ui/task-detail-view.js +22 -2
- package/dist/workflows/weaver-bot.d.ts +2 -2
- package/dist/workflows/weaver-bot.d.ts.map +1 -1
- package/dist/workflows/weaver-bot.js +5 -4
- package/dist/workflows/weaver-bot.js.map +1 -1
- package/flowweaver.manifest.json +1 -1
- package/package.json +1 -1
- package/src/ai-chat-provider.ts +4 -4
- package/src/bot/ai-client.ts +65 -0
- package/src/bot/behavior-defaults.ts +5 -2
- package/src/bot/capability-registry.ts +48 -30
- package/src/bot/file-validator.ts +97 -0
- package/src/bot/instance-manager.ts +77 -7
- package/src/bot/orchestrator.ts +63 -126
- package/src/bot/runner.ts +124 -70
- package/src/bot/step-executor.ts +115 -25
- package/src/bot/swarm-controller.ts +65 -76
- package/src/bot/task-types.ts +1 -0
- package/src/bot/weaver-tools.ts +7 -1
- package/src/node-types/agent-execute.ts +2 -2
- package/src/node-types/bot-report.ts +5 -2
- package/src/node-types/build-context.ts +2 -1
- package/src/node-types/exec-validate-retry.ts +14 -203
- package/src/node-types/load-config.ts +1 -0
- package/src/node-types/plan-task.ts +313 -88
- package/src/ui/bot-panel.tsx +1 -1
- package/src/ui/swarm-dashboard.tsx +3 -3
- package/src/ui/task-detail-view.tsx +25 -2
- package/src/workflows/weaver-bot.ts +5 -4
|
@@ -1,61 +1,135 @@
|
|
|
1
|
-
import type { WeaverContext } from '../bot/types.js';
|
|
2
|
-
import {
|
|
3
|
-
import type { AiTool } from '../bot/ai-client.js';
|
|
1
|
+
import type { WeaverContext, StepLogEntry } from '../bot/types.js';
|
|
2
|
+
import { callPlatformWithMessages, callCapabilityTriage } from '../bot/ai-client.js';
|
|
3
|
+
import type { AiTool, AiCallResult, ChatMessage } from '../bot/ai-client.js';
|
|
4
4
|
import { auditEmit } from '../bot/audit-logger.js';
|
|
5
|
-
import {
|
|
5
|
+
import { executeStep } from '../bot/step-executor.js';
|
|
6
6
|
import { getCapabilitiesByNames, BUILT_IN_CAPABILITIES } from '../bot/capability-registry.js';
|
|
7
7
|
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
|
-
//
|
|
10
|
-
// structured JSON arguments instead of prose. Works across Anthropic and OpenAI.
|
|
9
|
+
// Tool definitions — built dynamically from capability-granted operations
|
|
11
10
|
// ---------------------------------------------------------------------------
|
|
12
11
|
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
/** All known tool schemas, keyed by operation name. */
|
|
13
|
+
const TOOL_SCHEMAS: Record<string, AiTool> = {
|
|
14
|
+
read_file: {
|
|
15
|
+
name: 'read_file',
|
|
16
|
+
description: 'Read a file and return its content',
|
|
17
|
+
parameters: { type: 'object', properties: { file: { type: 'string' } }, required: ['file'] },
|
|
18
|
+
},
|
|
19
|
+
write_file: {
|
|
20
|
+
name: 'write_file',
|
|
21
|
+
description: 'Write a file (full content)',
|
|
22
|
+
parameters: { type: 'object', properties: { file: { type: 'string' }, content: { type: 'string' } }, required: ['file', 'content'] },
|
|
23
|
+
},
|
|
24
|
+
patch_file: {
|
|
25
|
+
name: 'patch_file',
|
|
26
|
+
description: 'Find and replace in a file',
|
|
19
27
|
parameters: {
|
|
20
28
|
type: 'object',
|
|
21
29
|
properties: {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
type: 'array',
|
|
25
|
-
description: 'Ordered list of operations to execute',
|
|
26
|
-
items: {
|
|
27
|
-
type: 'object',
|
|
28
|
-
properties: {
|
|
29
|
-
operation: {
|
|
30
|
-
type: 'string',
|
|
31
|
-
description: `Tool to invoke: ${ops.join(', ')}`,
|
|
32
|
-
},
|
|
33
|
-
description: { type: 'string', description: 'What this step does' },
|
|
34
|
-
args: {
|
|
35
|
-
type: 'object',
|
|
36
|
-
description: 'Arguments for the operation (e.g. {file, content} for write_file, {file, find, replace} for patch_file)',
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
required: ['operation', 'description', 'args'],
|
|
40
|
-
},
|
|
41
|
-
},
|
|
30
|
+
file: { type: 'string' },
|
|
31
|
+
patches: { type: 'array', items: { type: 'object', properties: { find: { type: 'string' }, replace: { type: 'string' } } } },
|
|
42
32
|
},
|
|
43
|
-
required: ['
|
|
33
|
+
required: ['file', 'patches'],
|
|
44
34
|
},
|
|
45
|
-
}
|
|
35
|
+
},
|
|
36
|
+
run_shell: {
|
|
37
|
+
name: 'run_shell',
|
|
38
|
+
description: 'Run a shell command',
|
|
39
|
+
parameters: { type: 'object', properties: { command: { type: 'string' } }, required: ['command'] },
|
|
40
|
+
},
|
|
41
|
+
list_files: {
|
|
42
|
+
name: 'list_files',
|
|
43
|
+
description: 'List files in a directory',
|
|
44
|
+
parameters: { type: 'object', properties: { directory: { type: 'string' }, pattern: { type: 'string' } } },
|
|
45
|
+
},
|
|
46
|
+
task_create: {
|
|
47
|
+
name: 'task_create',
|
|
48
|
+
description: 'Create a swarm subtask',
|
|
49
|
+
parameters: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
title: { type: 'string' },
|
|
53
|
+
description: { type: 'string' },
|
|
54
|
+
assignedProfile: { type: 'string' },
|
|
55
|
+
parentId: { type: 'string' },
|
|
56
|
+
dependsOn: { type: 'array', items: { type: 'string' } },
|
|
57
|
+
complexity: { type: 'string' },
|
|
58
|
+
},
|
|
59
|
+
required: ['title'],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
done: {
|
|
63
|
+
name: 'done',
|
|
64
|
+
description: 'Signal task completion. Call this when you have finished the task.',
|
|
65
|
+
parameters: { type: 'object', properties: { summary: { type: 'string' } }, required: ['summary'] },
|
|
66
|
+
},
|
|
67
|
+
remember: {
|
|
68
|
+
name: 'remember',
|
|
69
|
+
description: 'Save a project convention for future sessions',
|
|
70
|
+
parameters: { type: 'object', properties: { key: { type: 'string' }, value: { type: 'string' } }, required: ['key', 'value'] },
|
|
71
|
+
},
|
|
72
|
+
recall: {
|
|
73
|
+
name: 'recall',
|
|
74
|
+
description: 'Recall saved project conventions',
|
|
75
|
+
parameters: { type: 'object', properties: {} },
|
|
76
|
+
},
|
|
77
|
+
validate: {
|
|
78
|
+
name: 'validate',
|
|
79
|
+
description: 'Validate a Flow Weaver workflow file',
|
|
80
|
+
parameters: { type: 'object', properties: { file: { type: 'string' } }, required: ['file'] },
|
|
81
|
+
},
|
|
82
|
+
respond: {
|
|
83
|
+
name: 'respond',
|
|
84
|
+
description: 'Send a text response to the user',
|
|
85
|
+
parameters: { type: 'object', properties: { response: { type: 'string' } }, required: ['response'] },
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** Build tool definitions from capability-granted operation names. */
|
|
90
|
+
function buildToolDefinitions(grantedTools: string[]): AiTool[] {
|
|
91
|
+
const tools: AiTool[] = [];
|
|
92
|
+
const seen = new Set<string>();
|
|
93
|
+
|
|
94
|
+
for (const name of grantedTools) {
|
|
95
|
+
if (seen.has(name)) continue;
|
|
96
|
+
seen.add(name);
|
|
97
|
+
const schema = TOOL_SCHEMAS[name];
|
|
98
|
+
if (schema) tools.push(schema);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Always include 'done' so the AI can signal completion
|
|
102
|
+
if (!seen.has('done')) {
|
|
103
|
+
tools.push(TOOL_SCHEMAS.done);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return tools;
|
|
46
107
|
}
|
|
47
108
|
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Safety limits
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
const MAX_TOOL_CALLS = 30;
|
|
114
|
+
const AGENT_TIMEOUT_MS = 180_000; // 3 minutes
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Agent Loop — replaces Plan Task + Execute & Validate
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
48
120
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
121
|
+
* Agent loop: sends task + tools to the AI, executes tool calls iteratively
|
|
122
|
+
* until the AI signals completion or a safety limit is reached.
|
|
123
|
+
*
|
|
124
|
+
* Replaces the old Plan Task → Execute & Validate two-phase workflow.
|
|
51
125
|
*
|
|
52
126
|
* @flowWeaver nodeType
|
|
53
|
-
* @label
|
|
54
|
-
* @icon
|
|
127
|
+
* @label Agent Loop
|
|
128
|
+
* @icon psychology
|
|
55
129
|
* @color blue
|
|
56
130
|
* @input ctx [order:0] - Weaver context (JSON)
|
|
57
131
|
* @input [modelOverride] [order:1] - Model ID override from profile behavior
|
|
58
|
-
* @output ctx [order:0] - Weaver context with
|
|
132
|
+
* @output ctx [order:0] - Weaver context with results (JSON)
|
|
59
133
|
* @output onSuccess [order:-2] - On Success
|
|
60
134
|
* @output onFailure [order:-1] [hidden] - On Failure
|
|
61
135
|
*/
|
|
@@ -72,15 +146,24 @@ export async function weaverPlanTask(
|
|
|
72
146
|
|
|
73
147
|
if (!execute) {
|
|
74
148
|
context.planJson = '{"steps":[],"summary":"dry run"}';
|
|
149
|
+
context.resultJson = JSON.stringify({ success: true, toolCallCount: 0 });
|
|
150
|
+
context.filesModified = '[]';
|
|
151
|
+
context.stepLogJson = '[]';
|
|
152
|
+
context.allValid = true;
|
|
75
153
|
return { onSuccess: true, onFailure: false, ctx: JSON.stringify(context) };
|
|
76
154
|
}
|
|
77
155
|
|
|
78
156
|
const pInfo = modelOverride
|
|
79
157
|
? { ...env.providerInfo, model: modelOverride }
|
|
80
158
|
: env.providerInfo;
|
|
159
|
+
const { projectDir } = env;
|
|
81
160
|
|
|
82
161
|
if (!context.taskJson) {
|
|
83
|
-
context.planJson = JSON.stringify({ steps: [], summary: '
|
|
162
|
+
context.planJson = JSON.stringify({ steps: [], summary: 'Agent loop failed: missing taskJson' });
|
|
163
|
+
context.resultJson = JSON.stringify({ success: false, error: 'missing taskJson' });
|
|
164
|
+
context.filesModified = '[]';
|
|
165
|
+
context.stepLogJson = '[]';
|
|
166
|
+
context.allValid = false;
|
|
84
167
|
return { onSuccess: false, onFailure: true, ctx: JSON.stringify(context) };
|
|
85
168
|
}
|
|
86
169
|
const task = JSON.parse(context.taskJson);
|
|
@@ -91,80 +174,222 @@ export async function weaverPlanTask(
|
|
|
91
174
|
const availableCaps = getCapabilitiesByNames(availableCapNames);
|
|
92
175
|
|
|
93
176
|
// Capability triage: cheap Haiku call selects which capabilities this task needs
|
|
94
|
-
let selectedCaps = availableCaps;
|
|
177
|
+
let selectedCaps = availableCaps;
|
|
95
178
|
const triageResult = await callCapabilityTriage(pInfo, task.instruction, availableCaps);
|
|
96
179
|
if (triageResult) {
|
|
97
180
|
selectedCaps = getCapabilitiesByNames(triageResult);
|
|
98
181
|
}
|
|
99
|
-
// If triage failed, selectedCaps = all available (no degradation)
|
|
100
182
|
|
|
183
|
+
// Build system prompt from capabilities
|
|
101
184
|
let systemPrompt: string;
|
|
102
185
|
try {
|
|
103
186
|
const mod = await import('../bot/system-prompt.js');
|
|
104
187
|
const basePrompt = mod.buildPromptFromCapabilities(selectedCaps);
|
|
105
|
-
// Only include the context bundle if the 'context' capability was selected.
|
|
106
|
-
// This prevents sending 10k+ tokens of FW authoring docs for simple file creation tasks.
|
|
107
188
|
const selectedCapNames = new Set(selectedCaps.map(c => c.name));
|
|
108
189
|
const contextBundle = selectedCapNames.has('context') ? context.contextBundle : undefined;
|
|
109
190
|
const botPrompt = mod.buildBotSystemPrompt(contextBundle, undefined, context.env?.projectDir);
|
|
110
191
|
systemPrompt = basePrompt + '\n\n' + botPrompt;
|
|
111
192
|
} catch (err) {
|
|
112
|
-
if (process.env.WEAVER_VERBOSE) console.error('[
|
|
113
|
-
systemPrompt = 'You are Weaver, an AI workflow bot.
|
|
193
|
+
if (process.env.WEAVER_VERBOSE) console.error('[agent-loop] system prompt build failed:', err);
|
|
194
|
+
systemPrompt = 'You are Weaver, an AI workflow bot. Use the provided tools to complete tasks.';
|
|
114
195
|
}
|
|
115
196
|
|
|
116
|
-
|
|
197
|
+
// Build tool definitions from capability-granted operations
|
|
198
|
+
let tools: AiTool[];
|
|
199
|
+
try {
|
|
200
|
+
const mod2 = await import('../bot/system-prompt.js');
|
|
201
|
+
const grantedTools = mod2.collectToolsFromCapabilities(selectedCaps);
|
|
202
|
+
tools = buildToolDefinitions(grantedTools);
|
|
203
|
+
} catch {
|
|
204
|
+
tools = buildToolDefinitions(['read_file', 'write_file', 'patch_file', 'run_shell', 'list_files', 'task_create']);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Build initial user prompt
|
|
208
|
+
let userPrompt = `Task: ${task.instruction}\nMode: ${task.mode ?? 'create'}\n${task.id ? `Your Task ID is ${task.id}. Use parentId: "@self" in task_create to create subtasks under this task.` : ''}\n${task.targets ? 'Targets: ' + task.targets.join(', ') : ''}
|
|
117
209
|
|
|
118
|
-
|
|
210
|
+
Execute this task step by step using the available tools.
|
|
211
|
+
When you are done, call the "done" tool with a summary of what you accomplished.
|
|
119
212
|
Rules:
|
|
120
|
-
1.
|
|
121
|
-
2.
|
|
122
|
-
3.
|
|
123
|
-
|
|
213
|
+
1. Read files before modifying them (use read_file to get exact content for patches).
|
|
214
|
+
2. Use patch_file for modifications, write_file only for new files.
|
|
215
|
+
3. Verify your work by running tests or tsc when appropriate.`;
|
|
216
|
+
|
|
217
|
+
// Append retry context when this is a retry attempt (attempt > 0)
|
|
218
|
+
const attempt = task.attempt ?? 0;
|
|
219
|
+
if (attempt > 0) {
|
|
220
|
+
const retryParts: string[] = ['\n\n--- RETRY CONTEXT (attempt ' + (attempt + 1) + ') ---'];
|
|
221
|
+
if (task.lastError) {
|
|
222
|
+
retryParts.push('Previous attempt failed with error: ' + task.lastError);
|
|
223
|
+
}
|
|
224
|
+
const summaries: Array<{ outcome?: string; filesModified?: string[]; summary?: string }> = task.runSummaries ?? [];
|
|
225
|
+
const lastSummary = summaries.length > 0 ? summaries[summaries.length - 1] : undefined;
|
|
226
|
+
if (lastSummary) {
|
|
227
|
+
if (lastSummary.outcome) retryParts.push('Last outcome: ' + lastSummary.outcome);
|
|
228
|
+
if (lastSummary.summary) retryParts.push('Last summary: ' + lastSummary.summary);
|
|
229
|
+
if (lastSummary.filesModified && lastSummary.filesModified.length > 0) {
|
|
230
|
+
retryParts.push('Files already created/modified: ' + lastSummary.filesModified.join(', '));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
retryParts.push('Do NOT recreate files that already exist. Read them first and continue from where the previous attempt left off.');
|
|
234
|
+
retryParts.push('---');
|
|
235
|
+
userPrompt += retryParts.join('\n');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Seed symbolic ID map for task references
|
|
239
|
+
const symbolicIdMap: Record<string, string> = {};
|
|
240
|
+
if (task.id) symbolicIdMap['@self'] = task.id;
|
|
241
|
+
if (task.parentId) symbolicIdMap['@parent'] = task.parentId;
|
|
242
|
+
|
|
243
|
+
// State tracking
|
|
244
|
+
const filesModified: string[] = [];
|
|
245
|
+
const stepLog: StepLogEntry[] = [];
|
|
246
|
+
let toolCallCount = 0;
|
|
247
|
+
const deadline = Date.now() + AGENT_TIMEOUT_MS;
|
|
248
|
+
|
|
249
|
+
// Messages array for multi-turn conversation
|
|
250
|
+
const messages: ChatMessage[] = [
|
|
251
|
+
{ role: 'system', content: systemPrompt },
|
|
252
|
+
{ role: 'user', content: userPrompt },
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
auditEmit('run-start', { task: task.instruction, mode: 'agent-loop' });
|
|
124
256
|
|
|
125
257
|
try {
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
258
|
+
// Agent loop
|
|
259
|
+
let done = false;
|
|
260
|
+
while (!done && toolCallCount < MAX_TOOL_CALLS && Date.now() < deadline) {
|
|
261
|
+
const result: AiCallResult = await callPlatformWithMessages(messages, tools, pInfo.model, pInfo.maxTokens ?? 8192);
|
|
262
|
+
|
|
263
|
+
// If AI returns text with no tool calls, we're done
|
|
264
|
+
if (!result.toolCalls || result.toolCalls.length === 0) {
|
|
265
|
+
if (result.content) {
|
|
266
|
+
console.log(`\x1b[36m→ Agent: ${result.content.slice(0, 200)}\x1b[0m`);
|
|
267
|
+
}
|
|
268
|
+
done = true;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Process each tool call
|
|
273
|
+
for (const tc of result.toolCalls) {
|
|
274
|
+
if (toolCallCount >= MAX_TOOL_CALLS || Date.now() >= deadline) {
|
|
275
|
+
done = true;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
toolCallCount++;
|
|
280
|
+
const toolName = tc.name;
|
|
281
|
+
const toolArgs = tc.arguments;
|
|
282
|
+
|
|
283
|
+
// Check for done signal
|
|
284
|
+
if (toolName === 'done' || toolName === 'complete') {
|
|
285
|
+
console.log(`\x1b[36m→ Agent done: ${(toolArgs as Record<string, string>).summary ?? 'completed'}\x1b[0m`);
|
|
286
|
+
|
|
287
|
+
// Execute through step-executor for consistent handling
|
|
288
|
+
try {
|
|
289
|
+
const stepResult = await executeStep(
|
|
290
|
+
{ operation: toolName, args: toolArgs },
|
|
291
|
+
projectDir,
|
|
292
|
+
symbolicIdMap,
|
|
293
|
+
);
|
|
294
|
+
stepLog.push({ step: `${toolCallCount}:${toolName}`, status: 'ok', detail: stepResult.output ?? 'done' });
|
|
295
|
+
} catch {
|
|
296
|
+
stepLog.push({ step: `${toolCallCount}:${toolName}`, status: 'ok', detail: 'done' });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Add assistant message with tool use and tool result to messages
|
|
300
|
+
messages.push({
|
|
301
|
+
role: 'assistant',
|
|
302
|
+
content: result.content || undefined,
|
|
303
|
+
tool_use: { id: tc.id, name: toolName, input: toolArgs },
|
|
304
|
+
});
|
|
305
|
+
messages.push({
|
|
306
|
+
role: 'user',
|
|
307
|
+
tool_use_id: tc.id,
|
|
308
|
+
content: 'Task completed.',
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
done = true;
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Execute the tool via step-executor
|
|
316
|
+
let toolOutput: string;
|
|
317
|
+
try {
|
|
318
|
+
const stepResult = await executeStep(
|
|
319
|
+
{ operation: toolName, args: toolArgs },
|
|
320
|
+
projectDir,
|
|
321
|
+
symbolicIdMap,
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
// Track files modified — only from write/patch operations, not reads/listings
|
|
325
|
+
const isWriteOp = toolName === 'write_file' || toolName === 'patch_file' || toolName === 'create_workflow' || toolName === 'modify_source' || toolName === 'implement_node';
|
|
326
|
+
if (isWriteOp && stepResult.file) filesModified.push(stepResult.file);
|
|
327
|
+
|
|
328
|
+
// Build output for AI
|
|
329
|
+
if (stepResult.blocked) {
|
|
330
|
+
toolOutput = `BLOCKED: ${stepResult.blockReason}`;
|
|
331
|
+
stepLog.push({ step: `${toolCallCount}:${toolName}`, status: 'blocked', detail: stepResult.blockReason });
|
|
332
|
+
} else {
|
|
333
|
+
toolOutput = stepResult.output ?? 'OK';
|
|
334
|
+
stepLog.push({ step: `${toolCallCount}:${toolName}`, status: 'ok', detail: toolName });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
console.log(`\x1b[32m + ${toolCallCount}: ${toolName}\x1b[0m`);
|
|
338
|
+
} catch (err: unknown) {
|
|
339
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
340
|
+
toolOutput = `ERROR: ${msg}`;
|
|
341
|
+
stepLog.push({ step: `${toolCallCount}:${toolName}`, status: 'error', detail: msg });
|
|
342
|
+
console.error(`\x1b[31m x ${toolCallCount}: ${toolName}: ${msg}\x1b[0m`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Append assistant tool_use + tool result to messages for next iteration
|
|
346
|
+
messages.push({
|
|
347
|
+
role: 'assistant',
|
|
348
|
+
content: result.content || undefined,
|
|
349
|
+
tool_use: { id: tc.id, name: toolName, input: toolArgs },
|
|
350
|
+
});
|
|
351
|
+
messages.push({
|
|
352
|
+
role: 'user',
|
|
353
|
+
tool_use_id: tc.id,
|
|
354
|
+
content: toolOutput,
|
|
355
|
+
});
|
|
157
356
|
}
|
|
158
357
|
}
|
|
159
|
-
console.log(`\x1b[36m→ Plan: ${plan.summary ?? 'generated'}\x1b[0m`);
|
|
160
|
-
auditEmit('plan-created', { summary: plan.summary, stepCount: plan.steps?.length ?? 0 });
|
|
161
358
|
|
|
162
|
-
|
|
359
|
+
// Deduplicate files
|
|
360
|
+
const uniqueFiles = [...new Set(filesModified)];
|
|
361
|
+
|
|
362
|
+
// Store results in context (compatible with exec-validate-retry output format)
|
|
363
|
+
context.resultJson = JSON.stringify({
|
|
364
|
+
success: true,
|
|
365
|
+
toolCallCount,
|
|
366
|
+
filesModified: uniqueFiles,
|
|
367
|
+
stepsCompleted: toolCallCount,
|
|
368
|
+
stepsTotal: toolCallCount,
|
|
369
|
+
});
|
|
370
|
+
context.planJson = JSON.stringify({ steps: [], summary: `Agent loop: ${toolCallCount} tool calls` });
|
|
371
|
+
context.filesModified = JSON.stringify(uniqueFiles);
|
|
372
|
+
context.stepLogJson = JSON.stringify(stepLog);
|
|
373
|
+
context.allValid = true;
|
|
374
|
+
|
|
375
|
+
auditEmit('run-complete', {
|
|
376
|
+
success: true,
|
|
377
|
+
toolCalls: toolCallCount,
|
|
378
|
+
filesModified: uniqueFiles.length,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
console.log(`\x1b[36m→ Agent loop: ${toolCallCount} tool calls, ${uniqueFiles.length} files modified\x1b[0m`);
|
|
163
382
|
return { onSuccess: true, onFailure: false, ctx: JSON.stringify(context) };
|
|
164
383
|
} catch (err: unknown) {
|
|
165
384
|
const msg = err instanceof Error ? err.message : String(err);
|
|
166
|
-
console.error(`\x1b[31m→
|
|
167
|
-
|
|
385
|
+
console.error(`\x1b[31m→ Agent loop failed: ${msg}\x1b[0m`);
|
|
386
|
+
|
|
387
|
+
context.resultJson = JSON.stringify({ success: false, error: msg });
|
|
388
|
+
context.planJson = JSON.stringify({ steps: [], summary: `Agent loop failed: ${msg}` });
|
|
389
|
+
context.filesModified = JSON.stringify([...new Set(filesModified)]);
|
|
390
|
+
context.stepLogJson = JSON.stringify(stepLog);
|
|
391
|
+
context.allValid = false;
|
|
392
|
+
|
|
168
393
|
return { onSuccess: false, onFailure: true, ctx: JSON.stringify(context) };
|
|
169
394
|
}
|
|
170
395
|
}
|
package/src/ui/bot-panel.tsx
CHANGED
|
@@ -286,7 +286,7 @@ function BotPanel() {
|
|
|
286
286
|
React.createElement(Flex, { variant: 'row-center-start-nowrap-4' },
|
|
287
287
|
React.createElement(Typography, {
|
|
288
288
|
variant: 'caption-thick', color: 'color-text-high',
|
|
289
|
-
}, `${inst.
|
|
289
|
+
}, inst.profileId ? `${inst.instanceId} (${inst.profileId})` : inst.instanceId),
|
|
290
290
|
React.createElement(StatusIcon, {
|
|
291
291
|
status: statusToIconStatus(inst.status),
|
|
292
292
|
size: 'sm',
|
|
@@ -357,7 +357,7 @@ function SwarmDashboard() {
|
|
|
357
357
|
React.createElement(Tabs, {
|
|
358
358
|
tabs: [
|
|
359
359
|
{ id: 'tasks', title: `Tasks (${tasks.length})` },
|
|
360
|
-
{ id: 'bots', title: hasSwarmInstances ? `
|
|
360
|
+
{ id: 'bots', title: hasSwarmInstances ? `Workers (${swarmInstanceEntries.length})` : `Bots (${registeredBots.length})` },
|
|
361
361
|
{ id: 'profiles', title: `Profiles (${profiles.length})` },
|
|
362
362
|
{ id: 'config', title: 'Config' },
|
|
363
363
|
],
|
|
@@ -475,7 +475,7 @@ function SwarmDashboard() {
|
|
|
475
475
|
React.createElement(Typography, {
|
|
476
476
|
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
477
477
|
style: { width: '120px', flexShrink: 0 },
|
|
478
|
-
}, '
|
|
478
|
+
}, 'Worker'),
|
|
479
479
|
React.createElement(Typography, {
|
|
480
480
|
variant: 'smallCaption-regular', color: 'color-text-subtle',
|
|
481
481
|
style: { width: '110px', flexShrink: 0 },
|
|
@@ -511,7 +511,7 @@ function SwarmDashboard() {
|
|
|
511
511
|
key: inst.instanceId,
|
|
512
512
|
bot: {
|
|
513
513
|
botId: inst.instanceId,
|
|
514
|
-
botName: `${inst.
|
|
514
|
+
botName: inst.profileId ? `${inst.instanceId} (${inst.profileId})` : inst.instanceId,
|
|
515
515
|
status: inst.status,
|
|
516
516
|
currentTaskId: inst.currentTaskId,
|
|
517
517
|
currentRunId: inst.currentRunId,
|
|
@@ -223,6 +223,27 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
|
|
|
223
223
|
const t = (data.task ?? data) as Task;
|
|
224
224
|
setTask(t);
|
|
225
225
|
|
|
226
|
+
// Populate run history from task's runSummaries (RunStore may be empty for swarm tasks)
|
|
227
|
+
const rs = (t as unknown as Record<string, unknown>).context as Record<string, unknown> | undefined;
|
|
228
|
+
const summaries = rs?.runSummaries as Array<Record<string, unknown>> | undefined;
|
|
229
|
+
if (summaries?.length) {
|
|
230
|
+
setHistory(prev => {
|
|
231
|
+
if (prev.length > 0) return prev; // Don't overwrite if RunStore data exists
|
|
232
|
+
return summaries.map((s) => ({
|
|
233
|
+
id: s.runId as string,
|
|
234
|
+
runId: s.runId as string,
|
|
235
|
+
outcome: s.outcome as string,
|
|
236
|
+
summary: s.summary as string,
|
|
237
|
+
report: s.report as string | undefined,
|
|
238
|
+
filesModified: s.filesModified as string[],
|
|
239
|
+
durationMs: s.durationMs as number,
|
|
240
|
+
cost: s.cost as number,
|
|
241
|
+
tokensUsed: s.tokensUsed as number,
|
|
242
|
+
error: s.error as string | undefined,
|
|
243
|
+
} as unknown as HistoricalRun));
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
226
247
|
// Subtasks from response or fetch separately
|
|
227
248
|
if (data.subtasks && Array.isArray(data.subtasks)) {
|
|
228
249
|
setSubtasks(data.subtasks as Subtask[]);
|
|
@@ -242,13 +263,14 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
|
|
|
242
263
|
try {
|
|
243
264
|
const raw = await callTool('fw_weaver_history', { taskId });
|
|
244
265
|
const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
245
|
-
if (Array.isArray(data)) {
|
|
266
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
246
267
|
setHistory(
|
|
247
268
|
data.map((r: Record<string, unknown>) => {
|
|
248
269
|
const costObj = r.cost && typeof r.cost === 'object' ? (r.cost as Record<string, unknown>) : undefined;
|
|
249
270
|
return { ...r, costDetail: costObj, cost: costObj?.totalCost } as unknown as HistoricalRun;
|
|
250
271
|
}),
|
|
251
272
|
);
|
|
273
|
+
return;
|
|
252
274
|
}
|
|
253
275
|
} catch {
|
|
254
276
|
// Non-fatal
|
|
@@ -261,6 +283,7 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
|
|
|
261
283
|
Promise.all([fetchTask(), fetchHistory()]).finally(() => setLoading(false));
|
|
262
284
|
}, [fetchTask, fetchHistory]);
|
|
263
285
|
|
|
286
|
+
|
|
264
287
|
// Poll while in-progress (every 5s)
|
|
265
288
|
useEffect(() => {
|
|
266
289
|
if (!task || task.status !== 'in-progress') return;
|
|
@@ -673,7 +696,7 @@ function TaskDetailView({ taskId, onBack, onEdit }: TaskDetailViewProps) {
|
|
|
673
696
|
React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8' },
|
|
674
697
|
React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-medium' }, 'Status'),
|
|
675
698
|
React.createElement(Flex, { variant: 'row-center-start-nowrap-6' },
|
|
676
|
-
task.status === 'failed' && React.createElement(Button, {
|
|
699
|
+
(task.status === 'failed' || (task.status === 'open' && task.attempt >= task.maxAttempts)) && React.createElement(Button, {
|
|
677
700
|
size: 'xs', variant: 'fill', color: 'primary',
|
|
678
701
|
onClick: handleRetry,
|
|
679
702
|
loading: actionLoading === 'retry',
|
|
@@ -465,10 +465,10 @@ class GeneratedExecutionContext {
|
|
|
465
465
|
* @connect readWf.onSuccess -> report.execute
|
|
466
466
|
* @connect abort.onSuccess -> report.execute
|
|
467
467
|
* @connect notify.onSuccess -> report.execute
|
|
468
|
-
* @connect
|
|
468
|
+
* @connect mergePlan.ctx -> report.mainCtx
|
|
469
469
|
* @connect readWf.ctx -> report.readCtx
|
|
470
470
|
* @connect abort.ctx -> report.abortCtx
|
|
471
|
-
* @connect
|
|
471
|
+
* @connect plan.ctx -> report.failCtx
|
|
472
472
|
* @connect report.summary -> Exit.summary
|
|
473
473
|
* @param execute [order:-1] - Execute
|
|
474
474
|
* @param taskJson [order:0] - TaskJson
|
|
@@ -2118,16 +2118,17 @@ export async function weaverBot(
|
|
|
2118
2118
|
try {
|
|
2119
2119
|
const report_execute = (readWfIdx !== undefined ? await ctx.getVariable({ id: 'readWf', portName: 'onSuccess', executionIndex: readWfIdx }) as boolean : false) || (abortIdx !== undefined ? await ctx.getVariable({ id: 'abort', portName: 'onSuccess', executionIndex: abortIdx }) as boolean : false) || (notifyIdx !== undefined ? await ctx.getVariable({ id: 'notify', portName: 'onSuccess', executionIndex: notifyIdx }) as boolean : false) || (gitOpsIdx !== undefined ? await ctx.getVariable({ id: 'gitOps', portName: 'onSuccess', executionIndex: gitOpsIdx }) as boolean : false) || (receiveIdx !== undefined ? await ctx.getVariable({ id: 'receive', portName: 'onFailure', executionIndex: receiveIdx }) as boolean : false) || (planIdx !== undefined ? await ctx.getVariable({ id: 'plan', portName: 'onFailure', executionIndex: planIdx }) as boolean : false) || (execRetryIdx !== undefined ? await ctx.getVariable({ id: 'execRetry', portName: 'onFailure', executionIndex: execRetryIdx }) as boolean : false);
|
|
2120
2120
|
await ctx.setVariable({ id: 'report', portName: 'execute', executionIndex: reportIdx, nodeTypeName: 'weaverBotReport' }, report_execute);
|
|
2121
|
-
const report_mainCtx =
|
|
2121
|
+
const report_mainCtx = mergePlanIdx !== undefined ? await ctx.getVariable({ id: 'mergePlan', portName: 'ctx', executionIndex: mergePlanIdx }) as string : undefined;
|
|
2122
2122
|
await ctx.setVariable({ id: 'report', portName: 'mainCtx', executionIndex: reportIdx, nodeTypeName: 'weaverBotReport' }, report_mainCtx);
|
|
2123
2123
|
const report_readCtx = readWfIdx !== undefined ? await ctx.getVariable({ id: 'readWf', portName: 'ctx', executionIndex: readWfIdx }) as string : undefined;
|
|
2124
2124
|
await ctx.setVariable({ id: 'report', portName: 'readCtx', executionIndex: reportIdx, nodeTypeName: 'weaverBotReport' }, report_readCtx);
|
|
2125
2125
|
const report_abortCtx = abortIdx !== undefined ? await ctx.getVariable({ id: 'abort', portName: 'ctx', executionIndex: abortIdx }) as string : undefined;
|
|
2126
2126
|
await ctx.setVariable({ id: 'report', portName: 'abortCtx', executionIndex: reportIdx, nodeTypeName: 'weaverBotReport' }, report_abortCtx);
|
|
2127
|
-
const report_failCtx =
|
|
2127
|
+
const report_failCtx = planIdx !== undefined ? await ctx.getVariable({ id: 'plan', portName: 'ctx', executionIndex: planIdx }) as string : undefined;
|
|
2128
2128
|
await ctx.setVariable({ id: 'report', portName: 'failCtx', executionIndex: reportIdx, nodeTypeName: 'weaverBotReport' }, report_failCtx);
|
|
2129
2129
|
const reportResult = await weaverBotReport(report_execute, report_mainCtx, report_readCtx, report_abortCtx, report_failCtx);
|
|
2130
2130
|
await ctx.setVariable({ id: 'report', portName: 'summary', executionIndex: reportIdx, nodeTypeName: 'weaverBotReport' }, reportResult.summary);
|
|
2131
|
+
await ctx.setVariable({ id: 'report', portName: 'report', executionIndex: reportIdx, nodeTypeName: 'weaverBotReport' }, reportResult.report);
|
|
2131
2132
|
await ctx.setVariable({ id: 'report', portName: 'reportJson', executionIndex: reportIdx, nodeTypeName: 'weaverBotReport' }, reportResult.reportJson);
|
|
2132
2133
|
await ctx.setVariable({ id: 'report', portName: 'onFailure', executionIndex: reportIdx, nodeTypeName: 'weaverBotReport' }, reportResult.onFailure);
|
|
2133
2134
|
await ctx.setVariable({ id: 'report', portName: 'onSuccess', executionIndex: reportIdx, nodeTypeName: 'weaverBotReport' }, reportResult.onSuccess);
|