@synergenius/flowweaver-pack-weaver 0.6.1 → 0.7.0

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 (168) hide show
  1. package/dist/bot/ai-client.d.ts +1 -0
  2. package/dist/bot/ai-client.d.ts.map +1 -1
  3. package/dist/bot/ai-client.js +52 -1
  4. package/dist/bot/ai-client.js.map +1 -1
  5. package/dist/bot/audit-logger.d.ts +5 -0
  6. package/dist/bot/audit-logger.d.ts.map +1 -0
  7. package/dist/bot/audit-logger.js +42 -0
  8. package/dist/bot/audit-logger.js.map +1 -0
  9. package/dist/bot/audit-store.d.ts +13 -0
  10. package/dist/bot/audit-store.d.ts.map +1 -0
  11. package/dist/bot/audit-store.js +59 -0
  12. package/dist/bot/audit-store.js.map +1 -0
  13. package/dist/bot/cli-provider.d.ts +1 -0
  14. package/dist/bot/cli-provider.d.ts.map +1 -1
  15. package/dist/bot/cli-provider.js +86 -22
  16. package/dist/bot/cli-provider.js.map +1 -1
  17. package/dist/bot/cli-stream-parser.d.ts +11 -0
  18. package/dist/bot/cli-stream-parser.d.ts.map +1 -0
  19. package/dist/bot/cli-stream-parser.js +53 -0
  20. package/dist/bot/cli-stream-parser.js.map +1 -0
  21. package/dist/bot/file-validator.d.ts +1 -1
  22. package/dist/bot/file-validator.d.ts.map +1 -1
  23. package/dist/bot/file-validator.js +13 -27
  24. package/dist/bot/file-validator.js.map +1 -1
  25. package/dist/bot/fw-api.d.ts +8 -0
  26. package/dist/bot/fw-api.d.ts.map +1 -0
  27. package/dist/bot/fw-api.js +12 -0
  28. package/dist/bot/fw-api.js.map +1 -0
  29. package/dist/bot/runner.d.ts +2 -1
  30. package/dist/bot/runner.d.ts.map +1 -1
  31. package/dist/bot/runner.js +8 -0
  32. package/dist/bot/runner.js.map +1 -1
  33. package/dist/bot/step-executor.d.ts +3 -2
  34. package/dist/bot/step-executor.d.ts.map +1 -1
  35. package/dist/bot/step-executor.js +9 -30
  36. package/dist/bot/step-executor.js.map +1 -1
  37. package/dist/bot/system-prompt.d.ts +13 -1
  38. package/dist/bot/system-prompt.d.ts.map +1 -1
  39. package/dist/bot/system-prompt.js +28 -22
  40. package/dist/bot/system-prompt.js.map +1 -1
  41. package/dist/bot/types.d.ts +9 -1
  42. package/dist/bot/types.d.ts.map +1 -1
  43. package/dist/cli-bridge.d.ts.map +1 -1
  44. package/dist/cli-bridge.js +2 -1
  45. package/dist/cli-bridge.js.map +1 -1
  46. package/dist/cli-handlers.d.ts +2 -1
  47. package/dist/cli-handlers.d.ts.map +1 -1
  48. package/dist/cli-handlers.js +69 -0
  49. package/dist/cli-handlers.js.map +1 -1
  50. package/dist/node-types/approval-gate.d.ts.map +1 -1
  51. package/dist/node-types/approval-gate.js +4 -0
  52. package/dist/node-types/approval-gate.js.map +1 -1
  53. package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
  54. package/dist/node-types/exec-validate-retry.js +10 -4
  55. package/dist/node-types/exec-validate-retry.js.map +1 -1
  56. package/dist/node-types/execute-plan.js +1 -1
  57. package/dist/node-types/execute-plan.js.map +1 -1
  58. package/dist/node-types/git-ops.d.ts.map +1 -1
  59. package/dist/node-types/git-ops.js +2 -0
  60. package/dist/node-types/git-ops.js.map +1 -1
  61. package/dist/node-types/plan-task.d.ts.map +1 -1
  62. package/dist/node-types/plan-task.js +9 -1
  63. package/dist/node-types/plan-task.js.map +1 -1
  64. package/dist/node-types/send-notify.d.ts.map +1 -1
  65. package/dist/node-types/send-notify.js +4 -1
  66. package/dist/node-types/send-notify.js.map +1 -1
  67. package/dist/node-types/validate-result.d.ts +2 -2
  68. package/dist/node-types/validate-result.d.ts.map +1 -1
  69. package/dist/node-types/validate-result.js +2 -2
  70. package/dist/node-types/validate-result.js.map +1 -1
  71. package/dist/workflows/weaver-bot-batch.d.ts +4 -1
  72. package/dist/workflows/weaver-bot-batch.d.ts.map +1 -1
  73. package/dist/workflows/weaver-bot-batch.js +1 -1
  74. package/dist/workflows/weaver-bot-batch.js.map +1 -1
  75. package/dist/workflows/weaver-bot.d.ts +4 -1
  76. package/dist/workflows/weaver-bot.d.ts.map +1 -1
  77. package/dist/workflows/weaver-bot.js +1 -1
  78. package/dist/workflows/weaver-bot.js.map +1 -1
  79. package/flowweaver.manifest.json +1 -1
  80. package/package.json +3 -2
  81. package/src/bot/agent-provider.ts +273 -0
  82. package/src/bot/ai-client.ts +109 -0
  83. package/src/bot/approvals.ts +273 -0
  84. package/src/bot/audit-logger.ts +45 -0
  85. package/src/bot/audit-store.ts +69 -0
  86. package/src/bot/bot-agent-channel.ts +99 -0
  87. package/src/bot/cli-provider.ts +169 -0
  88. package/src/bot/cli-stream-parser.ts +59 -0
  89. package/src/bot/cost-store.ts +92 -0
  90. package/src/bot/cost-tracker.ts +72 -0
  91. package/src/bot/cron-parser.ts +153 -0
  92. package/src/bot/cron-scheduler.ts +48 -0
  93. package/src/bot/dashboard.ts +658 -0
  94. package/src/bot/design-checker.ts +327 -0
  95. package/src/bot/file-lock.ts +73 -0
  96. package/src/bot/file-validator.ts +41 -0
  97. package/src/bot/file-watcher.ts +103 -0
  98. package/src/bot/fw-api.ts +18 -0
  99. package/src/bot/genesis-prompt-context.ts +135 -0
  100. package/src/bot/genesis-store.ts +180 -0
  101. package/src/bot/index.ts +127 -0
  102. package/src/bot/notifications.ts +263 -0
  103. package/src/bot/pipeline-runner.ts +324 -0
  104. package/src/bot/provider-registry.ts +236 -0
  105. package/src/bot/run-store.ts +169 -0
  106. package/src/bot/runner.ts +311 -0
  107. package/src/bot/session-state.ts +73 -0
  108. package/src/bot/steering.ts +44 -0
  109. package/src/bot/step-executor.ts +34 -0
  110. package/src/bot/system-prompt.ts +280 -0
  111. package/src/bot/task-queue.ts +111 -0
  112. package/src/bot/types.ts +571 -0
  113. package/src/bot/utils.ts +17 -0
  114. package/src/bot/watch-daemon.ts +203 -0
  115. package/src/bot/web-approval.ts +240 -0
  116. package/src/cli-bridge.ts +41 -0
  117. package/src/cli-handlers.ts +1271 -0
  118. package/src/docs/weaver-config.md +135 -0
  119. package/src/index.ts +173 -0
  120. package/src/mcp-tools.ts +274 -0
  121. package/src/node-types/abort-task.ts +31 -0
  122. package/src/node-types/approval-gate.ts +75 -0
  123. package/src/node-types/bot-report.ts +82 -0
  124. package/src/node-types/build-context.ts +65 -0
  125. package/src/node-types/detect-provider.ts +75 -0
  126. package/src/node-types/exec-validate-retry.ts +175 -0
  127. package/src/node-types/execute-plan.ts +130 -0
  128. package/src/node-types/execute-target.ts +267 -0
  129. package/src/node-types/fix-errors.ts +68 -0
  130. package/src/node-types/genesis-apply-retry.ts +138 -0
  131. package/src/node-types/genesis-apply.ts +96 -0
  132. package/src/node-types/genesis-approve.ts +73 -0
  133. package/src/node-types/genesis-check-stabilize.ts +37 -0
  134. package/src/node-types/genesis-check-threshold.ts +34 -0
  135. package/src/node-types/genesis-commit.ts +71 -0
  136. package/src/node-types/genesis-compile-validate.ts +77 -0
  137. package/src/node-types/genesis-diff-fingerprint.ts +67 -0
  138. package/src/node-types/genesis-diff-workflow.ts +71 -0
  139. package/src/node-types/genesis-escrow-grace.ts +62 -0
  140. package/src/node-types/genesis-escrow-migrate.ts +138 -0
  141. package/src/node-types/genesis-escrow-recover.ts +99 -0
  142. package/src/node-types/genesis-escrow-stage.ts +104 -0
  143. package/src/node-types/genesis-escrow-validate.ts +120 -0
  144. package/src/node-types/genesis-load-config.ts +44 -0
  145. package/src/node-types/genesis-observe.ts +119 -0
  146. package/src/node-types/genesis-propose.ts +97 -0
  147. package/src/node-types/genesis-report.ts +95 -0
  148. package/src/node-types/genesis-snapshot.ts +30 -0
  149. package/src/node-types/genesis-try-apply.ts +165 -0
  150. package/src/node-types/genesis-update-history.ts +72 -0
  151. package/src/node-types/genesis-validate-proposal.ts +124 -0
  152. package/src/node-types/git-ops.ts +72 -0
  153. package/src/node-types/index.ts +36 -0
  154. package/src/node-types/load-config.ts +27 -0
  155. package/src/node-types/plan-task.ts +77 -0
  156. package/src/node-types/read-workflow.ts +68 -0
  157. package/src/node-types/receive-task.ts +92 -0
  158. package/src/node-types/report.ts +25 -0
  159. package/src/node-types/resolve-target.ts +64 -0
  160. package/src/node-types/route-task.ts +25 -0
  161. package/src/node-types/send-notify.ts +75 -0
  162. package/src/node-types/validate-result.ts +49 -0
  163. package/src/templates/index.ts +5 -0
  164. package/src/templates/weaver-bot-template.ts +106 -0
  165. package/src/workflows/genesis-task.ts +91 -0
  166. package/src/workflows/index.ts +3 -0
  167. package/src/workflows/weaver-bot-batch.ts +65 -0
  168. package/src/workflows/weaver-bot.ts +79 -0
