@synergenius/flow-weaver-pack-weaver 0.9.138 → 0.9.142
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/bot/assistant-tools.d.ts.map +1 -1
- package/dist/bot/assistant-tools.js +5 -11
- package/dist/bot/assistant-tools.js.map +1 -1
- package/dist/bot/capability-registry.d.ts.map +1 -1
- package/dist/bot/capability-registry.js +185 -15
- package/dist/bot/capability-registry.js.map +1 -1
- package/dist/bot/dashboard.js +3 -3
- package/dist/bot/dashboard.js.map +1 -1
- package/dist/bot/hierarchy-event-log.d.ts +37 -0
- package/dist/bot/hierarchy-event-log.d.ts.map +1 -0
- package/dist/bot/hierarchy-event-log.js +58 -0
- package/dist/bot/hierarchy-event-log.js.map +1 -0
- package/dist/bot/operations.d.ts +2 -0
- package/dist/bot/operations.d.ts.map +1 -1
- package/dist/bot/operations.js +5 -0
- package/dist/bot/operations.js.map +1 -1
- package/dist/bot/profile-store.d.ts.map +1 -1
- package/dist/bot/profile-store.js +39 -0
- package/dist/bot/profile-store.js.map +1 -1
- package/dist/bot/runner.d.ts.map +1 -1
- package/dist/bot/runner.js +7 -2
- package/dist/bot/runner.js.map +1 -1
- package/dist/bot/step-executor.d.ts.map +1 -1
- package/dist/bot/step-executor.js +33 -1
- package/dist/bot/step-executor.js.map +1 -1
- package/dist/bot/swarm-controller.d.ts +1 -0
- package/dist/bot/swarm-controller.d.ts.map +1 -1
- package/dist/bot/swarm-controller.js +59 -5
- package/dist/bot/swarm-controller.js.map +1 -1
- package/dist/bot/task-store.d.ts +1 -1
- package/dist/bot/task-store.d.ts.map +1 -1
- package/dist/bot/task-store.js +25 -37
- package/dist/bot/task-store.js.map +1 -1
- package/dist/bot/task-types.d.ts +5 -1
- package/dist/bot/task-types.d.ts.map +1 -1
- package/dist/node-types/bot-report.d.ts +2 -1
- package/dist/node-types/bot-report.d.ts.map +1 -1
- package/dist/node-types/bot-report.js +9 -4
- 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 +32 -0
- package/dist/node-types/build-context.js.map +1 -1
- package/dist/node-types/plan-task.d.ts.map +1 -1
- package/dist/node-types/plan-task.js +5 -1
- package/dist/node-types/plan-task.js.map +1 -1
- package/dist/node-types/report.d.ts +1 -1
- package/dist/node-types/report.d.ts.map +1 -1
- package/dist/node-types/report.js +58 -8
- package/dist/node-types/report.js.map +1 -1
- package/dist/ui/capability-editor.js +184 -15
- package/dist/ui/profile-editor.js +184 -15
- package/dist/ui/swarm-dashboard.js +244 -44
- package/dist/ui/task-detail-view.js +60 -29
- package/dist/ui/use-stream-timeline.d.ts.map +1 -1
- package/dist/ui/use-stream-timeline.js +69 -29
- package/dist/ui/use-stream-timeline.js.map +1 -1
- package/dist/workflows/weaver-bot.d.ts +1 -0
- package/dist/workflows/weaver-bot.d.ts.map +1 -1
- package/dist/workflows/weaver-bot.js +239 -4
- package/dist/workflows/weaver-bot.js.map +1 -1
- package/flowweaver.manifest.json +1 -1
- package/package.json +1 -1
- package/src/bot/assistant-tools.ts +5 -11
- package/src/bot/capability-registry.ts +196 -18
- package/src/bot/dashboard.ts +3 -3
- package/src/bot/hierarchy-event-log.ts +64 -0
- package/src/bot/operations.ts +7 -0
- package/src/bot/profile-store.ts +39 -0
- package/src/bot/runner.ts +8 -4
- package/src/bot/step-executor.ts +29 -1
- package/src/bot/swarm-controller.ts +62 -5
- package/src/bot/task-store.ts +26 -39
- package/src/bot/task-types.ts +7 -1
- package/src/node-types/bot-report.ts +8 -3
- package/src/node-types/build-context.ts +32 -0
- package/src/node-types/plan-task.ts +5 -1
- package/src/node-types/report.ts +56 -8
- package/src/ui/use-stream-timeline.ts +73 -33
- package/src/workflows/weaver-bot.ts +398 -3
|
@@ -10,7 +10,7 @@ import type { CapabilityDefinition } from './capability-types.js';
|
|
|
10
10
|
import {
|
|
11
11
|
OP_WRITE_FILE, OP_READ_FILE, OP_PATCH_FILE, OP_LIST_FILES,
|
|
12
12
|
OP_RUN_SHELL, OP_VALIDATE, OP_TSC_CHECK, OP_RUN_TESTS,
|
|
13
|
-
OP_TASK_CREATE,
|
|
13
|
+
OP_TASK_CREATE, OP_REMEMBER, OP_RECALL,
|
|
14
14
|
} from './operations.js';
|
|
15
15
|
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
@@ -67,13 +67,31 @@ Use run_shell for running tests (npx vitest), validation (flow-weaver validate),
|
|
|
67
67
|
|
|
68
68
|
const CAP_TASK_MGMT: CapabilityDefinition = {
|
|
69
69
|
name: 'task-mgmt',
|
|
70
|
-
description: 'Create and manage swarm subtasks for parallel execution.',
|
|
70
|
+
description: 'Create and manage swarm subtasks for parallel execution, with decomposition and review nudges.',
|
|
71
71
|
tools: [OP_TASK_CREATE],
|
|
72
|
-
prompt: `## Task Management
|
|
73
|
-
|
|
74
|
-
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
prompt: `## Task Management & Decomposition
|
|
73
|
+
|
|
74
|
+
- task_create: Create swarm subtasks. args: { title, description, complexity, subtasks[], dependsOn[], assignedProfile? }
|
|
75
|
+
|
|
76
|
+
### Decomposition
|
|
77
|
+
When you encounter a broad objective (multi-file, multi-concern), decompose into subtasks:
|
|
78
|
+
- If the task is bigger than a single file change, create subtasks instead of doing it all yourself.
|
|
79
|
+
- Minimize dependencies between subtasks to maximize parallel execution.
|
|
80
|
+
- Set complexity per subtask: trivial | simple | moderate | complex.
|
|
81
|
+
- Use dependsOn to express blocking relationships (e.g., setup before code, code before tests).
|
|
82
|
+
|
|
83
|
+
### Review Task Creation
|
|
84
|
+
After creating or modifying multiple files, create a review task:
|
|
85
|
+
- title: "Review: [what was changed]"
|
|
86
|
+
- description: List the files modified and what to check
|
|
87
|
+
- assignedProfile: "reviewer"
|
|
88
|
+
- complexity: "simple"
|
|
89
|
+
Skip review for trivial single-file tasks.
|
|
90
|
+
|
|
91
|
+
### Dependency Guidelines
|
|
92
|
+
- BAD: A → B → C → D (serial, slow)
|
|
93
|
+
- GOOD: A → [B + C + D] (A blocks all, but B/C/D run in parallel)
|
|
94
|
+
Structure as: setup → independent implementations → integration/testing.`,
|
|
77
95
|
};
|
|
78
96
|
|
|
79
97
|
const CAP_FW_GRAMMAR: CapabilityDefinition = {
|
|
@@ -156,17 +174,41 @@ Note: compile, validate, modify, diff, diagram, and describe operations are avai
|
|
|
156
174
|
|
|
157
175
|
const CAP_CODE_REVIEW: CapabilityDefinition = {
|
|
158
176
|
name: 'code-review',
|
|
159
|
-
description: '
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
1. Correctness
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
177
|
+
description: 'Comprehensive code review with correctness, security, style, testing, and performance checks.',
|
|
178
|
+
tools: [OP_READ_FILE, OP_PATCH_FILE, OP_RUN_SHELL],
|
|
179
|
+
prompt: `## Code Review Checklist
|
|
180
|
+
|
|
181
|
+
### 1. Correctness
|
|
182
|
+
- Does the code do what the task asked?
|
|
183
|
+
- Edge cases handled (empty input, null, invalid types)?
|
|
184
|
+
- Error paths covered (try/catch, validation)?
|
|
185
|
+
- Return types match function signature?
|
|
186
|
+
|
|
187
|
+
### 2. Security
|
|
188
|
+
- NO hardcoded API keys, passwords, or tokens (use env vars)
|
|
189
|
+
- NO shell: true in child_process (command injection risk)
|
|
190
|
+
- NO eval() or Function() with untrusted input
|
|
191
|
+
- User input validated and sanitized before use
|
|
192
|
+
- File paths validated (no ../ traversal)
|
|
193
|
+
|
|
194
|
+
### 3. Style
|
|
195
|
+
- Naming is clear and consistent with project conventions
|
|
196
|
+
- No dead code (unused variables, unreachable branches)
|
|
197
|
+
- No debug statements left in (console.log, debugger)
|
|
198
|
+
- Imports organized, no duplicates
|
|
199
|
+
|
|
200
|
+
### 4. Testing
|
|
201
|
+
- Unit tests exist for new/changed functions
|
|
202
|
+
- Tests cover happy path AND edge cases
|
|
203
|
+
- Error cases have tests
|
|
204
|
+
- Code coverage adequate (aim for 80%+ of changed code)
|
|
205
|
+
|
|
206
|
+
### 5. Performance
|
|
207
|
+
- No O(n²) loops where O(n) is possible
|
|
208
|
+
- No blocking I/O in async code
|
|
209
|
+
- No memory leaks (listeners removed, timers cleared)
|
|
210
|
+
|
|
211
|
+
Report findings as: FILE:LINE | SEVERITY (critical/high/medium/low) | ISSUE → Fix suggestion`,
|
|
170
212
|
};
|
|
171
213
|
|
|
172
214
|
const CAP_WEB: CapabilityDefinition = {
|
|
@@ -186,6 +228,134 @@ Use list_files to understand the project structure before making changes.
|
|
|
186
228
|
The context bundle (when available) provides a snapshot of the workspace.`,
|
|
187
229
|
};
|
|
188
230
|
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// New capabilities — swarm improvements
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
const CAP_VERIFICATION: CapabilityDefinition = {
|
|
236
|
+
name: 'verification',
|
|
237
|
+
description: 'Post-write verification: run tsc and tests to catch errors before delivery.',
|
|
238
|
+
tools: [OP_RUN_SHELL],
|
|
239
|
+
prompt: `## Verification
|
|
240
|
+
|
|
241
|
+
After writing or patching code, ALWAYS verify your work:
|
|
242
|
+
1. Run \`npx tsc --noEmit\` in the project root to catch TypeScript errors
|
|
243
|
+
2. If package.json has a "test" script, run \`npm test\` to validate functionality
|
|
244
|
+
3. If verification fails, read the errors, fix the code, and re-verify
|
|
245
|
+
|
|
246
|
+
Include verification as explicit steps in your plan. Verification is NOT optional.
|
|
247
|
+
Do NOT deliver code that hasn't been verified.`,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const CAP_CROSS_FILE_CHECK: CapabilityDefinition = {
|
|
251
|
+
name: 'cross-file-check',
|
|
252
|
+
description: 'Verify imports, exports, module paths, and cross-file dependencies.',
|
|
253
|
+
tools: [OP_READ_FILE, OP_LIST_FILES, OP_RUN_SHELL],
|
|
254
|
+
prompt: `## Cross-File Dependency Checks
|
|
255
|
+
|
|
256
|
+
When modifying code that affects multiple files:
|
|
257
|
+
1. If you rename an export, grep for all imports of it and update them
|
|
258
|
+
2. Verify relative import paths resolve correctly (../types vs ./types)
|
|
259
|
+
3. Check for circular dependencies (A imports B imports A)
|
|
260
|
+
4. If you change a function signature, update all callers
|
|
261
|
+
5. Use \`run_shell\` with grep to search: grep -r "functionName" src/
|
|
262
|
+
|
|
263
|
+
Do NOT move or rename exports without verifying all dependents.`,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const CAP_PROJECT_SETUP: CapabilityDefinition = {
|
|
267
|
+
name: 'project-setup',
|
|
268
|
+
description: 'Initialize new projects with correct structure, config, and dependencies.',
|
|
269
|
+
tools: [OP_WRITE_FILE, OP_RUN_SHELL],
|
|
270
|
+
prompt: `## Project Setup
|
|
271
|
+
|
|
272
|
+
When initializing a project:
|
|
273
|
+
1. Create package.json with name, type: "module", main, scripts (build, test)
|
|
274
|
+
2. Create tsconfig.json with strict: true, module: "esnext", target: "ES2020"
|
|
275
|
+
3. Create standard directories: src/, tests/
|
|
276
|
+
4. Install dependencies with run_shell: npm install <deps>
|
|
277
|
+
5. Create .gitignore excluding node_modules/, dist/
|
|
278
|
+
6. Verify setup: run tsc --noEmit to ensure TypeScript compiles`,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const CAP_SECURITY: CapabilityDefinition = {
|
|
282
|
+
name: 'security',
|
|
283
|
+
description: 'Audit code for vulnerabilities, secrets, and security best practices.',
|
|
284
|
+
tools: [OP_READ_FILE, OP_LIST_FILES, OP_RUN_SHELL],
|
|
285
|
+
prompt: `## Security Audit
|
|
286
|
+
|
|
287
|
+
Check for:
|
|
288
|
+
1. **Secrets**: NO hardcoded API keys, passwords, tokens. Use env vars.
|
|
289
|
+
grep -r "password\\|secret\\|apiKey\\|token" src/ to find leaks.
|
|
290
|
+
2. **Injection**: NO string concatenation in SQL. NO shell: true in child_process. NO eval().
|
|
291
|
+
3. **Dependencies**: Run npm audit to check for known CVEs.
|
|
292
|
+
4. **File paths**: Validate paths to prevent ../ traversal attacks.
|
|
293
|
+
5. **Data handling**: Validate user input (type, length, format). Sanitize before logging.
|
|
294
|
+
|
|
295
|
+
Report findings with severity: critical | high | medium | low.`,
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const CAP_DECOMPOSITION: CapabilityDefinition = {
|
|
299
|
+
name: 'decomposition',
|
|
300
|
+
description: 'Break complex objectives into subtask DAGs with dependencies for parallel execution.',
|
|
301
|
+
tools: [OP_TASK_CREATE],
|
|
302
|
+
prompt: `## Task Decomposition
|
|
303
|
+
|
|
304
|
+
When given a large objective, break it into smaller subtasks:
|
|
305
|
+
1. Identify all work items (files, features, tests)
|
|
306
|
+
2. Group by dependency: what must happen first?
|
|
307
|
+
3. Create subtasks with task_create, each focused on one responsibility
|
|
308
|
+
4. Set dependencies with dependsOn to model blocking relationships
|
|
309
|
+
5. Minimize dependencies to maximize parallel execution
|
|
310
|
+
6. Estimate complexity per subtask: trivial | simple | moderate | complex
|
|
311
|
+
|
|
312
|
+
Example: "Implement auth module"
|
|
313
|
+
- Task A: Extract shared auth types (simple)
|
|
314
|
+
- Task B: Rewrite login endpoint (moderate, depends on A)
|
|
315
|
+
- Task C: Add login tests (moderate, depends on B)
|
|
316
|
+
- Task D: Update auth docs (simple, independent — runs in parallel with B)
|
|
317
|
+
|
|
318
|
+
Assign profiles: code tasks → developer, review tasks → reviewer, infra → ops.`,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const CAP_ROUTING: CapabilityDefinition = {
|
|
322
|
+
name: 'routing',
|
|
323
|
+
description: 'Route tasks to appropriate bot profiles based on capabilities and complexity.',
|
|
324
|
+
tools: [OP_TASK_CREATE],
|
|
325
|
+
prompt: `## Task Routing
|
|
326
|
+
|
|
327
|
+
When creating subtasks, assign the right profile:
|
|
328
|
+
- Code writing, file creation, bug fixes → developer profile
|
|
329
|
+
- Code review, quality checks → reviewer profile
|
|
330
|
+
- Shell commands, project setup, infrastructure → ops profile
|
|
331
|
+
- Leave assignedProfile empty for auto-triage when unsure
|
|
332
|
+
|
|
333
|
+
Match complexity to profile capabilities:
|
|
334
|
+
- trivial/simple tasks: any profile (prefer cheapest)
|
|
335
|
+
- moderate tasks: specialist profiles
|
|
336
|
+
- complex tasks: profiles with full capability sets`,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const CAP_MEMORY: CapabilityDefinition = {
|
|
340
|
+
name: 'memory',
|
|
341
|
+
description: 'Remember and recall project conventions for continuity across sessions.',
|
|
342
|
+
tools: [OP_REMEMBER, OP_RECALL],
|
|
343
|
+
prompt: `## Project Memory
|
|
344
|
+
|
|
345
|
+
Persist project conventions for future sessions:
|
|
346
|
+
- remember: Save a convention. args: { key: "naming", value: "kebab-case for files" }
|
|
347
|
+
- recall: Load all saved conventions. args: {} — returns project memory.
|
|
348
|
+
|
|
349
|
+
What to remember:
|
|
350
|
+
- Naming conventions (file names, variable names)
|
|
351
|
+
- Architecture decisions (Result pattern, Zod for validation)
|
|
352
|
+
- Test patterns (where tests go, what framework)
|
|
353
|
+
- Common dependencies and their usage
|
|
354
|
+
|
|
355
|
+
Before planning, recall project memory to follow established patterns.
|
|
356
|
+
When you discover a new convention, remember it for future bots.`,
|
|
357
|
+
};
|
|
358
|
+
|
|
189
359
|
// ---------------------------------------------------------------------------
|
|
190
360
|
// Registry
|
|
191
361
|
// ---------------------------------------------------------------------------
|
|
@@ -203,6 +373,14 @@ export const BUILT_IN_CAPABILITIES: readonly CapabilityDefinition[] = [
|
|
|
203
373
|
CAP_CODE_REVIEW,
|
|
204
374
|
CAP_WEB,
|
|
205
375
|
CAP_CONTEXT,
|
|
376
|
+
// Swarm improvement capabilities
|
|
377
|
+
CAP_VERIFICATION,
|
|
378
|
+
CAP_CROSS_FILE_CHECK,
|
|
379
|
+
CAP_PROJECT_SETUP,
|
|
380
|
+
CAP_SECURITY,
|
|
381
|
+
CAP_DECOMPOSITION,
|
|
382
|
+
CAP_ROUTING,
|
|
383
|
+
CAP_MEMORY,
|
|
206
384
|
];
|
|
207
385
|
|
|
208
386
|
const capabilityMap = new Map<string, CapabilityDefinition>(
|
package/src/bot/dashboard.ts
CHANGED
|
@@ -326,12 +326,12 @@ export class DashboardServer {
|
|
|
326
326
|
const pending = tasks.filter(t => t.status === 'pending').length;
|
|
327
327
|
const inProgress = tasks.filter(t => t.status === 'in-progress').length;
|
|
328
328
|
const done = tasks.filter(t => t.status === 'done').length;
|
|
329
|
-
const
|
|
329
|
+
const cancelled = tasks.filter(t => t.status === 'cancelled').length;
|
|
330
330
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
331
|
-
res.end(JSON.stringify({ status: inProgress > 0 ? 'executing' : 'idle', pending, inProgress, done,
|
|
331
|
+
res.end(JSON.stringify({ status: inProgress > 0 ? 'executing' : 'idle', pending, inProgress, done, cancelled }));
|
|
332
332
|
}).catch(() => {
|
|
333
333
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
334
|
-
res.end(JSON.stringify({ status: 'idle', pending: 0, inProgress: 0, done: 0,
|
|
334
|
+
res.end(JSON.stringify({ status: 'idle', pending: 0, inProgress: 0, done: 0, cancelled: 0 }));
|
|
335
335
|
});
|
|
336
336
|
}
|
|
337
337
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export interface HierarchyEvent {
|
|
5
|
+
id: number;
|
|
6
|
+
parentId: string;
|
|
7
|
+
type: string;
|
|
8
|
+
taskId: string;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
data?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Append-only event log scoped to task hierarchies.
|
|
15
|
+
*
|
|
16
|
+
* Events are tagged with `parentId` so sibling tasks can read only
|
|
17
|
+
* events relevant to their hierarchy. Unrelated task hierarchies
|
|
18
|
+
* don't see each other's events.
|
|
19
|
+
*
|
|
20
|
+
* Writes to `.weaver/hierarchy-events.ndjson`.
|
|
21
|
+
*/
|
|
22
|
+
export class HierarchyEventLog {
|
|
23
|
+
private readonly filePath: string;
|
|
24
|
+
private nextId = 0;
|
|
25
|
+
|
|
26
|
+
constructor(projectDir: string) {
|
|
27
|
+
const dir = path.join(projectDir, '.weaver');
|
|
28
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
29
|
+
this.filePath = path.join(dir, 'hierarchy-events.ndjson');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Append a hierarchy-scoped event. */
|
|
33
|
+
emit(event: { parentId: string; type: string; taskId: string; data?: Record<string, unknown> }): void {
|
|
34
|
+
const full: HierarchyEvent = { ...event, id: this.nextId++, timestamp: Date.now() };
|
|
35
|
+
fs.appendFileSync(this.filePath, JSON.stringify(full) + '\n', 'utf-8');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Read events for a specific parent hierarchy, starting from offset.
|
|
40
|
+
* Only returns events matching the given parentId.
|
|
41
|
+
*/
|
|
42
|
+
tailByParent(parentId: string, offset = 0): HierarchyEvent[] {
|
|
43
|
+
if (!fs.existsSync(this.filePath)) return [];
|
|
44
|
+
const content = fs.readFileSync(this.filePath, 'utf-8');
|
|
45
|
+
const result: HierarchyEvent[] = [];
|
|
46
|
+
for (const line of content.split('\n')) {
|
|
47
|
+
const trimmed = line.trim();
|
|
48
|
+
if (!trimmed) continue;
|
|
49
|
+
try {
|
|
50
|
+
const event = JSON.parse(trimmed) as HierarchyEvent;
|
|
51
|
+
if (event.parentId === parentId && event.id >= offset) {
|
|
52
|
+
result.push(event);
|
|
53
|
+
}
|
|
54
|
+
} catch { /* skip corrupt line */ }
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Remove all events. */
|
|
60
|
+
clear(): void {
|
|
61
|
+
try { fs.unlinkSync(this.filePath); } catch { /* ok */ }
|
|
62
|
+
this.nextId = 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/bot/operations.ts
CHANGED
|
@@ -34,6 +34,13 @@ export const OP_RUN_TESTS = 'run_tests';
|
|
|
34
34
|
|
|
35
35
|
export const OP_TASK_CREATE = 'task_create';
|
|
36
36
|
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Memory
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
export const OP_REMEMBER = 'remember';
|
|
42
|
+
export const OP_RECALL = 'recall';
|
|
43
|
+
|
|
37
44
|
// ---------------------------------------------------------------------------
|
|
38
45
|
// Passthrough (no execution needed)
|
|
39
46
|
// ---------------------------------------------------------------------------
|
package/src/bot/profile-store.ts
CHANGED
|
@@ -230,6 +230,45 @@ const DEFAULT_PROFILES: Record<string, Array<Omit<CreateProfileInput, 'botId'>>>
|
|
|
230
230
|
},
|
|
231
231
|
},
|
|
232
232
|
},
|
|
233
|
+
{
|
|
234
|
+
name: 'Orchestrator',
|
|
235
|
+
description: 'Decomposes high-level objectives into tasks, assigns profiles, manages dependencies.',
|
|
236
|
+
icon: 'accountTree',
|
|
237
|
+
color: 'color-node-purple-icon',
|
|
238
|
+
capabilities: [
|
|
239
|
+
{ name: 'decomposition', description: 'Break complex objectives into subtask DAGs with dependencies' },
|
|
240
|
+
{ name: 'routing', description: 'Match tasks to the right bot profile based on capabilities' },
|
|
241
|
+
{ name: 'task-mgmt', description: 'Create, prioritize, and manage swarm tasks' },
|
|
242
|
+
],
|
|
243
|
+
preferences: {
|
|
244
|
+
costStrategy: 'frugal',
|
|
245
|
+
requireApproval: false,
|
|
246
|
+
instructions: `You are the orchestrator. Your job is to decompose high-level objectives into concrete, actionable subtasks that other bots can execute.
|
|
247
|
+
|
|
248
|
+
## Protocol
|
|
249
|
+
1. ANALYZE: Understand the objective. What files, features, and concerns are involved?
|
|
250
|
+
2. DECOMPOSE: Break into subtasks. Each subtask should be completable by a single bot.
|
|
251
|
+
3. ASSIGN: Set assignedProfile per task (developer, reviewer, ops). Set complexity and priority.
|
|
252
|
+
4. DEPENDENCIES: Use dependsOn to model blocking relationships. Minimize deps for parallelism.
|
|
253
|
+
|
|
254
|
+
## Rules
|
|
255
|
+
- You do NOT write code yourself. You create tasks for other bots.
|
|
256
|
+
- Every subtask needs: title, description, assignedProfile, complexity.
|
|
257
|
+
- Structure as: setup → independent implementations → integration → review.
|
|
258
|
+
- If a task is simple enough (single file, clear scope), don't decompose further.`,
|
|
259
|
+
behavior: {
|
|
260
|
+
phases: {
|
|
261
|
+
plan: { enabled: true, tier: 'fast' as const },
|
|
262
|
+
execute: { enabled: true, tier: 'fast' as const },
|
|
263
|
+
review: { enabled: false },
|
|
264
|
+
validate: { enabled: false },
|
|
265
|
+
gitOps: { enabled: false },
|
|
266
|
+
},
|
|
267
|
+
escalation: { maxAttempts: 2, onExhausted: 'block' as const },
|
|
268
|
+
exitProtocol: { reportConcerns: false, requireEvidence: false },
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
},
|
|
233
272
|
],
|
|
234
273
|
'weaver-genesis': [
|
|
235
274
|
{
|
package/src/bot/runner.ts
CHANGED
|
@@ -255,6 +255,7 @@ export async function runWorkflow(
|
|
|
255
255
|
const nodeLabels = new Map<string, string>(); // nodeTypeName | instanceId → label
|
|
256
256
|
const nodeVisuals = new Map<string, { color?: string; icon?: string }>(); // nodeTypeName | instanceId → visual meta
|
|
257
257
|
const portLabels = new Map<string, string>(); // "nodeTypeName.portName" → label
|
|
258
|
+
const portFormats = new Map<string, string>(); // "nodeTypeName.portName" → format hint
|
|
258
259
|
try {
|
|
259
260
|
const parserMod = '@synergenius/flow-weaver';
|
|
260
261
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -267,8 +268,9 @@ export async function runWorkflow(
|
|
|
267
268
|
// Extract output port labels: outputs is Record<string, PortConfig>
|
|
268
269
|
if (nt.outputs && typeof nt.outputs === 'object') {
|
|
269
270
|
for (const [portName, portCfg] of Object.entries(nt.outputs)) {
|
|
270
|
-
const cfg = portCfg as { label?: string } | undefined;
|
|
271
|
+
const cfg = portCfg as { label?: string; customMetadata?: Record<string, string | boolean | number> } | undefined;
|
|
271
272
|
if (cfg?.label) portLabels.set(`${nt.name}.${portName}`, cfg.label);
|
|
273
|
+
if (cfg?.customMetadata?.format) portFormats.set(`${nt.name}.${portName}`, String(cfg.customMetadata.format));
|
|
272
274
|
}
|
|
273
275
|
}
|
|
274
276
|
}
|
|
@@ -288,7 +290,7 @@ export async function runWorkflow(
|
|
|
288
290
|
// Track node start times, node types, and outputs so we can attach them on completion.
|
|
289
291
|
const nodeStartTimes = new Map<string, number>();
|
|
290
292
|
const nodeTypes = new Map<string, string>(); // nodeId → nodeTypeName
|
|
291
|
-
const nodeOutputs = new Map<string, Array<{ portLabel: string; value: unknown }>>();
|
|
293
|
+
const nodeOutputs = new Map<string, Array<{ portLabel: string; value: unknown; format?: string }>>();
|
|
292
294
|
|
|
293
295
|
/** Try to parse JSON strings into objects so the UI renders them as structured JSON. */
|
|
294
296
|
function resolveOutputValue(value: unknown): unknown {
|
|
@@ -320,9 +322,11 @@ export async function runWorkflow(
|
|
|
320
322
|
const nodeTypeName = ident.nodeTypeName as string | undefined;
|
|
321
323
|
if (nodeId && portName) {
|
|
322
324
|
if (nodeTypeName) nodeTypes.set(nodeId, nodeTypeName);
|
|
323
|
-
const
|
|
325
|
+
const resolvedNt = nodeTypeName ?? nodeTypes.get(nodeId);
|
|
326
|
+
const label = resolvePortLabel(resolvedNt, portName);
|
|
327
|
+
const format = resolvedNt ? portFormats.get(`${resolvedNt}.${portName}`) : undefined;
|
|
324
328
|
const outputs = nodeOutputs.get(nodeId) ?? [];
|
|
325
|
-
outputs.push({ portLabel: label, value: resolveOutputValue(traceEvent.data.value) });
|
|
329
|
+
outputs.push({ portLabel: label, value: resolveOutputValue(traceEvent.data.value), ...(format ? { format } : {}) });
|
|
326
330
|
nodeOutputs.set(nodeId, outputs);
|
|
327
331
|
}
|
|
328
332
|
}
|
package/src/bot/step-executor.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { promisify } from 'node:util';
|
|
|
7
7
|
const execAsync = promisify(exec);
|
|
8
8
|
import { runCommand } from '@synergenius/flow-weaver';
|
|
9
9
|
import { BLOCKED_SHELL_PATTERNS } from './safety.js';
|
|
10
|
-
import { OP_WRITE_FILE, OP_READ_FILE, OP_PATCH_FILE, OP_LIST_FILES, OP_RUN_SHELL, OP_TASK_CREATE, OP_RESPOND, OP_NO_OP, OP_NOOP, OP_DONE, OP_COMPLETE, OP_CREATE_WORKFLOW, OP_MODIFY_SOURCE, OP_IMPLEMENT_NODE, normalizeOperation } from './operations.js';
|
|
10
|
+
import { OP_WRITE_FILE, OP_READ_FILE, OP_PATCH_FILE, OP_LIST_FILES, OP_RUN_SHELL, OP_TASK_CREATE, OP_RESPOND, OP_NO_OP, OP_NOOP, OP_DONE, OP_COMPLETE, OP_CREATE_WORKFLOW, OP_MODIFY_SOURCE, OP_IMPLEMENT_NODE, OP_REMEMBER, OP_RECALL, normalizeOperation } from './operations.js';
|
|
11
11
|
import { TaskStore } from './task-store.js';
|
|
12
12
|
import type { CreateTaskInput } from './task-types.js';
|
|
13
13
|
|
|
@@ -424,6 +424,34 @@ export async function executeStep(
|
|
|
424
424
|
return { output: `Created task "${title}" (${task.id})${subtasks.length ? ` with ${subtasks.length} subtasks` : ''}. IDs: ${ids.join(', ')}` };
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
// -----------------------------------------------------------------
|
|
428
|
+
// Memory operations — persist/recall project conventions
|
|
429
|
+
// -----------------------------------------------------------------
|
|
430
|
+
case OP_REMEMBER: {
|
|
431
|
+
const key = args.key as string | undefined;
|
|
432
|
+
if (!key) return { blocked: true, blockReason: 'remember requires a "key" argument.' };
|
|
433
|
+
const value = (args.value as string) ?? '';
|
|
434
|
+
const memDir = path.join(projectDir, '.weaver');
|
|
435
|
+
if (!fs.existsSync(memDir)) fs.mkdirSync(memDir, { recursive: true });
|
|
436
|
+
const memPath = path.join(memDir, 'project-memory.json');
|
|
437
|
+
let memory: Record<string, string> = {};
|
|
438
|
+
try {
|
|
439
|
+
if (fs.existsSync(memPath)) memory = JSON.parse(fs.readFileSync(memPath, 'utf-8'));
|
|
440
|
+
} catch { /* start fresh */ }
|
|
441
|
+
memory[key] = value;
|
|
442
|
+
fs.writeFileSync(memPath, JSON.stringify(memory, null, 2), 'utf-8');
|
|
443
|
+
return { output: `Remembered: ${key} = ${value}` };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
case OP_RECALL: {
|
|
447
|
+
const memPath = path.join(projectDir, '.weaver', 'project-memory.json');
|
|
448
|
+
let memory: Record<string, string> = {};
|
|
449
|
+
try {
|
|
450
|
+
if (fs.existsSync(memPath)) memory = JSON.parse(fs.readFileSync(memPath, 'utf-8'));
|
|
451
|
+
} catch { /* no memory yet */ }
|
|
452
|
+
return { output: JSON.stringify(memory) };
|
|
453
|
+
}
|
|
454
|
+
|
|
427
455
|
// -----------------------------------------------------------------
|
|
428
456
|
// Passthrough operations
|
|
429
457
|
// -----------------------------------------------------------------
|
|
@@ -14,6 +14,7 @@ import { TaskStore } from './task-store.js';
|
|
|
14
14
|
import { BotRegistry } from './bot-registry.js';
|
|
15
15
|
import type { BotRegistration } from './bot-registry.js';
|
|
16
16
|
import { SwarmEventLog } from './swarm-event-log.js';
|
|
17
|
+
import { HierarchyEventLog } from './hierarchy-event-log.js';
|
|
17
18
|
import { RunStore } from './run-store.js';
|
|
18
19
|
import { EventLog } from './event-log.js';
|
|
19
20
|
import { runRegistry } from './run-registry.js';
|
|
@@ -93,6 +94,7 @@ export class SwarmController {
|
|
|
93
94
|
private readonly statePath: string;
|
|
94
95
|
private readonly taskStore: TaskStore;
|
|
95
96
|
private readonly eventLog: SwarmEventLog;
|
|
97
|
+
private readonly hierarchyEventLog: HierarchyEventLog;
|
|
96
98
|
private readonly orchestrator: Orchestrator;
|
|
97
99
|
private readonly instanceManager: InstanceManager;
|
|
98
100
|
private readonly profileStore: ProfileStore;
|
|
@@ -137,6 +139,7 @@ export class SwarmController {
|
|
|
137
139
|
this.statePath = path.join(this.weaverDir, SWARM_STATE_FILE);
|
|
138
140
|
this.taskStore = new TaskStore(projectDir);
|
|
139
141
|
this.eventLog = new SwarmEventLog(projectDir);
|
|
142
|
+
this.hierarchyEventLog = new HierarchyEventLog(projectDir);
|
|
140
143
|
this.orchestrator = new Orchestrator({ aiRouter: new AIRouterImpl(projectDir) });
|
|
141
144
|
this.instanceManager = new InstanceManager();
|
|
142
145
|
this.profileStore = new ProfileStore(projectDir);
|
|
@@ -313,7 +316,7 @@ export class SwarmController {
|
|
|
313
316
|
});
|
|
314
317
|
} else {
|
|
315
318
|
await this.taskStore.update(task.id, {
|
|
316
|
-
status: '
|
|
319
|
+
status: 'open',
|
|
317
320
|
currentBotId: undefined,
|
|
318
321
|
});
|
|
319
322
|
}
|
|
@@ -426,14 +429,22 @@ export class SwarmController {
|
|
|
426
429
|
await this._checkSteering(inst.instanceId);
|
|
427
430
|
}
|
|
428
431
|
|
|
432
|
+
// Route unassigned tasks to orchestrator profile for decomposition
|
|
433
|
+
const unassignedTasks = await this.taskStore.list({ status: ['open', 'pending'] });
|
|
434
|
+
for (const t of unassignedTasks) {
|
|
435
|
+
if (!t.assignedProfile && !t.isParent) {
|
|
436
|
+
await this.taskStore.update(t.id, { assignedProfile: 'orchestrator' });
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
429
440
|
// Collect pending tasks (and all tasks for dependency checks)
|
|
430
|
-
const pendingTasks = await this.taskStore.list({ status: ['
|
|
441
|
+
const pendingTasks = await this.taskStore.list({ status: ['open', 'pending'] });
|
|
431
442
|
const allTasks = await this.taskStore.list();
|
|
432
443
|
const routableTasks = pendingTasks.filter((t) => {
|
|
433
444
|
// Skip parent tasks
|
|
434
445
|
if (t.isParent) return false;
|
|
435
446
|
// Skip tasks that have exhausted retries
|
|
436
|
-
if (t.
|
|
447
|
+
if (t.attempt >= t.maxAttempts) return false;
|
|
437
448
|
// Skip tasks over per-task budget
|
|
438
449
|
if (t.budgetTokens !== undefined && t.tokensUsed >= t.budgetTokens) return false;
|
|
439
450
|
if (t.budgetCost !== undefined && t.costUsed >= t.budgetCost) return false;
|
|
@@ -503,12 +514,40 @@ export class SwarmController {
|
|
|
503
514
|
}
|
|
504
515
|
}
|
|
505
516
|
|
|
517
|
+
// Build file conflict map: which files are being modified by currently executing tasks
|
|
518
|
+
const activeFiles = new Map<string, string>(); // filepath → taskId
|
|
519
|
+
for (const [instId] of this.executionPromises) {
|
|
520
|
+
const inst = this.instanceManager.listAll().find(i => i.instanceId === instId);
|
|
521
|
+
if (inst?.currentTaskId) {
|
|
522
|
+
const runningTask = await this.taskStore.get(inst.currentTaskId);
|
|
523
|
+
if (runningTask?.context.files) {
|
|
524
|
+
for (const f of runningTask.context.files) {
|
|
525
|
+
activeFiles.set(f, runningTask.id);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
506
531
|
// Apply assignments
|
|
507
532
|
for (const assignment of output.assignments) {
|
|
508
533
|
const profile = profiles.find((p) => p.id === assignment.profileId);
|
|
509
534
|
const bot = this.profileBotMap.get(assignment.profileId);
|
|
510
535
|
if (!profile || !bot) continue;
|
|
511
536
|
|
|
537
|
+
// File conflict check: skip if task targets files currently being modified
|
|
538
|
+
const candidateTask = routableTasks.find(t => t.id === assignment.taskId);
|
|
539
|
+
if (candidateTask?.context.files?.length) {
|
|
540
|
+
const hasFileConflict = candidateTask.context.files.some(f => activeFiles.has(f));
|
|
541
|
+
if (hasFileConflict) {
|
|
542
|
+
this.eventLog.emit({
|
|
543
|
+
type: 'task-skipped',
|
|
544
|
+
timestamp: Date.now(),
|
|
545
|
+
data: { taskId: assignment.taskId, reason: 'file-conflict' },
|
|
546
|
+
});
|
|
547
|
+
continue; // Skip this assignment, retry next cycle
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
512
551
|
try {
|
|
513
552
|
// Assign task in TaskStore
|
|
514
553
|
await this.taskStore.assignToInstance(assignment.taskId, assignment.instanceId, assignment.profileId);
|
|
@@ -668,7 +707,7 @@ export class SwarmController {
|
|
|
668
707
|
};
|
|
669
708
|
|
|
670
709
|
// Release task
|
|
671
|
-
const releaseStatus = result.success ? 'done' : '
|
|
710
|
+
const releaseStatus = result.success ? 'done' : 'open';
|
|
672
711
|
await this.taskStore.release(taskId, releaseStatus, runSummary);
|
|
673
712
|
|
|
674
713
|
// Record token usage
|
|
@@ -686,13 +725,31 @@ export class SwarmController {
|
|
|
686
725
|
this.instanceManager.markIdle(instanceId, result.success);
|
|
687
726
|
} catch { /* instance may have been stopped */ }
|
|
688
727
|
|
|
689
|
-
// Emit task event
|
|
728
|
+
// Emit swarm-level task event
|
|
690
729
|
this.eventLog.emit({
|
|
691
730
|
type: result.success ? 'task-done' : 'task-failed',
|
|
692
731
|
timestamp: Date.now(),
|
|
693
732
|
data: { botId: instanceId, taskId, outcome: runSummary.outcome },
|
|
694
733
|
});
|
|
695
734
|
|
|
735
|
+
// Emit hierarchy-scoped event so sibling tasks can see what happened
|
|
736
|
+
try {
|
|
737
|
+
const task = await this.taskStore.get(taskId);
|
|
738
|
+
if (task?.parentId) {
|
|
739
|
+
this.hierarchyEventLog.emit({
|
|
740
|
+
parentId: task.parentId,
|
|
741
|
+
type: result.success ? 'task-completed' : 'task-run-failed',
|
|
742
|
+
taskId,
|
|
743
|
+
data: {
|
|
744
|
+
summary: runSummary.summary,
|
|
745
|
+
filesModified: runSummary.filesModified,
|
|
746
|
+
botId: instanceId,
|
|
747
|
+
attempt: task.attempt,
|
|
748
|
+
},
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
} catch { /* non-fatal — hierarchy events are best-effort */ }
|
|
752
|
+
|
|
696
753
|
this._syncInstancesState();
|
|
697
754
|
this._persist();
|
|
698
755
|
}
|