@synergenius/flow-weaver-pack-weaver 0.9.152 → 0.9.154
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 +46 -33
- 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 +28 -9
- 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/system-prompt.d.ts.map +1 -1
- package/dist/bot/system-prompt.js +2 -0
- package/dist/bot/system-prompt.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 +12 -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.d.ts.map +1 -1
- package/dist/node-types/build-context.js +13 -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 +46 -33
- package/dist/ui/chat-task-result.js +7 -7
- package/dist/ui/profile-editor.js +44 -31
- package/dist/ui/swarm-dashboard.js +80 -47
- package/dist/ui/task-detail-view.js +31 -11
- package/dist/ui/task-editor.js +1 -1
- package/dist/ui/task-pool-list.js +1 -1
- package/dist/workflows/weaver-bot.d.ts +5 -4
- package/dist/workflows/weaver-bot.d.ts.map +1 -1
- package/dist/workflows/weaver-bot.js +8 -7
- 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 +46 -33
- 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 +30 -9
- package/src/bot/swarm-controller.ts +65 -76
- package/src/bot/system-prompt.ts +2 -0
- package/src/bot/task-types.ts +1 -0
- package/src/bot/weaver-tools.ts +14 -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 +13 -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/chat-task-result.tsx +10 -8
- package/src/ui/swarm-dashboard.tsx +4 -4
- package/src/ui/task-detail-view.tsx +35 -12
- package/src/ui/task-editor.tsx +2 -2
- package/src/ui/task-pool-list.tsx +2 -2
- package/src/workflows/weaver-bot.ts +8 -7
|
@@ -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',
|
|
@@ -18,16 +18,18 @@ const { Flex, Typography, StatusIcon, Button } = require('@fw/plugin-ui-kit');
|
|
|
18
18
|
interface TaskData {
|
|
19
19
|
id: string;
|
|
20
20
|
title: string;
|
|
21
|
-
status: 'pending' | 'in-progress' | 'blocked' | 'done' | '
|
|
21
|
+
status: 'open' | 'pending' | 'in-progress' | 'blocked' | 'done' | 'cancelled';
|
|
22
22
|
isParent: boolean;
|
|
23
23
|
currentBotId?: string;
|
|
24
24
|
assignedProfile?: string;
|
|
25
|
+
attempt?: number;
|
|
26
|
+
maxAttempts?: number;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
interface SubtaskData {
|
|
28
30
|
id: string;
|
|
29
31
|
title: string;
|
|
30
|
-
status: 'pending' | 'in-progress' | 'blocked' | 'done' | '
|
|
32
|
+
status: 'open' | 'pending' | 'in-progress' | 'blocked' | 'done' | 'cancelled';
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
interface ChatTaskResultProps {
|
|
@@ -59,6 +61,7 @@ type StatusKind = 'running' | 'completed' | 'failed' | 'pending';
|
|
|
59
61
|
|
|
60
62
|
function statusToIcon(status: TaskData['status']): StatusKind {
|
|
61
63
|
switch (status) {
|
|
64
|
+
case 'open':
|
|
62
65
|
case 'pending':
|
|
63
66
|
case 'blocked':
|
|
64
67
|
return 'pending';
|
|
@@ -66,7 +69,6 @@ function statusToIcon(status: TaskData['status']): StatusKind {
|
|
|
66
69
|
return 'running';
|
|
67
70
|
case 'done':
|
|
68
71
|
return 'completed';
|
|
69
|
-
case 'failed':
|
|
70
72
|
case 'cancelled':
|
|
71
73
|
return 'failed';
|
|
72
74
|
default:
|
|
@@ -76,18 +78,18 @@ function statusToIcon(status: TaskData['status']): StatusKind {
|
|
|
76
78
|
|
|
77
79
|
function statusLabel(status: TaskData['status']): string {
|
|
78
80
|
switch (status) {
|
|
79
|
-
case '
|
|
80
|
-
case '
|
|
81
|
+
case 'open': return 'Open';
|
|
82
|
+
case 'pending': return 'Queued';
|
|
83
|
+
case 'in-progress': return 'Running';
|
|
81
84
|
case 'blocked': return 'Blocked';
|
|
82
85
|
case 'done': return 'Done';
|
|
83
|
-
case 'failed': return 'Failed';
|
|
84
86
|
case 'cancelled': return 'Cancelled';
|
|
85
87
|
default: return status;
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
function isTerminal(status: TaskData['status']): boolean {
|
|
90
|
-
return status === 'done' || status === '
|
|
92
|
+
return status === 'done' || status === 'cancelled';
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
// ---------------------------------------------------------------------------
|
|
@@ -204,7 +206,7 @@ function ChatTaskResult(props: ChatTaskResultProps | null) {
|
|
|
204
206
|
React.createElement(Typography, {
|
|
205
207
|
variant: 'smallCaption-regular',
|
|
206
208
|
color: task.status === 'done' ? 'color-text-positive' :
|
|
207
|
-
task.status === '
|
|
209
|
+
task.status === 'cancelled' ? 'color-text-negative' :
|
|
208
210
|
task.status === 'in-progress' ? 'color-text-info' :
|
|
209
211
|
'color-text-medium',
|
|
210
212
|
}, statusLabel(task.status)),
|
|
@@ -77,7 +77,7 @@ interface SwarmStatus {
|
|
|
77
77
|
packVersion?: string;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
type TaskStatus = 'pending' | 'in-progress' | 'blocked' | 'done' | '
|
|
80
|
+
type TaskStatus = 'open' | 'pending' | 'in-progress' | 'blocked' | 'done' | 'cancelled';
|
|
81
81
|
|
|
82
82
|
interface PoolTask {
|
|
83
83
|
id: string;
|
|
@@ -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,
|