@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.
Files changed (104) hide show
  1. package/dist/ai-chat-provider.js +4 -4
  2. package/dist/ai-chat-provider.js.map +1 -1
  3. package/dist/bot/ai-client.d.ts +30 -0
  4. package/dist/bot/ai-client.d.ts.map +1 -1
  5. package/dist/bot/ai-client.js +37 -0
  6. package/dist/bot/ai-client.js.map +1 -1
  7. package/dist/bot/behavior-defaults.d.ts.map +1 -1
  8. package/dist/bot/behavior-defaults.js +7 -2
  9. package/dist/bot/behavior-defaults.js.map +1 -1
  10. package/dist/bot/capability-registry.d.ts.map +1 -1
  11. package/dist/bot/capability-registry.js +46 -33
  12. package/dist/bot/capability-registry.js.map +1 -1
  13. package/dist/bot/file-validator.d.ts +7 -0
  14. package/dist/bot/file-validator.d.ts.map +1 -1
  15. package/dist/bot/file-validator.js +76 -0
  16. package/dist/bot/file-validator.js.map +1 -1
  17. package/dist/bot/instance-manager.d.ts +22 -7
  18. package/dist/bot/instance-manager.d.ts.map +1 -1
  19. package/dist/bot/instance-manager.js +69 -7
  20. package/dist/bot/instance-manager.js.map +1 -1
  21. package/dist/bot/orchestrator.d.ts +11 -9
  22. package/dist/bot/orchestrator.d.ts.map +1 -1
  23. package/dist/bot/orchestrator.js +56 -107
  24. package/dist/bot/orchestrator.js.map +1 -1
  25. package/dist/bot/runner.d.ts +29 -0
  26. package/dist/bot/runner.d.ts.map +1 -1
  27. package/dist/bot/runner.js +114 -73
  28. package/dist/bot/runner.js.map +1 -1
  29. package/dist/bot/step-executor.d.ts.map +1 -1
  30. package/dist/bot/step-executor.js +28 -9
  31. package/dist/bot/step-executor.js.map +1 -1
  32. package/dist/bot/swarm-controller.d.ts +7 -6
  33. package/dist/bot/swarm-controller.d.ts.map +1 -1
  34. package/dist/bot/swarm-controller.js +64 -74
  35. package/dist/bot/swarm-controller.js.map +1 -1
  36. package/dist/bot/system-prompt.d.ts.map +1 -1
  37. package/dist/bot/system-prompt.js +2 -0
  38. package/dist/bot/system-prompt.js.map +1 -1
  39. package/dist/bot/task-types.d.ts +1 -0
  40. package/dist/bot/task-types.d.ts.map +1 -1
  41. package/dist/bot/weaver-tools.d.ts +1 -1
  42. package/dist/bot/weaver-tools.d.ts.map +1 -1
  43. package/dist/bot/weaver-tools.js +12 -1
  44. package/dist/bot/weaver-tools.js.map +1 -1
  45. package/dist/node-types/agent-execute.js +2 -2
  46. package/dist/node-types/agent-execute.js.map +1 -1
  47. package/dist/node-types/bot-report.d.ts.map +1 -1
  48. package/dist/node-types/bot-report.js +5 -2
  49. package/dist/node-types/bot-report.js.map +1 -1
  50. package/dist/node-types/build-context.d.ts.map +1 -1
  51. package/dist/node-types/build-context.js +13 -1
  52. package/dist/node-types/build-context.js.map +1 -1
  53. package/dist/node-types/exec-validate-retry.d.ts +3 -3
  54. package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
  55. package/dist/node-types/exec-validate-retry.js +13 -184
  56. package/dist/node-types/exec-validate-retry.js.map +1 -1
  57. package/dist/node-types/load-config.d.ts +1 -0
  58. package/dist/node-types/load-config.d.ts.map +1 -1
  59. package/dist/node-types/load-config.js +1 -0
  60. package/dist/node-types/load-config.js.map +1 -1
  61. package/dist/node-types/plan-task.d.ts +7 -5
  62. package/dist/node-types/plan-task.d.ts.map +1 -1
  63. package/dist/node-types/plan-task.js +282 -83
  64. package/dist/node-types/plan-task.js.map +1 -1
  65. package/dist/ui/bot-panel.js +1 -1
  66. package/dist/ui/capability-editor.js +46 -33
  67. package/dist/ui/chat-task-result.js +7 -7
  68. package/dist/ui/profile-editor.js +44 -31
  69. package/dist/ui/swarm-dashboard.js +80 -47
  70. package/dist/ui/task-detail-view.js +31 -11
  71. package/dist/ui/task-editor.js +1 -1
  72. package/dist/ui/task-pool-list.js +1 -1
  73. package/dist/workflows/weaver-bot.d.ts +5 -4
  74. package/dist/workflows/weaver-bot.d.ts.map +1 -1
  75. package/dist/workflows/weaver-bot.js +8 -7
  76. package/dist/workflows/weaver-bot.js.map +1 -1
  77. package/flowweaver.manifest.json +1 -1
  78. package/package.json +1 -1
  79. package/src/ai-chat-provider.ts +4 -4
  80. package/src/bot/ai-client.ts +65 -0
  81. package/src/bot/behavior-defaults.ts +5 -2
  82. package/src/bot/capability-registry.ts +46 -33
  83. package/src/bot/file-validator.ts +97 -0
  84. package/src/bot/instance-manager.ts +77 -7
  85. package/src/bot/orchestrator.ts +63 -126
  86. package/src/bot/runner.ts +124 -70
  87. package/src/bot/step-executor.ts +30 -9
  88. package/src/bot/swarm-controller.ts +65 -76
  89. package/src/bot/system-prompt.ts +2 -0
  90. package/src/bot/task-types.ts +1 -0
  91. package/src/bot/weaver-tools.ts +14 -1
  92. package/src/node-types/agent-execute.ts +2 -2
  93. package/src/node-types/bot-report.ts +5 -2
  94. package/src/node-types/build-context.ts +13 -1
  95. package/src/node-types/exec-validate-retry.ts +14 -203
  96. package/src/node-types/load-config.ts +1 -0
  97. package/src/node-types/plan-task.ts +313 -88
  98. package/src/ui/bot-panel.tsx +1 -1
  99. package/src/ui/chat-task-result.tsx +10 -8
  100. package/src/ui/swarm-dashboard.tsx +4 -4
  101. package/src/ui/task-detail-view.tsx +35 -12
  102. package/src/ui/task-editor.tsx +2 -2
  103. package/src/ui/task-pool-list.tsx +2 -2
  104. package/src/workflows/weaver-bot.ts +8 -7
