@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.
Files changed (79) hide show
  1. package/dist/bot/assistant-tools.d.ts.map +1 -1
  2. package/dist/bot/assistant-tools.js +5 -11
  3. package/dist/bot/assistant-tools.js.map +1 -1
  4. package/dist/bot/capability-registry.d.ts.map +1 -1
  5. package/dist/bot/capability-registry.js +185 -15
  6. package/dist/bot/capability-registry.js.map +1 -1
  7. package/dist/bot/dashboard.js +3 -3
  8. package/dist/bot/dashboard.js.map +1 -1
  9. package/dist/bot/hierarchy-event-log.d.ts +37 -0
  10. package/dist/bot/hierarchy-event-log.d.ts.map +1 -0
  11. package/dist/bot/hierarchy-event-log.js +58 -0
  12. package/dist/bot/hierarchy-event-log.js.map +1 -0
  13. package/dist/bot/operations.d.ts +2 -0
  14. package/dist/bot/operations.d.ts.map +1 -1
  15. package/dist/bot/operations.js +5 -0
  16. package/dist/bot/operations.js.map +1 -1
  17. package/dist/bot/profile-store.d.ts.map +1 -1
  18. package/dist/bot/profile-store.js +39 -0
  19. package/dist/bot/profile-store.js.map +1 -1
  20. package/dist/bot/runner.d.ts.map +1 -1
  21. package/dist/bot/runner.js +7 -2
  22. package/dist/bot/runner.js.map +1 -1
  23. package/dist/bot/step-executor.d.ts.map +1 -1
  24. package/dist/bot/step-executor.js +33 -1
  25. package/dist/bot/step-executor.js.map +1 -1
  26. package/dist/bot/swarm-controller.d.ts +1 -0
  27. package/dist/bot/swarm-controller.d.ts.map +1 -1
  28. package/dist/bot/swarm-controller.js +59 -5
  29. package/dist/bot/swarm-controller.js.map +1 -1
  30. package/dist/bot/task-store.d.ts +1 -1
  31. package/dist/bot/task-store.d.ts.map +1 -1
  32. package/dist/bot/task-store.js +25 -37
  33. package/dist/bot/task-store.js.map +1 -1
  34. package/dist/bot/task-types.d.ts +5 -1
  35. package/dist/bot/task-types.d.ts.map +1 -1
  36. package/dist/node-types/bot-report.d.ts +2 -1
  37. package/dist/node-types/bot-report.d.ts.map +1 -1
  38. package/dist/node-types/bot-report.js +9 -4
  39. package/dist/node-types/bot-report.js.map +1 -1
  40. package/dist/node-types/build-context.d.ts.map +1 -1
  41. package/dist/node-types/build-context.js +32 -0
  42. package/dist/node-types/build-context.js.map +1 -1
  43. package/dist/node-types/plan-task.d.ts.map +1 -1
  44. package/dist/node-types/plan-task.js +5 -1
  45. package/dist/node-types/plan-task.js.map +1 -1
  46. package/dist/node-types/report.d.ts +1 -1
  47. package/dist/node-types/report.d.ts.map +1 -1
  48. package/dist/node-types/report.js +58 -8
  49. package/dist/node-types/report.js.map +1 -1
  50. package/dist/ui/capability-editor.js +184 -15
  51. package/dist/ui/profile-editor.js +184 -15
  52. package/dist/ui/swarm-dashboard.js +244 -44
  53. package/dist/ui/task-detail-view.js +60 -29
  54. package/dist/ui/use-stream-timeline.d.ts.map +1 -1
  55. package/dist/ui/use-stream-timeline.js +69 -29
  56. package/dist/ui/use-stream-timeline.js.map +1 -1
  57. package/dist/workflows/weaver-bot.d.ts +1 -0
  58. package/dist/workflows/weaver-bot.d.ts.map +1 -1
  59. package/dist/workflows/weaver-bot.js +239 -4
  60. package/dist/workflows/weaver-bot.js.map +1 -1
  61. package/flowweaver.manifest.json +1 -1
  62. package/package.json +1 -1
  63. package/src/bot/assistant-tools.ts +5 -11
  64. package/src/bot/capability-registry.ts +196 -18
  65. package/src/bot/dashboard.ts +3 -3
  66. package/src/bot/hierarchy-event-log.ts +64 -0
  67. package/src/bot/operations.ts +7 -0
  68. package/src/bot/profile-store.ts +39 -0
  69. package/src/bot/runner.ts +8 -4
  70. package/src/bot/step-executor.ts +29 -1
  71. package/src/bot/swarm-controller.ts +62 -5
  72. package/src/bot/task-store.ts +26 -39
  73. package/src/bot/task-types.ts +7 -1
  74. package/src/node-types/bot-report.ts +8 -3
  75. package/src/node-types/build-context.ts +32 -0
  76. package/src/node-types/plan-task.ts +5 -1
  77. package/src/node-types/report.ts +56 -8
  78. package/src/ui/use-stream-timeline.ts +73 -33
  79. 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
- - task_create: Create swarm subtasks for parallel execution. args: { title, description, complexity, subtasks[] }
74
- - task_list, task_get, task_update: Query and update existing tasks
75
-
76
- Use task_create to decompose complex work into smaller, independent subtasks that other bots can execute in parallel.`,
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: 'Code review guidelines, quality checklist, and security review patterns.',
160
- prompt: `## Code Review
161
-
162
- When reviewing code, check for:
163
- 1. Correctness: Does the code do what the task asked?
164
- 2. Security: No hardcoded secrets, no injection vulnerabilities, no exposed APIs
165
- 3. Style: Consistent with project conventions, proper naming, no dead code
166
- 4. Testing: Are there tests? Do they cover edge cases?
167
- 5. Performance: No unnecessary loops, no blocking calls in async code
168
-
169
- Report concerns with specific file:line references and suggested fixes.`,
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>(
@@ -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 failed = tasks.filter(t => t.status === 'failed').length;
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, failed }));
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, failed: 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
+ }
@@ -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
  // ---------------------------------------------------------------------------
@@ -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 label = resolvePortLabel(nodeTypeName ?? nodeTypes.get(nodeId), portName);
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
  }
@@ -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: 'failed',
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: ['pending', 'failed'] });
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.status === 'failed' && t.attempt >= t.maxAttempts) return false;
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' : 'failed';
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
  }