@synergenius/flow-weaver-pack-weaver 0.9.34 → 0.9.35

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 (38) hide show
  1. package/dist/ai-chat-provider.d.ts.map +1 -1
  2. package/dist/ai-chat-provider.js +140 -79
  3. package/dist/ai-chat-provider.js.map +1 -1
  4. package/dist/bot/agent-loop.d.ts +20 -0
  5. package/dist/bot/agent-loop.d.ts.map +1 -0
  6. package/dist/bot/agent-loop.js +331 -0
  7. package/dist/bot/agent-loop.js.map +1 -0
  8. package/dist/bot/run-registry.d.ts +41 -0
  9. package/dist/bot/run-registry.d.ts.map +1 -0
  10. package/dist/bot/run-registry.js +53 -0
  11. package/dist/bot/run-registry.js.map +1 -0
  12. package/dist/bot/runner.d.ts.map +1 -1
  13. package/dist/bot/runner.js +12 -2
  14. package/dist/bot/runner.js.map +1 -1
  15. package/dist/cli.d.ts +3 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +749 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/docs/weaver-config.md +15 -9
  20. package/dist/templates/weaver-template.d.ts +11 -0
  21. package/dist/templates/weaver-template.d.ts.map +1 -0
  22. package/dist/templates/weaver-template.js +53 -0
  23. package/dist/templates/weaver-template.js.map +1 -0
  24. package/dist/workflows/weaver-bot-session.d.ts +65 -0
  25. package/dist/workflows/weaver-bot-session.d.ts.map +1 -0
  26. package/dist/workflows/weaver-bot-session.js +68 -0
  27. package/dist/workflows/weaver-bot-session.js.map +1 -0
  28. package/dist/workflows/weaver.d.ts +24 -0
  29. package/dist/workflows/weaver.d.ts.map +1 -0
  30. package/dist/workflows/weaver.js +28 -0
  31. package/dist/workflows/weaver.js.map +1 -0
  32. package/package.json +1 -1
  33. package/src/ai-chat-provider.ts +129 -81
  34. package/src/bot/run-registry.ts +71 -0
  35. package/src/bot/runner.ts +10 -2
  36. package/dist/docs/weaver-bot-usage.md +0 -34
  37. package/dist/docs/weaver-genesis.md +0 -32
  38. package/dist/docs/weaver-task-queue.md +0 -34
@@ -12,6 +12,7 @@ import { runWorkflow } from './bot/runner.js';
12
12
  import { RunStore } from './bot/run-store.js';
13
13
  import { CostStore } from './bot/cost-store.js';
14
14
  import { defaultRegistry, discoverProviders } from './bot/provider-registry.js';
15
+ import { runRegistry } from './bot/run-registry.js';
15
16
 