@@ -1,61 +1,135 @@
1
- import type { WeaverContext } from '../bot/types.js';
2
- import { callAIWithTools, callCapabilityTriage, parseJsonResponse, normalizePlan } from '../bot/ai-client.js';
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 { PLAN_OPERATIONS } from '../bot/operations.js';
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
- // Plan tool definition passed via native tool_use so the AI returns
10
- // structured JSON arguments instead of prose. Works across Anthropic and OpenAI.
9
+ // Tool definitionsbuilt dynamically from capability-granted operations
11
10
  // ---------------------------------------------------------------------------
12
11
 
13
- /** Build the create_plan tool definition with dynamic operation list. */
14
- function buildPlanTool(operations: string[]): AiTool {
15
- const ops = operations.length > 0 ? operations : PLAN_OPERATIONS as unknown as string[];
16
- return {
17
- name: 'create_plan',
18
- description: 'Create a structured execution plan for this task. Call this tool with the plan steps and summary.',
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
- summary: { type: 'string', description: 'One-line summary of what the plan does' },
23
- steps: {
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: ['summary', 'steps'],
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
- * Sends task + context to the AI provider and gets back a structured
50
- * execution plan. The core AI planning node.
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 Plan Task
54
- * @icon task
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 planJson (JSON)
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: 'Planning failed: missing taskJson' });
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; // fallback: all available
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('[plan-task] system prompt build failed:', err);
113
- systemPrompt = 'You are Weaver, an AI workflow bot. Return ONLY valid JSON with a plan.';
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
- const userPrompt = `Task: ${task.instruction}\nMode: ${task.mode ?? 'create'}\n${task.id ? `Your Task ID is ${task.id} — use this as parentId in every task_create call.` : ''}\n${task.targets ? 'Targets: ' + task.targets.join(', ') : ''}
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
- Plan this task by calling the create_plan tool with concrete steps.
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. Every step MUST have complete, concrete args no empty patches, no placeholders.
121
- 2. For file creation tasks, use write_file with full file content in args.
122
- 3. If you need to discover file contents first, plan ONLY discovery steps (read_file, list_files).
123
- 4. Do NOT plan patch_file unless you know the exact find/replace strings.`;
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
- // Build plan tool with operations scoped to selected capabilities
127
- const mod2 = await import('../bot/system-prompt.js');
128
- const grantedTools = mod2.collectToolsFromCapabilities(selectedCaps);
129
- // Always include respond in the available operations
130
- const planOps = grantedTools.length > 0 ? [...grantedTools, 'respond'] : PLAN_OPERATIONS as unknown as string[];
131
- const planTool = buildPlanTool(planOps);
132
-
133
- // Use native tool_use when available (Anthropic, OpenAI).
134
- // The AI calls create_plan with structured args instead of returning prose.
135
- const result = await callAIWithTools(pInfo, systemPrompt, userPrompt, [planTool], 8192);
136
-
137
- let plan: { steps: Array<{ id: string; operation: string; description: string; args: Record<string, unknown> }>; summary: string };
138
-
139
- // Check if AI used the create_plan tool (structured response)
140
- const planCall = result.toolCalls.find(tc => tc.name === 'create_plan');
141
- if (planCall) {
142
- plan = normalizePlan(planCall.arguments);
143
- } else {
144
- // Fallback: parse JSON from text content (CLI providers, or AI chose not to use tool)
145
- const text = result.content;
146
- try {
147
- const raw = parseJsonResponse(text);
148
- plan = normalizePlan(raw);
149
- } catch {
150
- // LLM returned plain text — wrap as a text-response plan.
151
- const trimmed = text.trim();
152
- const firstLine = trimmed.split('\n')[0].slice(0, 120);
153
- plan = {
154
- steps: [{ id: 'step-1', operation: 'respond', description: firstLine, args: { response: trimmed } }],
155
- summary: firstLine,
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
- context.planJson = JSON.stringify(plan);
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→ Planning failed: ${msg}\x1b[0m`);
167
- context.planJson = JSON.stringify({ steps: [], summary: `Planning failed: ${msg}` });
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
  }
@@ -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.profileId} #${inst.index}`),
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' | 'failed' | 'cancelled';
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' | 'failed' | 'cancelled';
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 'pending': return 'Pending';
80
- case 'in-progress': return 'In Progress';
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 === 'failed' || status === 'cancelled';
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 === 'failed' ? 'color-text-negative' :
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' | 'failed' | 'cancelled';
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 ? `Instances (${swarmInstanceEntries.length})` : `Bots (${registeredBots.length})` },
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
- }, 'Instance'),
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.profileId} #${inst.index}`,
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,