@@ -0,0 +1,169 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as crypto from 'node:crypto';
4
+ import * as os from 'node:os';
5
+ import type { RunRecord, RunFilter, RetentionPolicy } from './types.js';
6
+
7
+ export class RunStore {
8
+ private readonly dir: string;
9
+ private readonly filePath: string;
10
+
11
+ constructor(storeDir?: string) {
12
+ this.dir = storeDir ?? process.env.WEAVER_HISTORY_DIR ?? path.join(os.homedir(), '.weaver');
13
+ fs.mkdirSync(this.dir, { recursive: true });
14
+ this.filePath = path.join(this.dir, 'history.ndjson');
15
+ }
16
+
17
+ static newId(): string {
18
+ return crypto.randomUUID();
19
+ }
20
+
21
+ append(record: RunRecord): void {
22
+ fs.appendFileSync(this.filePath, JSON.stringify(record) + '\n', 'utf-8');
23
+ }
24
+
25
+ list(filter?: RunFilter): RunRecord[] {
26
+ const all = this.readAll();
27
+ let filtered = all;
28
+
29
+ if (filter?.workflowFile) {
30
+ const wf = filter.workflowFile;
31
+ filtered = filtered.filter((r) => r.workflowFile === wf);
32
+ }
33
+ if (filter?.outcome) {
34
+ const out = filter.outcome;
35
+ filtered = filtered.filter((r) => r.outcome === out);
36
+ }
37
+ if (filter?.success !== undefined) {
38
+ const s = filter.success;
39
+ filtered = filtered.filter((r) => r.success === s);
40
+ }
41
+ if (filter?.since) {
42
+ const since = filter.since;
43
+ filtered = filtered.filter((r) => r.startedAt >= since);
44
+ }
45
+ if (filter?.before) {
46
+ const before = filter.before;
47
+ filtered = filtered.filter((r) => r.startedAt <= before);
48
+ }
49
+
50
+ filtered.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
51
+
52
+ const limit = filter?.limit ?? 50;
53
+ return filtered.slice(0, limit);
54
+ }
55
+
56
+ get(idOrPrefix: string): RunRecord | null {
57
+ if (idOrPrefix.length < 4) {
58
+ throw new Error('ID prefix must be at least 4 characters');
59
+ }
60
+
61
+ const all = this.readAll();
62
+ const matches = all.filter((r) => r.id.startsWith(idOrPrefix));
63
+
64
+ if (matches.length === 0) return null;
65
+ if (matches.length === 1) return matches[0]!;
66
+
67
+ throw new Error(
68
+ `Ambiguous ID prefix "${idOrPrefix}" matches ${matches.length} runs: ` +
69
+ matches.map((r) => r.id).join(', '),
70
+ );
71
+ }
72
+
73
+ /** Mark a run as in-progress. Creates a small marker file that survives crashes. */
74
+ markRunning(runId: string, workflowFile: string): void {
75
+ const marker = path.join(this.dir, `running-${runId}.json`);
76
+ fs.writeFileSync(marker, JSON.stringify({ id: runId, workflowFile, startedAt: new Date().toISOString(), pid: process.pid }), 'utf-8');
77
+ }
78
+
79
+ /** Remove the in-progress marker after the run completes. */
80
+ clearRunning(runId: string): void {
81
+ const marker = path.join(this.dir, `running-${runId}.json`);
82
+ try { fs.unlinkSync(marker); } catch { /* already gone */ }
83
+ }
84
+
85
+ /** Check for orphaned in-progress markers (runs killed mid-execution). */
86
+ checkOrphans(): Array<{ id: string; workflowFile: string; startedAt: string; pid: number }> {
87
+ const orphans: Array<{ id: string; workflowFile: string; startedAt: string; pid: number }> = [];
88
+ try {
89
+ const files = fs.readdirSync(this.dir).filter((f) => f.startsWith('running-') && f.endsWith('.json'));
90
+ for (const file of files) {
91
+ try {
92
+ const data = JSON.parse(fs.readFileSync(path.join(this.dir, file), 'utf-8'));
93
+ // Check if the PID is still alive
94
+ let alive = false;
95
+ try { process.kill(data.pid, 0); alive = true; } catch { /* process gone */ }
96
+ if (!alive) {
97
+ orphans.push(data);
98
+ // Record the orphaned run as an error and clean up
99
+ this.append({
100
+ id: data.id,
101
+ workflowFile: data.workflowFile,
102
+ startedAt: data.startedAt,
103
+ finishedAt: new Date().toISOString(),
104
+ durationMs: Date.now() - new Date(data.startedAt).getTime(),
105
+ success: false,
106
+ outcome: 'error',
107
+ summary: 'Process killed during execution (recovered on next start)',
108
+ dryRun: false,
109
+ });
110
+ fs.unlinkSync(path.join(this.dir, file));
111
+ }
112
+ } catch { /* skip corrupt marker */ }
113
+ }
114
+ } catch { /* dir read failed */ }
115
+ return orphans;
116
+ }
117
+
118
+ prune(policy: RetentionPolicy): number {
119
+ const all = this.readAll();
120
+ if (all.length === 0) return 0;
121
+
122
+ all.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
123
+ let kept = [...all];
124
+
125
+ if (policy.maxAgeDays !== undefined) {
126
+ const cutoff = new Date(Date.now() - policy.maxAgeDays * 86_400_000).toISOString();
127
+ kept = kept.filter((r) => r.startedAt >= cutoff);
128
+ }
129
+
130
+ if (policy.maxRecords !== undefined) {
131
+ kept = kept.slice(0, policy.maxRecords);
132
+ }
133
+
134
+ const pruned = all.length - kept.length;
135
+ if (pruned === 0) return 0;
136
+
137
+ // Rewrite atomically: oldest first in file
138
+ kept.sort((a, b) => a.startedAt.localeCompare(b.startedAt));
139
+ const tmpPath = this.filePath + '.tmp';
140
+ fs.writeFileSync(tmpPath, kept.map((r) => JSON.stringify(r)).join('\n') + '\n', 'utf-8');
141
+ fs.renameSync(tmpPath, this.filePath);
142
+
143
+ return pruned;
144
+ }
145
+
146
+ clear(): boolean {
147
+ if (!fs.existsSync(this.filePath)) return false;
148
+ fs.unlinkSync(this.filePath);
149
+ return true;
150
+ }
151
+
152
+ private readAll(): RunRecord[] {
153
+ if (!fs.existsSync(this.filePath)) return [];
154
+
155
+ const content = fs.readFileSync(this.filePath, 'utf-8');
156
+ const lines = content.split('\n').filter((line) => line.trim().length > 0);
157
+ const records: RunRecord[] = [];
158
+
159
+ for (const line of lines) {
160
+ try {
161
+ records.push(JSON.parse(line) as RunRecord);
162
+ } catch {
163
+ console.error('[weaver] Skipping corrupt history line');
164
+ }
165
+ }
166
+
167
+ return records;
168
+ }
169
+ }
@@ -0,0 +1,311 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import type {
4
+ ApprovalMode,
5
+ AuditEventCallback,
6
+ BotConfig,
7
+ BotNotifyConfig,
8
+ ExecutionEvent,
9
+ RunOutcome,
10
+ WeaverConfig,
11
+ WorkflowResult,
12
+ } from './types.js';
13
+ import { initAuditLogger, auditEmit, teardownAuditLogger } from './audit-logger.js';
14
+ import {
15
+ createProvider,
16
+ resolveProviderConfig,
17
+ } from './agent-provider.js';
18
+ import { BotAgentChannel } from './bot-agent-channel.js';
19
+ import {
20
+ WebhookNotificationChannel,
21
+ createNotifier,
22
+ } from './notifications.js';
23
+ import type { NotificationErrorHandler } from './notifications.js';
24
+ import { RunStore } from './run-store.js';
25
+ import { CostTracker } from './cost-tracker.js';
26
+ import { CostStore } from './cost-store.js';
27
+
28
+ function resolveApproval(
29
+ approval: BotConfig['approval'],
30
+ ): { mode: ApprovalMode; timeoutSeconds: number; webhookUrl?: string; webOpen?: boolean } {
31
+ if (!approval || approval === 'auto') {
32
+ return { mode: 'auto', timeoutSeconds: 300 };
33
+ }
34
+ if (typeof approval === 'string') {
35
+ return { mode: approval, timeoutSeconds: 300 };
36
+ }
37
+ return {
38
+ mode: approval.mode,
39
+ timeoutSeconds: approval.timeoutSeconds ?? 300,
40
+ webhookUrl: approval.webhookUrl,
41
+ webOpen: approval.webOpen,
42
+ };
43
+ }
44
+
45
+ function resolveNotify(
46
+ notify: BotConfig['notify'],
47
+ ): BotNotifyConfig[] {
48
+ if (!notify) return [];
49
+ return Array.isArray(notify) ? notify : [notify];
50
+ }
51
+
52
+ function resolveWeaverConfig(
53
+ filePath: string,
54
+ explicit?: WeaverConfig,
55
+ ): WeaverConfig {
56
+ if (explicit) return explicit;
57
+
58
+ const dir = path.dirname(filePath);
59
+ const localConfig = path.join(dir, '.weaver.json');
60
+ if (fs.existsSync(localConfig)) {
61
+ return JSON.parse(fs.readFileSync(localConfig, 'utf-8'));
62
+ }
63
+
64
+ const cwdConfig = path.join(process.cwd(), '.weaver.json');
65
+ if (fs.existsSync(cwdConfig)) {
66
+ return JSON.parse(fs.readFileSync(cwdConfig, 'utf-8'));
67
+ }
68
+
69
+ return { provider: 'auto' };
70
+ }
71
+
72
+ function buildSummary(result: unknown): string {
73
+ if (!result || typeof result !== 'object') return String(result);
74
+
75
+ const r = result as Record<string, unknown>;
76
+ if (typeof r.summary === 'string') return r.summary;
77
+
78
+ // Build a meaningful summary from whatever the workflow returned
79
+ const parts: string[] = [];
80
+ for (const [key, value] of Object.entries(r)) {
81
+ if (key === 'onSuccess' || key === 'onFailure') continue;
82
+ if (value === null || value === undefined) continue;
83
+ const str = typeof value === 'string' ? value : JSON.stringify(value);
84
+ parts.push(`${key}: ${str.length > 100 ? str.slice(0, 100) + '...' : str}`);
85
+ }
86
+ return parts.length > 0 ? parts.join(', ') : 'completed';
87
+ }
88
+
89
+ export async function runWorkflow(
90
+ filePath: string,
91
+ options?: {
92
+ params?: Record<string, unknown>;
93
+ verbose?: boolean;
94
+ dryRun?: boolean;
95
+ config?: WeaverConfig;
96
+ onEvent?: (event: ExecutionEvent) => void;
97
+ onAuditEvent?: AuditEventCallback;
98
+ onNotificationError?: NotificationErrorHandler;
99
+ dashboardServer?: import('./dashboard.js').DashboardServer;
100
+ },
101
+ ): Promise<WorkflowResult> {
102
+ const absPath = path.resolve(filePath);
103
+ const verbose = options?.verbose ?? false;
104
+
105
+ let store: RunStore | null = null;
106
+ try { store = new RunStore(); } catch { /* non-fatal */ }
107
+ const runId = RunStore.newId();
108
+ const startedAt = new Date().toISOString();
109
+ initAuditLogger(runId, options?.onAuditEvent);
110
+
111
+ // Mark run as in-progress so abrupt kills leave a trace
112
+ try { store?.markRunning(runId, absPath); } catch { /* non-fatal */ }
113
+
114
+ if (!fs.existsSync(absPath)) {
115
+ throw new Error(`Workflow file not found: ${absPath}`);
116
+ }
117
+
118
+ const config = resolveWeaverConfig(absPath, options?.config);
119
+ const providerConfig = resolveProviderConfig(config.provider);
120
+ const approvalConfig = resolveApproval(config.approval);
121
+ const notifyConfigs = resolveNotify(config.notify);
122
+
123
+ const provider = await createProvider(providerConfig);
124
+
125
+ const costTracker = new CostTracker(providerConfig.model ?? 'unknown', providerConfig.name);
126
+ provider.onUsage = (step, model, usage) => costTracker.track(step, model, usage);
127
+ const channels = notifyConfigs.map(
128
+ (c) => new WebhookNotificationChannel(c, options?.onNotificationError),
129
+ );
130
+ const notifier = createNotifier(channels);
131
+
132
+ const projectDir = path.dirname(absPath);
133
+
134
+ if (verbose) {
135
+ console.log(`[weaver] Workflow: ${absPath}`);
136
+ const providerLabel = providerConfig.model
137
+ ? `${providerConfig.name} (${providerConfig.model})`
138
+ : providerConfig.name;
139
+ console.log(`[weaver] Provider: ${providerLabel}`);
140
+ console.log(`[weaver] Approval: ${approvalConfig.mode}`);
141
+ console.log(`[weaver] Notifications: ${channels.length} channel(s)`);
142
+ }
143
+
144
+ auditEmit('run-start', { workflowFile: absPath, provider: providerConfig.name, projectDir });
145
+
146
+ await notifier({
147
+ type: 'workflow-start',
148
+ workflowFile: absPath,
149
+ projectDir,
150
+ });
151
+
152
+ const botChannel = new BotAgentChannel(provider, {
153
+ approvalMode: approvalConfig.mode,
154
+ approvalTimeoutSeconds: approvalConfig.timeoutSeconds,
155
+ approvalWebhookUrl: approvalConfig.webhookUrl,
156
+ approvalWebOpen: approvalConfig.webOpen,
157
+ dashboardServer: options?.dashboardServer,
158
+ notifier,
159
+ context: { projectDir, workflowFile: absPath },
160
+ });
161
+
162
+ try {
163
+ const mod = '@synergenius/flow-weaver/executor';
164
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
165
+ const { executeWorkflowFromFile } = await (import(mod) as Promise<any>);
166
+
167
+ if (options?.dryRun) {
168
+ if (verbose) console.log('[weaver] Dry run, skipping execution');
169
+ const dryResult: WorkflowResult = { success: true, summary: 'Dry run', outcome: 'skipped' };
170
+ recordRun(store, { id: runId, workflowFile: absPath, startedAt, success: true, outcome: 'skipped', summary: 'Dry run', dryRun: true, provider: providerConfig.name, params: options?.params }, verbose);
171
+ return dryResult;
172
+ }
173
+
174
+ // Forward trace events as ExecutionEvents
175
+ const onTraceEvent = options?.onEvent
176
+ ? (traceEvent: { type: string; timestamp: number; data?: Record<string, unknown> }) => {
177
+ if (traceEvent.type !== 'STATUS_CHANGED' || !traceEvent.data) return;
178
+ const nodeId = traceEvent.data.id as string | undefined;
179
+ const status = traceEvent.data.status as string | undefined;
180
+ if (!nodeId || !status) return;
181
+
182
+ let eventType: ExecutionEvent['type'] | null = null;
183
+ if (status === 'RUNNING') eventType = 'node-start';
184
+ else if (status === 'SUCCEEDED') eventType = 'node-complete';
185
+ else if (status === 'FAILED') eventType = 'node-error';
186
+
187
+ if (eventType) {
188
+ options.onEvent!({
189
+ type: eventType,
190
+ nodeId,
191
+ nodeType: traceEvent.data.nodeTypeName as string | undefined,
192
+ timestamp: traceEvent.timestamp,
193
+ error: traceEvent.data.error as string | undefined,
194
+ });
195
+ }
196
+ }
197
+ : undefined;
198
+
199
+ const execResult = await executeWorkflowFromFile(
200
+ absPath,
201
+ options?.params ?? {},
202
+ {
203
+ agentChannel: botChannel,
204
+ includeTrace: !!onTraceEvent,
205
+ production: !onTraceEvent,
206
+ onEvent: onTraceEvent,
207
+ },
208
+ );
209
+
210
+ const result = execResult.result as Record<string, unknown> | null;
211
+ const success = (result?.onSuccess as boolean) ?? false;
212
+ const summary = buildSummary(result);
213
+ const outcome = success ? 'completed' : 'failed';
214
+
215
+ await notifier({
216
+ type: 'workflow-complete',
217
+ workflowFile: absPath,
218
+ projectDir,
219
+ summary,
220
+ outcome,
221
+ });
222
+
223
+ const costSummary = costTracker.hasEntries() ? costTracker.getRunSummary() : undefined;
224
+ persistCost(costSummary, absPath, providerConfig.name, verbose);
225
+ recordRun(store, {
226
+ id: runId, workflowFile: absPath, startedAt, success, outcome: outcome as RunOutcome, summary,
227
+ functionName: execResult.functionName, executionTime: execResult.executionTime,
228
+ dryRun: false, provider: providerConfig.name, params: options?.params,
229
+ }, verbose);
230
+
231
+ auditEmit('run-complete', { success, outcome, summary });
232
+
233
+ return {
234
+ success,
235
+ summary,
236
+ outcome,
237
+ functionName: execResult.functionName,
238
+ executionTime: execResult.executionTime,
239
+ cost: costSummary,
240
+ };
241
+ } catch (err: unknown) {
242
+ const msg = err instanceof Error ? err.message : String(err);
243
+
244
+ await notifier({
245
+ type: 'error',
246
+ workflowFile: absPath,
247
+ projectDir,
248
+ error: msg,
249
+ });
250
+
251
+ const costSummary = costTracker.hasEntries() ? costTracker.getRunSummary() : undefined;
252
+ persistCost(costSummary, absPath, providerConfig.name, verbose);
253
+ recordRun(store, {
254
+ id: runId, workflowFile: absPath, startedAt, success: false, outcome: 'error', summary: msg,
255
+ dryRun: options?.dryRun ?? false, provider: providerConfig.name, params: options?.params,
256
+ }, verbose);
257
+
258
+ auditEmit('run-complete', { success: false, error: msg });
259
+
260
+ return { success: false, summary: msg, outcome: 'error', cost: costSummary };
261
+ } finally {
262
+ teardownAuditLogger();
263
+ }
264
+ }
265
+
266
+ function recordRun(
267
+ store: RunStore | null,
268
+ data: {
269
+ id: string; workflowFile: string; startedAt: string; success: boolean;
270
+ outcome: RunOutcome; summary: string; functionName?: string;
271
+ executionTime?: number; dryRun: boolean; provider?: string;
272
+ params?: Record<string, unknown>;
273
+ },
274
+ verbose: boolean,
275
+ ): void {
276
+ if (!store) return;
277
+ const finishedAt = new Date().toISOString();
278
+ try {
279
+ store.append({
280
+ ...data,
281
+ finishedAt,
282
+ durationMs: new Date(finishedAt).getTime() - new Date(data.startedAt).getTime(),
283
+ });
284
+ store.clearRunning(data.id);
285
+ } catch (err) {
286
+ if (verbose) console.error(`[weaver] Failed to record run history: ${err}`);
287
+ }
288
+ }
289
+
290
+ function persistCost(
291
+ costSummary: import('./types.js').RunCostSummary | undefined,
292
+ workflowFile: string,
293
+ provider: string,
294
+ verbose: boolean,
295
+ ): void {
296
+ if (!costSummary || costSummary.totalInputTokens === 0) return;
297
+ try {
298
+ new CostStore().append({
299
+ timestamp: Date.now(),
300
+ workflowFile,
301
+ provider,
302
+ model: costSummary.model,
303
+ inputTokens: costSummary.totalInputTokens,
304
+ outputTokens: costSummary.totalOutputTokens,
305
+ estimatedCost: costSummary.totalCost,
306
+ steps: costSummary.entries.length,
307
+ });
308
+ } catch (err) {
309
+ if (verbose) console.error(`[weaver] Failed to persist cost data: ${err}`);
310
+ }
311
+ }
@@ -0,0 +1,73 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ import * as crypto from 'node:crypto';
5
+ import { withFileLock } from './file-lock.js';
6
+
7
+ export interface SessionState {
8
+ sessionId: string;
9
+ status: 'idle' | 'planning' | 'executing' | 'validating' | 'waiting-approval' | 'paused' | 'fixing';
10
+ currentTask: string | null;
11
+ completedTasks: number;
12
+ totalCost: number;
13
+ startedAt: number;
14
+ lastActivity: number;
15
+ }
16
+
17
+ export class SessionStore {
18
+ private filePath: string;
19
+
20
+ constructor(dir?: string) {
21
+ const base = dir ?? path.join(os.homedir(), '.weaver');
22
+ this.filePath = path.join(base, 'session.json');
23
+ }
24
+
25
+ async create(): Promise<SessionState> {
26
+ const state: SessionState = {
27
+ sessionId: crypto.randomUUID().slice(0, 8),
28
+ status: 'idle',
29
+ currentTask: null,
30
+ completedTasks: 0,
31
+ totalCost: 0,
32
+ startedAt: Date.now(),
33
+ lastActivity: Date.now(),
34
+ };
35
+ await this.save(state);
36
+ return state;
37
+ }
38
+
39
+ load(): SessionState | null {
40
+ try {
41
+ if (!fs.existsSync(this.filePath)) return null;
42
+ return JSON.parse(fs.readFileSync(this.filePath, 'utf-8')) as SessionState;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ async save(state: SessionState): Promise<void> {
49
+ return withFileLock(this.filePath, () => {
50
+ const dir = path.dirname(this.filePath);
51
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
52
+ state.lastActivity = Date.now();
53
+ fs.writeFileSync(this.filePath, JSON.stringify(state, null, 2), 'utf-8');
54
+ });
55
+ }
56
+
57
+ async update(patch: Partial<SessionState>): Promise<SessionState | null> {
58
+ return withFileLock(this.filePath, () => {
59
+ const state = this.load();
60
+ if (!state) return null;
61
+ Object.assign(state, patch);
62
+ const dir = path.dirname(this.filePath);
63
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
64
+ state.lastActivity = Date.now();
65
+ fs.writeFileSync(this.filePath, JSON.stringify(state, null, 2), 'utf-8');
66
+ return state;
67
+ });
68
+ }
69
+
70
+ clear(): void {
71
+ try { fs.unlinkSync(this.filePath); } catch { /* ignore */ }
72
+ }
73
+ }
@@ -0,0 +1,44 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ import { withFileLock } from './file-lock.js';
5
+
6
+ export interface SteeringCommand {
7
+ command: 'pause' | 'resume' | 'cancel' | 'redirect' | 'queue';
8
+ payload?: string;
9
+ timestamp: number;
10
+ }
11
+
12
+ export class SteeringController {
13
+ private controlPath: string;
14
+
15
+ constructor(controlDir?: string) {
16
+ const dir = controlDir ?? path.join(os.homedir(), '.weaver');
17
+ this.controlPath = path.join(dir, 'control.json');
18
+ }
19
+
20
+ async check(): Promise<SteeringCommand | null> {
21
+ return withFileLock(this.controlPath, () => {
22
+ try {
23
+ if (!fs.existsSync(this.controlPath)) return null;
24
+ const raw = fs.readFileSync(this.controlPath, 'utf-8');
25
+ fs.unlinkSync(this.controlPath);
26
+ return JSON.parse(raw) as SteeringCommand;
27
+ } catch {
28
+ return null;
29
+ }
30
+ });
31
+ }
32
+
33
+ async write(command: SteeringCommand): Promise<void> {
34
+ return withFileLock(this.controlPath, () => {
35
+ const dir = path.dirname(this.controlPath);
36
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
37
+ fs.writeFileSync(this.controlPath, JSON.stringify(command, null, 2), 'utf-8');
38
+ });
39
+ }
40
+
41
+ clear(): void {
42
+ try { fs.unlinkSync(this.controlPath); } catch { /* ignore */ }
43
+ }
44
+ }
@@ -0,0 +1,34 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { runCommand } from '@synergenius/flow-weaver';
4
+
5
+ export async function executeStep(
6
+ step: { operation: string; args: Record<string, unknown> },
7
+ projectDir: string,
8
+ ): Promise<{ file?: string; files?: string[]; created?: boolean; output?: string }> {
9
+ const args = step.args;
10
+ const file = args.file as string | undefined;
11
+
12
+ switch (step.operation) {
13
+ case 'write-file':
14
+ case 'create-workflow':
15
+ case 'modify-source':
16
+ case 'implement-node': {
17
+ const filePath = path.resolve(projectDir, file!);
18
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
19
+ const existed = fs.existsSync(filePath);
20
+ fs.writeFileSync(filePath, (args.content as string) ?? (args.body as string) ?? '', 'utf-8');
21
+ return { file: filePath, created: !existed };
22
+ }
23
+ case 'read-file':
24
+ return {};
25
+ default: {
26
+ const result = await runCommand(step.operation, { ...args, cwd: projectDir });
27
+ return {
28
+ file: result.files?.[0],
29
+ files: result.files,
30
+ output: result.output ?? (result.data ? JSON.stringify(result.data, null, 2) : undefined),
31
+ };
32
+ }
33
+ }
34
+ }