16
17
  interface AiChatToolContext {
17
18
  workspacePath: string;
@@ -137,17 +138,21 @@ const toolHandlers: Record<
137
138
  // Fire-and-forget: start workflow in background, events stream via EventLog
138
139
  eventLog.emit({ type: 'bot-started', timestamp: Date.now(), data: { instruction: task.instruction, mode: task.mode, runId } });
139
140
 
140
- runWorkflow(workflowPath, {
141
+ // runWorkflow registers itself in runRegistry via runner.ts.
142
+ // The .catch ensures EventLog is finalized for errors before runner's try/catch.
143
+ const runPromise = runWorkflow(workflowPath, {
141
144
  params: { taskJson: JSON.stringify(task), projectDir },
142
145
  dryRun: args.dryRun as boolean | undefined,
143
146
  eventLog,
144
147
  }).catch((err) => {
145
- // Ensure EventLog is finalized even for early errors (e.g., file not found)
146
- // that throw before runner.ts's own try/catch
147
- eventLog.done();
148
+ // Finalize EventLog for early errors that throw before runner.ts's try/finally
149
+ try { eventLog.done(); } catch { /* already done */ }
148
150
  console.error('[weaver-bot] Background execution failed:', err);
149
151
  });
150
152
 
153
+ // Keep a reference so we don't lose track of it
154
+ void runPromise;
155
+
151
156
  return JSON.stringify({ runId, instruction: task.instruction, status: 'started' });
152
157
  },
153
158
 
@@ -195,9 +200,41 @@ const toolHandlers: Record<
195
200
 
196
201
  async fw_weaver_status() {
197
202
  const { SessionStore } = await import('./bot/session-state.js');
203
+ const { EventLog } = await import('./bot/event-log.js');
198
204
  const store = new SessionStore();
199
205
  const state = store.load();
200
- return state ? JSON.stringify(state, null, 2) : JSON.stringify({ status: 'no active session' }, null, 2);
206
+
207
+ if (!state) {
208
+ return JSON.stringify({ status: 'no active session' }, null, 2);
209
+ }
210
+
211
+ // Startup cleanup: on first call after process restart, the registry is empty.
212
+ // If session.json claims 'executing' but nothing is in the registry, heal it.
213
+ if (!runRegistry.startupCleanupDone) {
214
+ runRegistry.markStartupCleanupDone();
215
+ if (state.status !== 'idle' && runRegistry.size === 0) {
216
+ // Orphaned session from a previous process — heal it
217
+ const orphanRunId = state.currentRunId;
218
+ if (orphanRunId) {
219
+ try { new EventLog(orphanRunId).done(); } catch { /* non-fatal */ }
220
+ }
221
+ await store.update({ status: 'idle', currentTask: null, currentRunId: null });
222
+ return JSON.stringify({ ...state, status: 'idle', currentTask: null, currentRunId: null, alive: false }, null, 2);
223
+ }
224
+ }
225
+
226
+ // Check if the current run is actually alive in the registry
227
+ const currentRunId = state.currentRunId ?? null;
228
+ const alive = currentRunId ? runRegistry.isAlive(currentRunId) : state.status === 'idle' ? null : runRegistry.size > 0;
229
+
230
+ // Self-heal: if status says executing but run is dead, clean up
231
+ if (currentRunId && alive === false && state.status !== 'idle') {
232
+ try { new EventLog(currentRunId).done(); } catch { /* non-fatal */ }
233
+ await store.update({ status: 'idle', currentTask: null, currentRunId: null });
234
+ return JSON.stringify({ ...state, status: 'idle', currentTask: null, currentRunId: null, alive: false }, null, 2);
235
+ }
236
+
237
+ return JSON.stringify({ ...state, alive }, null, 2);
201
238
  },
202
239
 
203
240
  async fw_weaver_events(args) {
@@ -208,7 +245,14 @@ const toolHandlers: Record<
208
245
  const offset = (args.offset as number) ?? 0;
209
246
  const log = new EventLog(runId);
210
247
  const events = log.tail(offset);
211
- const done = log.isDone();
248
+ let done = log.isDone();
249
+
250
+ // Self-healing: if the run is not in the registry and has no .done marker,
251
+ // it's a zombie. Write the .done marker so this is the last poll.
252
+ if (!done && !runRegistry.isAlive(runId)) {
253
+ try { log.done(); } catch { /* non-fatal */ }
254
+ done = true;
255
+ }
212
256
 
213
257
  return JSON.stringify({ events, done });
214
258
  },
@@ -251,9 +295,10 @@ const toolHandlers: Record<
251
295
 
252
296
  if (action === 'status') {
253
297
  const state = store.load();
254
- return state
255
- ? JSON.stringify(state, null, 2)
256
- : JSON.stringify({ status: 'no active session' });
298
+ if (!state) return JSON.stringify({ status: 'no active session' });
299
+ const crid = state.currentRunId ?? null;
300
+ const alive = crid ? runRegistry.isAlive(crid) : null;
301
+ return JSON.stringify({ ...state, alive }, null, 2);
257
302
  }
258
303
 
259
304
  if (action === 'stop') {
@@ -310,92 +355,95 @@ const toolHandlers: Record<
310
355
  let completed = 0;
311
356
  let failed = 0;
312
357
 
313
- for (let i = 0; i < Math.min(maxTasks, pending.length); i++) {
314
- const task = pending[i]!;
315
-
316
- // Check for cancel signal
317
- try {
318
- const { SteeringController } = await import('./bot/steering.js');
319
- const ctrl = new SteeringController();
320
- const signal = await ctrl.check();
321
- if (signal?.command === 'cancel') {
322
- sessionLog.emit({ type: 'session:cancelled', timestamp: Date.now() });
323
- break;
324
- }
325
- } catch { /* non-fatal */ }
326
-
327
- const runId = RunStore.newId();
328
- const taskLog = new EventLog(runId);
329
-
330
- // Mark task as running in the queue
331
- try { await queue.markRunning(task.id); } catch { /* non-fatal */ }
358
+ try {
359
+ for (let i = 0; i < Math.min(maxTasks, pending.length); i++) {
360
+ const task = pending[i]!;
332
361
 
333
- await store.update({
334
- status: 'executing',
335
- currentTask: task.instruction,
336
- currentRunId: runId,
337
- lastActivity: Date.now(),
338
- });
362
+ // Check for cancel signal
363
+ try {
364
+ const { SteeringController } = await import('./bot/steering.js');
365
+ const ctrl = new SteeringController();
366
+ const signal = await ctrl.check();
367
+ if (signal?.command === 'cancel') {
368
+ sessionLog.emit({ type: 'session:cancelled', timestamp: Date.now() });
369
+ break;
370
+ }
371
+ } catch { /* non-fatal */ }
339
372
 
340
- sessionLog.emit({ type: 'session:task-start', timestamp: Date.now(), data: {
341
- taskId: runId, instruction: task.instruction, index: i, total: Math.min(maxTasks, pending.length),
342
- }});
373
+ const runId = RunStore.newId();
374
+ const taskLog = new EventLog(runId);
343
375
 
344
- taskLog.emit({ type: 'bot-started', timestamp: Date.now(), data: {
345
- instruction: task.instruction, mode: 'create', runId,
346
- }});
376
+ // Mark task as running in the queue
377
+ try { await queue.markRunning(task.id); } catch { /* non-fatal */ }
347
378
 
348
- try {
349
- const result = await runWorkflow(workflowPath, {
350
- params: {
351
- taskJson: JSON.stringify({ instruction: task.instruction, mode: 'create' }),
352
- projectDir,
353
- },
354
- eventLog: taskLog,
379
+ await store.update({
380
+ status: 'executing',
381
+ currentTask: task.instruction,
382
+ currentRunId: runId,
383
+ lastActivity: Date.now(),
355
384
  });
356
385
 
357
- const success = result.success;
358
- completed += success ? 1 : 0;
359
- failed += success ? 0 : 1;
386
+ sessionLog.emit({ type: 'session:task-start', timestamp: Date.now(), data: {
387
+ taskId: runId, instruction: task.instruction, index: i, total: Math.min(maxTasks, pending.length),
388
+ }});
360
389
 
361
- sessionLog.emit({ type: 'session:task-complete', timestamp: Date.now(), data: {
362
- taskId: runId, outcome: result.outcome, duration: result.executionTime,
363
- cost: result.cost?.totalCost,
390
+ taskLog.emit({ type: 'bot-started', timestamp: Date.now(), data: {
391
+ instruction: task.instruction, mode: 'create', runId,
364
392
  }});
365
393
 
366
- // Mark task in queue
367
394
  try {
368
- await queue.remove(task.id);
369
- } catch { /* non-fatal */ }
395
+ const result = await runWorkflow(workflowPath, {
396
+ params: {
397
+ taskJson: JSON.stringify({ instruction: task.instruction, mode: 'create' }),
398
+ projectDir,
399
+ },
400
+ eventLog: taskLog,
401
+ });
402
+
403
+ const success = result.success;
404
+ completed += success ? 1 : 0;
405
+ failed += success ? 0 : 1;
406
+
407
+ sessionLog.emit({ type: 'session:task-complete', timestamp: Date.now(), data: {
408
+ taskId: runId, outcome: result.outcome, duration: result.executionTime,
409
+ cost: result.cost?.totalCost,
410
+ }});
411
+
412
+ // Mark task in queue
413
+ try {
414
+ await queue.remove(task.id);
415
+ } catch { /* non-fatal */ }
416
+
417
+ } catch (err) {
418
+ failed++;
419
+ sessionLog.emit({ type: 'session:task-complete', timestamp: Date.now(), data: {
420
+ taskId: runId, outcome: 'error', error: String(err),
421
+ }});
422
+ }
370
423
 
371
- } catch (err) {
372
- failed++;
373
- sessionLog.emit({ type: 'session:task-complete', timestamp: Date.now(), data: {
374
- taskId: runId, outcome: 'error', error: String(err),
375
- }});
424
+ await store.update({
425
+ completedTasks: completed + failed,
426
+ currentRunId: null,
427
+ lastActivity: Date.now(),
428
+ });
376
429
  }
377
430
 
378
- await store.update({
379
- completedTasks: completed + failed,
380
- currentRunId: null,
381
- lastActivity: Date.now(),
382
- });
431
+ sessionLog.emit({ type: 'session:ended', timestamp: Date.now(), data: {
432
+ reason: 'complete', completed, failed, total: completed + failed,
433
+ }});
434
+ } catch (err) {
435
+ console.error('[weaver-session] Session failed:', err);
436
+ sessionLog.emit({ type: 'session:ended', timestamp: Date.now(), data: {
437
+ reason: 'error', error: String(err),
438
+ }});
439
+ } finally {
440
+ // GUARANTEED: session log finalized and state reset, even on crash
441
+ try { sessionLog.done(); } catch { /* already done */ }
442
+ try {
443
+ await store.update({ status: 'idle', currentTask: null, currentRunId: null });
444
+ } catch { /* non-fatal */ }
383
445
  }
384
-
385
- sessionLog.emit({ type: 'session:ended', timestamp: Date.now(), data: {
386
- reason: 'complete', completed, failed, total: completed + failed,
387
- }});
388
- sessionLog.done();
389
-
390
- await store.update({ status: 'idle', currentTask: null, currentRunId: null });
391
- })().catch(async (err) => {
392
- console.error('[weaver-session] Session failed:', err);
393
- sessionLog.emit({ type: 'session:ended', timestamp: Date.now(), data: {
394
- reason: 'error', error: String(err),
395
- }});
396
- sessionLog.done();
397
- await store.update({ status: 'idle', currentTask: null, currentRunId: null });
398
- });
446
+ })();
399
447
 
400
448
  return JSON.stringify({ started: true, sessionId, taskCount: Math.min(maxTasks, pending.length) });
401
449
  }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * RunRegistry — in-memory registry of active workflow runs.
3
+ *
4
+ * Module-level singleton. The ai-chat-provider module is cached per workspace
5
+ * by the platform, so this Map persists across all tool calls within a session.
6
+ *
7
+ * Purpose:
8
+ * - Definitive alive/dead check: if a runId is in the registry, it's alive.
9
+ * If not, it's dead (completed, failed, or orphaned).
10
+ * - Self-healing: any read path (fw_weaver_status, fw_weaver_events) can check
11
+ * the registry and clean up stale state as a side effect.
12
+ * - Startup cleanup: on first access after process restart, the registry is empty,
13
+ * so any session.json claiming 'executing' is immediately detectable as orphaned.
14
+ */
15
+
16
+ export interface ActiveRun {
17
+ /** The workflow file being executed. */
18
+ file: string;
19
+ /** ISO timestamp when the run started. */
20
+ startedAt: string;
21
+ /** The Promise that resolves when the run completes (for awaiting if needed). */
22
+ promise: Promise<unknown>;
23
+ }
24
+
25
+ /** Module-level singleton — survives across tool calls, cleared on process restart. */
26
+ const registry = new Map<string, ActiveRun>();
27
+
28
+ /** Whether startup cleanup has been performed in this process lifetime. */
29
+ let startupCleanupDone = false;
30
+
31
+ export const runRegistry = {
32
+ /** Register a new active run. */
33
+ register(runId: string, entry: ActiveRun): void {
34
+ registry.set(runId, entry);
35
+ },
36
+
37
+ /** Remove a completed/failed run from the registry. */
38
+ remove(runId: string): void {
39
+ registry.delete(runId);
40
+ },
41
+
42
+ /** Check if a run is currently active (alive). */
43
+ isAlive(runId: string): boolean {
44
+ return registry.has(runId);
45
+ },
46
+
47
+ /** Get the active run entry, or undefined if not alive. */
48
+ get(runId: string): ActiveRun | undefined {
49
+ return registry.get(runId);
50
+ },
51
+
52
+ /** Get all active run IDs. */
53
+ activeRunIds(): string[] {
54
+ return Array.from(registry.keys());
55
+ },
56
+
57
+ /** Number of currently active runs. */
58
+ get size(): number {
59
+ return registry.size;
60
+ },
61
+
62
+ /** Whether startup cleanup has already run in this process lifetime. */
63
+ get startupCleanupDone(): boolean {
64
+ return startupCleanupDone;
65
+ },
66
+
67
+ /** Mark startup cleanup as completed. */
68
+ markStartupCleanupDone(): void {
69
+ startupCleanupDone = true;
70
+ },
71
+ };
package/src/bot/runner.ts CHANGED
@@ -26,6 +26,7 @@ import type { NotificationErrorHandler } from './notifications.js';
26
26
  import { RunStore } from './run-store.js';
27
27
  import { CostTracker } from './cost-tracker.js';
28
28
  import { CostStore } from './cost-store.js';
29
+ import { runRegistry } from './run-registry.js';
29
30
 
30
31
  function resolveApproval(
31
32
  approval: BotConfig['approval'],
@@ -122,6 +123,11 @@ export async function runWorkflow(
122
123
  const runId = RunStore.newId();
123
124
  const startedAt = new Date().toISOString();
124
125
 
126
+ // Track this run in the in-memory registry for alive/dead checks
127
+ let registryPromiseResolve: (() => void) | undefined;
128
+ const registryPromise = new Promise<void>((resolve) => { registryPromiseResolve = resolve; });
129
+ runRegistry.register(runId, { file: absPath, startedAt, promise: registryPromise });
130
+
125
131
  // Collect execution trace and audit events for persistence
126
132
  const collectedTrace: ExecutionEvent[] = [];
127
133
  const collectedAudit: AuditEvent[] = [];
@@ -362,7 +368,6 @@ export async function runWorkflow(
362
368
  }, verbose);
363
369
 
364
370
  logEvent?.({ type: 'bot-completed', timestamp: Date.now(), data: { success, outcome, summary } });
365
- options?.eventLog?.done();
366
371
 
367
372
  auditEmit('run-complete', { success, outcome, summary });
368
373
 
@@ -395,12 +400,15 @@ export async function runWorkflow(
395
400
  }, verbose);
396
401
 
397
402
  logEvent?.({ type: 'bot-failed', timestamp: Date.now(), data: { error: msg } });
398
- options?.eventLog?.done();
399
403
 
400
404
  auditEmit('run-complete', { success: false, error: msg });
401
405
 
402
406
  return { success: false, summary: msg, outcome: 'error', cost: costSummary };
403
407
  } finally {
408
+ // GUARANTEED cleanup — runs on success, error, dry-run early return, and crash
409
+ runRegistry.remove(runId);
410
+ registryPromiseResolve?.();
411
+ try { options?.eventLog?.done(); } catch { /* already done or no log */ }
404
412
  teardownAuditLogger();
405
413
  }
406
414
  }
@@ -1,34 +0,0 @@
1
- ## Weaver Bot
2
-
3
- The Weaver bot is an autonomous AI agent that creates and modifies Flow Weaver workflows from natural language instructions.
4
-
5
- ### Running the bot
6
-
7
- Use `fw_weaver_bot` with a task description:
8
- - `task`: Natural language instruction (required)
9
- - `mode`: `create` (new workflow), `modify` (edit existing), `read` (analyze), `batch` (multiple tasks)
10
- - `targets`: File paths for modify/read mode
11
- - `autoApprove`: Skip the approval gate (default: true in studio)
12
-
13
- ### Execution flow
14
-
15
- 1. **Receive task** — parses instruction and determines mode
16
- 2. **Build context** — gathers project state, templates, and relevant files
17
- 3. **Plan** — AI generates a step-by-step execution plan
18
- 4. **Approval gate** — plan shown for review (skipped if autoApprove)
19
- 5. **Execute + validate + retry** — runs steps, validates output, retries on errors (up to 3 attempts)
20
- 6. **Git ops** — commits changes if successful
21
- 7. **Report** — returns summary with outcome
22
-
23
- ### Steering a running bot
24
-
25
- Use `fw_weaver_steer` to control execution:
26
- - `pause` — pause at next safe point
27
- - `resume` — continue after pause
28
- - `cancel` — abort execution
29
- - `redirect` — change task mid-execution
30
- - `queue` — add a follow-up task
31
-
32
- ### Checking status
33
-
34
- Use `fw_weaver_status` to see the current bot session state, active task, and completion count.
@@ -1,32 +0,0 @@
1
- ## Genesis Self-Evolution
2
-
3
- Genesis is Weaver's self-evolution protocol. It autonomously observes, proposes, applies, and validates changes to workflows.
4
-
5
- ### Running a Genesis cycle
6
-
7
- Use `fw_weaver_genesis` to trigger a single cycle:
8
- - `projectDir`: Project directory (defaults to workspace)
9
- - `dryRun`: Preview changes without applying
10
-
11
- ### How it works
12
-
13
- 1. **Observe** — scans project for workflows and their health
14
- 2. **Fingerprint** — detects if workflows changed since last cycle (avoids duplicate proposals)
15
- 3. **Stabilize check** — prevents rapid churn (cooldown between cycles)
16
- 4. **Propose** — AI analyzes project state and proposes modifications within a budget
17
- 5. **Validate proposal** — checks proposed changes are safe
18
- 6. **Snapshot** — creates backup before applying
19
- 7. **Apply** — executes proposed operations via `flow-weaver modify`
20
- 8. **Validate result** — compiles and validates the modified workflow
21
- 9. **Threshold check** — decides if changes meet the quality bar
22
- 10. **Approve** — human approval gate (configurable)
23
- 11. **Commit** — git commit with rollback capability
24
- 12. **Escrow** — data safety pipeline for recovery
25
-
26
- ### Safety features
27
-
28
- - Pre-apply snapshots for rollback
29
- - Escrow recovery from previous cycles
30
- - Fingerprinting prevents duplicate proposals
31
- - Threshold checking before committing
32
- - Budget limits on proposed changes
@@ -1,34 +0,0 @@
1
- ## Task Queue & Steering
2
-
3
- Weaver supports queuing tasks for background processing and steering running bots in real-time.
4
-
5
- ### Task queue
6
-
7
- Use `fw_weaver_queue` to manage tasks:
8
- - `add` — add a task to the queue (requires `task` instruction)
9
- - `list` — show all queued tasks
10
- - `clear` — remove all pending tasks
11
- - `remove` — remove a specific task by ID
12
-
13
- Tasks are stored in NDJSON format and processed sequentially by the bot session.
14
-
15
- ### Steering commands
16
-
17
- Use `fw_weaver_steer` to control a running bot:
18
- - `pause` — pause execution at the next safe point
19
- - `resume` — continue after a pause
20
- - `cancel` — abort the current task
21
- - `redirect` — change the task instruction mid-execution (requires `payload` with new instruction)
22
- - `queue` — add a follow-up task without interrupting the current one
23
-
24
- ### Batch mode
25
-
26
- For multiple related tasks, use `fw_weaver_bot` with `mode: "batch"` to process them in sequence through the same bot session, sharing context between tasks.
27
-
28
- ### Monitoring
29
-
30
- Use `fw_weaver_status` to check the current session state:
31
- - Session phase (idle, planning, executing, validating)
32
- - Current task instruction
33
- - Completed task count
34
- - Error state if any