@synergenius/flow-weaver-pack-weaver 0.8.3 → 0.9.3

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 (265) hide show
  1. package/dist/bot/ai-client.d.ts +22 -2
  2. package/dist/bot/ai-client.d.ts.map +1 -1
  3. package/dist/bot/ai-client.js +168 -20
  4. package/dist/bot/ai-client.js.map +1 -1
  5. package/dist/bot/assistant-core.d.ts +25 -0
  6. package/dist/bot/assistant-core.d.ts.map +1 -0
  7. package/dist/bot/assistant-core.js +265 -0
  8. package/dist/bot/assistant-core.js.map +1 -0
  9. package/dist/bot/assistant-tools.d.ts +9 -0
  10. package/dist/bot/assistant-tools.d.ts.map +1 -0
  11. package/dist/bot/assistant-tools.js +602 -0
  12. package/dist/bot/assistant-tools.js.map +1 -0
  13. package/dist/bot/audit-logger.d.ts.map +1 -1
  14. package/dist/bot/audit-logger.js +9 -5
  15. package/dist/bot/audit-logger.js.map +1 -1
  16. package/dist/bot/audit-store.d.ts.map +1 -1
  17. package/dist/bot/audit-store.js +3 -11
  18. package/dist/bot/audit-store.js.map +1 -1
  19. package/dist/bot/bot-manager.d.ts +49 -0
  20. package/dist/bot/bot-manager.d.ts.map +1 -0
  21. package/dist/bot/bot-manager.js +279 -0
  22. package/dist/bot/bot-manager.js.map +1 -0
  23. package/dist/bot/child-process-tracker.d.ts +6 -0
  24. package/dist/bot/child-process-tracker.d.ts.map +1 -0
  25. package/dist/bot/child-process-tracker.js +35 -0
  26. package/dist/bot/child-process-tracker.js.map +1 -0
  27. package/dist/bot/cli-provider.d.ts.map +1 -1
  28. package/dist/bot/cli-provider.js +13 -8
  29. package/dist/bot/cli-provider.js.map +1 -1
  30. package/dist/bot/conversation-store.d.ts +40 -0
  31. package/dist/bot/conversation-store.d.ts.map +1 -0
  32. package/dist/bot/conversation-store.js +182 -0
  33. package/dist/bot/conversation-store.js.map +1 -0
  34. package/dist/bot/cost-store.d.ts.map +1 -1
  35. package/dist/bot/cost-store.js +10 -14
  36. package/dist/bot/cost-store.js.map +1 -1
  37. package/dist/bot/error-guide.d.ts +10 -0
  38. package/dist/bot/error-guide.d.ts.map +1 -0
  39. package/dist/bot/error-guide.js +34 -0
  40. package/dist/bot/error-guide.js.map +1 -0
  41. package/dist/bot/genesis-store.d.ts.map +1 -1
  42. package/dist/bot/genesis-store.js +11 -20
  43. package/dist/bot/genesis-store.js.map +1 -1
  44. package/dist/bot/index.d.ts +3 -0
  45. package/dist/bot/index.d.ts.map +1 -1
  46. package/dist/bot/index.js +3 -0
  47. package/dist/bot/index.js.map +1 -1
  48. package/dist/bot/knowledge-store.d.ts +17 -0
  49. package/dist/bot/knowledge-store.d.ts.map +1 -0
  50. package/dist/bot/knowledge-store.js +53 -0
  51. package/dist/bot/knowledge-store.js.map +1 -0
  52. package/dist/bot/pipeline-runner.d.ts.map +1 -1
  53. package/dist/bot/pipeline-runner.js +8 -1
  54. package/dist/bot/pipeline-runner.js.map +1 -1
  55. package/dist/bot/retry-utils.d.ts +19 -0
  56. package/dist/bot/retry-utils.d.ts.map +1 -0
  57. package/dist/bot/retry-utils.js +64 -0
  58. package/dist/bot/retry-utils.js.map +1 -0
  59. package/dist/bot/run-store.d.ts.map +1 -1
  60. package/dist/bot/run-store.js +2 -10
  61. package/dist/bot/run-store.js.map +1 -1
  62. package/dist/bot/runner.d.ts.map +1 -1
  63. package/dist/bot/runner.js +24 -3
  64. package/dist/bot/runner.js.map +1 -1
  65. package/dist/bot/safe-json.d.ts +32 -0
  66. package/dist/bot/safe-json.d.ts.map +1 -0
  67. package/dist/bot/safe-json.js +56 -0
  68. package/dist/bot/safe-json.js.map +1 -0
  69. package/dist/bot/safe-path.d.ts +18 -0
  70. package/dist/bot/safe-path.d.ts.map +1 -0
  71. package/dist/bot/safe-path.js +40 -0
  72. package/dist/bot/safe-path.js.map +1 -0
  73. package/dist/bot/session-state.d.ts.map +1 -1
  74. package/dist/bot/session-state.js +3 -1
  75. package/dist/bot/session-state.js.map +1 -1
  76. package/dist/bot/steering.js +1 -1
  77. package/dist/bot/steering.js.map +1 -1
  78. package/dist/bot/step-executor.d.ts +10 -5
  79. package/dist/bot/step-executor.d.ts.map +1 -1
  80. package/dist/bot/step-executor.js +252 -3
  81. package/dist/bot/step-executor.js.map +1 -1
  82. package/dist/bot/system-prompt.d.ts +1 -1
  83. package/dist/bot/system-prompt.d.ts.map +1 -1
  84. package/dist/bot/system-prompt.js +69 -43
  85. package/dist/bot/system-prompt.js.map +1 -1
  86. package/dist/bot/task-decomposer.d.ts +24 -0
  87. package/dist/bot/task-decomposer.d.ts.map +1 -0
  88. package/dist/bot/task-decomposer.js +75 -0
  89. package/dist/bot/task-decomposer.js.map +1 -0
  90. package/dist/bot/task-queue.d.ts +17 -4
  91. package/dist/bot/task-queue.d.ts.map +1 -1
  92. package/dist/bot/task-queue.js +102 -14
  93. package/dist/bot/task-queue.js.map +1 -1
  94. package/dist/bot/terminal-renderer.d.ts +60 -0
  95. package/dist/bot/terminal-renderer.d.ts.map +1 -0
  96. package/dist/bot/terminal-renderer.js +205 -0
  97. package/dist/bot/terminal-renderer.js.map +1 -0
  98. package/dist/bot/types.d.ts +7 -0
  99. package/dist/bot/types.d.ts.map +1 -1
  100. package/dist/bot/weaver-tools.d.ts +18 -0
  101. package/dist/bot/weaver-tools.d.ts.map +1 -0
  102. package/dist/bot/weaver-tools.js +215 -0
  103. package/dist/bot/weaver-tools.js.map +1 -0
  104. package/dist/cli-bridge.d.ts.map +1 -1
  105. package/dist/cli-bridge.js +10 -3
  106. package/dist/cli-bridge.js.map +1 -1
  107. package/dist/cli-handlers.d.ts +15 -1
  108. package/dist/cli-handlers.d.ts.map +1 -1
  109. package/dist/cli-handlers.js +742 -28
  110. package/dist/cli-handlers.js.map +1 -1
  111. package/dist/handlers/on-bot-completed.d.ts +21 -0
  112. package/dist/handlers/on-bot-completed.d.ts.map +1 -0
  113. package/dist/handlers/on-bot-completed.js +28 -0
  114. package/dist/handlers/on-bot-completed.js.map +1 -0
  115. package/dist/handlers/on-execution-failure.d.ts +23 -0
  116. package/dist/handlers/on-execution-failure.d.ts.map +1 -0
  117. package/dist/handlers/on-execution-failure.js +28 -0
  118. package/dist/handlers/on-execution-failure.js.map +1 -0
  119. package/dist/handlers/scheduled-run.d.ts +24 -0
  120. package/dist/handlers/scheduled-run.d.ts.map +1 -0
  121. package/dist/handlers/scheduled-run.js +25 -0
  122. package/dist/handlers/scheduled-run.js.map +1 -0
  123. package/dist/index.d.ts +3 -0
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +4 -0
  126. package/dist/index.js.map +1 -1
  127. package/dist/mcp-tools.js +2 -2
  128. package/dist/mcp-tools.js.map +1 -1
  129. package/dist/node-types/abort-task.d.ts.map +1 -1
  130. package/dist/node-types/abort-task.js +4 -3
  131. package/dist/node-types/abort-task.js.map +1 -1
  132. package/dist/node-types/agent-execute.d.ts +38 -0
  133. package/dist/node-types/agent-execute.d.ts.map +1 -0
  134. package/dist/node-types/agent-execute.js +256 -0
  135. package/dist/node-types/agent-execute.js.map +1 -0
  136. package/dist/node-types/bot-report.d.ts +5 -3
  137. package/dist/node-types/bot-report.d.ts.map +1 -1
  138. package/dist/node-types/bot-report.js +39 -7
  139. package/dist/node-types/bot-report.js.map +1 -1
  140. package/dist/node-types/build-context.d.ts +3 -3
  141. package/dist/node-types/build-context.d.ts.map +1 -1
  142. package/dist/node-types/build-context.js +108 -24
  143. package/dist/node-types/build-context.js.map +1 -1
  144. package/dist/node-types/detect-provider.d.ts +2 -2
  145. package/dist/node-types/detect-provider.d.ts.map +1 -1
  146. package/dist/node-types/detect-provider.js +3 -1
  147. package/dist/node-types/detect-provider.js.map +1 -1
  148. package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
  149. package/dist/node-types/exec-validate-retry.js +43 -6
  150. package/dist/node-types/exec-validate-retry.js.map +1 -1
  151. package/dist/node-types/execute-plan.d.ts.map +1 -1
  152. package/dist/node-types/execute-plan.js +31 -8
  153. package/dist/node-types/execute-plan.js.map +1 -1
  154. package/dist/node-types/execute-target.d.ts.map +1 -1
  155. package/dist/node-types/execute-target.js +3 -1
  156. package/dist/node-types/execute-target.js.map +1 -1
  157. package/dist/node-types/fix-errors.d.ts.map +1 -1
  158. package/dist/node-types/fix-errors.js +21 -5
  159. package/dist/node-types/fix-errors.js.map +1 -1
  160. package/dist/node-types/genesis-observe.d.ts.map +1 -1
  161. package/dist/node-types/genesis-observe.js +3 -1
  162. package/dist/node-types/genesis-observe.js.map +1 -1
  163. package/dist/node-types/genesis-report.js +4 -1
  164. package/dist/node-types/genesis-report.js.map +1 -1
  165. package/dist/node-types/git-ops.d.ts.map +1 -1
  166. package/dist/node-types/git-ops.js +98 -4
  167. package/dist/node-types/git-ops.js.map +1 -1
  168. package/dist/node-types/index.d.ts +2 -0
  169. package/dist/node-types/index.d.ts.map +1 -1
  170. package/dist/node-types/index.js +2 -0
  171. package/dist/node-types/index.js.map +1 -1
  172. package/dist/node-types/load-config.d.ts +2 -2
  173. package/dist/node-types/load-config.d.ts.map +1 -1
  174. package/dist/node-types/load-config.js.map +1 -1
  175. package/dist/node-types/plan-task.d.ts.map +1 -1
  176. package/dist/node-types/plan-task.js +14 -2
  177. package/dist/node-types/plan-task.js.map +1 -1
  178. package/dist/node-types/read-workflow.js +8 -2
  179. package/dist/node-types/read-workflow.js.map +1 -1
  180. package/dist/node-types/receive-task.d.ts.map +1 -1
  181. package/dist/node-types/receive-task.js +35 -26
  182. package/dist/node-types/receive-task.js.map +1 -1
  183. package/dist/node-types/send-notify.js +2 -1
  184. package/dist/node-types/send-notify.js.map +1 -1
  185. package/dist/node-types/validate-gate.d.ts +18 -0
  186. package/dist/node-types/validate-gate.d.ts.map +1 -0
  187. package/dist/node-types/validate-gate.js +96 -0
  188. package/dist/node-types/validate-gate.js.map +1 -0
  189. package/dist/workflows/genesis-task.d.ts +20 -12
  190. package/dist/workflows/genesis-task.d.ts.map +1 -1
  191. package/dist/workflows/genesis-task.js +20 -12
  192. package/dist/workflows/genesis-task.js.map +1 -1
  193. package/dist/workflows/weaver-agent.d.ts +35 -0
  194. package/dist/workflows/weaver-agent.d.ts.map +1 -0
  195. package/dist/workflows/weaver-agent.js +777 -0
  196. package/dist/workflows/weaver-agent.js.map +1 -0
  197. package/dist/workflows/weaver-bot-batch.d.ts +19 -26
  198. package/dist/workflows/weaver-bot-batch.d.ts.map +1 -1
  199. package/dist/workflows/weaver-bot-batch.js +1043 -27
  200. package/dist/workflows/weaver-bot-batch.js.map +1 -1
  201. package/dist/workflows/weaver-bot.d.ts +21 -35
  202. package/dist/workflows/weaver-bot.d.ts.map +1 -1
  203. package/dist/workflows/weaver-bot.js +1119 -36
  204. package/dist/workflows/weaver-bot.js.map +1 -1
  205. package/flowweaver.manifest.json +113 -2
  206. package/package.json +5 -2
  207. package/src/bot/ai-client.ts +180 -19
  208. package/src/bot/assistant-core.ts +306 -0
  209. package/src/bot/assistant-tools.ts +605 -0
  210. package/src/bot/audit-logger.ts +6 -5
  211. package/src/bot/audit-store.ts +3 -12
  212. package/src/bot/bot-manager.ts +293 -0
  213. package/src/bot/child-process-tracker.ts +40 -0
  214. package/src/bot/cli-provider.ts +13 -8
  215. package/src/bot/conversation-store.ts +222 -0
  216. package/src/bot/cost-store.ts +11 -12
  217. package/src/bot/error-guide.ts +34 -0
  218. package/src/bot/genesis-store.ts +11 -17
  219. package/src/bot/index.ts +5 -0
  220. package/src/bot/knowledge-store.ts +59 -0
  221. package/src/bot/pipeline-runner.ts +7 -1
  222. package/src/bot/retry-utils.ts +76 -0
  223. package/src/bot/run-store.ts +2 -11
  224. package/src/bot/runner.ts +26 -3
  225. package/src/bot/safe-json.ts +76 -0
  226. package/src/bot/safe-path.ts +44 -0
  227. package/src/bot/session-state.ts +2 -1
  228. package/src/bot/steering.ts +1 -1
  229. package/src/bot/step-executor.ts +313 -5
  230. package/src/bot/system-prompt.ts +70 -47
  231. package/src/bot/task-decomposer.ts +100 -0
  232. package/src/bot/task-queue.ts +119 -15
  233. package/src/bot/terminal-renderer.ts +241 -0
  234. package/src/bot/types.ts +8 -0
  235. package/src/bot/weaver-tools.ts +225 -0
  236. package/src/cli-bridge.ts +14 -3
  237. package/src/cli-handlers.ts +760 -29
  238. package/src/handlers/on-bot-completed.ts +48 -0
  239. package/src/handlers/on-execution-failure.ts +42 -0
  240. package/src/handlers/scheduled-run.ts +42 -0
  241. package/src/index.ts +5 -0
  242. package/src/mcp-tools.ts +2 -2
  243. package/src/node-types/abort-task.ts +5 -4
  244. package/src/node-types/agent-execute.ts +306 -0
  245. package/src/node-types/bot-report.ts +40 -9
  246. package/src/node-types/build-context.ts +112 -25
  247. package/src/node-types/detect-provider.ts +4 -3
  248. package/src/node-types/exec-validate-retry.ts +47 -8
  249. package/src/node-types/execute-plan.ts +32 -8
  250. package/src/node-types/execute-target.ts +2 -1
  251. package/src/node-types/fix-errors.ts +20 -5
  252. package/src/node-types/genesis-observe.ts +2 -1
  253. package/src/node-types/genesis-report.ts +1 -1
  254. package/src/node-types/git-ops.ts +93 -4
  255. package/src/node-types/index.ts +2 -0
  256. package/src/node-types/load-config.ts +3 -3
  257. package/src/node-types/plan-task.ts +15 -3
  258. package/src/node-types/read-workflow.ts +2 -2
  259. package/src/node-types/receive-task.ts +31 -26
  260. package/src/node-types/send-notify.ts +1 -1
  261. package/src/node-types/validate-gate.ts +112 -0
  262. package/src/workflows/genesis-task.ts +20 -12
  263. package/src/workflows/weaver-agent.ts +799 -0
  264. package/src/workflows/weaver-bot-batch.ts +1049 -27
  265. package/src/workflows/weaver-bot.ts +1123 -36
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Actionable error guidance — maps cryptic error messages
3
+ * to human-readable explanations with fix suggestions.
4
+ */
5
+
6
+ const GUIDES: Array<{ pattern: RegExp; guidance: string }> = [
7
+ { pattern: /ETIMEDOUT/i, guidance: 'Network timeout. Check internet connection or increase timeout with --timeout.' },
8
+ { pattern: /ECONNRESET/i, guidance: 'Connection reset. The server closed the connection. Retry in a few seconds.' },
9
+ { pattern: /ECONNREFUSED/i, guidance: 'Connection refused. Is the service running? Check the provider URL.' },
10
+ { pattern: /401|authentication|invalid.*key/i, guidance: 'Authentication failed. Check ANTHROPIC_API_KEY or run "weaver init" to reconfigure.' },
11
+ { pattern: /403|forbidden/i, guidance: 'Access denied. Your API key may not have permission for this model.' },
12
+ { pattern: /429|rate.?limit|too many requests/i, guidance: 'Rate limited. Wait a few minutes or reduce --parallel.' },
13
+ { pattern: /502|bad gateway/i, guidance: 'Server error (502). The API is temporarily unavailable. Will auto-retry.' },
14
+ { pattern: /503|service unavailable|overloaded/i, guidance: 'Service overloaded. Will auto-retry with backoff.' },
15
+ { pattern: /exit code 143/i, guidance: 'Process was killed (SIGTERM). Likely our timeout or Ctrl+C.' },
16
+ { pattern: /exit code 137/i, guidance: 'Process was killed (OOM or SIGKILL). System may be low on memory.' },
17
+ { pattern: /ENOMEM/i, guidance: 'Out of memory. Close other applications or increase available RAM.' },
18
+ { pattern: /ENOSPC/i, guidance: 'Disk full. Free up disk space.' },
19
+ { pattern: /lock.*retries|failed to acquire.*lock/i, guidance: 'File lock contention. Another weaver process may be running. Check with "ps aux | grep weaver".' },
20
+ { pattern: /not a workflow|No @flowWeaver/i, guidance: 'File is not a Flow Weaver workflow. Ensure it has @flowWeaver annotations.' },
21
+ { pattern: /parse.*json|unexpected token/i, guidance: 'JSON parse error. The AI may have returned malformed output. Retry the task.' },
22
+ { pattern: /Queue full/i, guidance: 'Too many pending tasks (200 max). Process or clear existing tasks first.' },
23
+ ];
24
+
25
+ /**
26
+ * Get actionable guidance for an error message.
27
+ * Returns null if no guidance is available.
28
+ */
29
+ export function getErrorGuidance(msg: string): string | null {
30
+ for (const { pattern, guidance } of GUIDES) {
31
+ if (pattern.test(msg)) return guidance;
32
+ }
33
+ return null;
34
+ }
@@ -2,6 +2,7 @@ import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import * as crypto from 'node:crypto';
4
4
  import type { GenesisConfig, GenesisHistory, GenesisCycleRecord, GenesisFingerprint, EscrowToken, GenesisSelfMigrationRecord } from './types.js';
5
+ import { jsonParseOr } from './safe-json.js';
5
6
 
6
7
  const DEFAULT_CONFIG: GenesisConfig = {
7
8
  intent: 'Improve workflow reliability and efficiency',
@@ -32,7 +33,8 @@ export class GenesisStore {
32
33
  fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2), 'utf-8');
33
34
  return { ...DEFAULT_CONFIG };
34
35
  }
35
- const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
36
+ const content = fs.readFileSync(configPath, 'utf-8');
37
+ const raw = jsonParseOr(content, {} as Record<string, unknown>, 'genesis config');
36
38
  return { ...DEFAULT_CONFIG, ...raw };
37
39
  }
38
40
 
@@ -46,7 +48,8 @@ export class GenesisStore {
46
48
  if (!fs.existsSync(historyPath)) {
47
49
  return { configHash: '', cycles: [] };
48
50
  }
49
- return JSON.parse(fs.readFileSync(historyPath, 'utf-8'));
51
+ const content = fs.readFileSync(historyPath, 'utf-8');
52
+ return jsonParseOr<GenesisHistory>(content, { configHash: '', cycles: [] }, 'genesis history');
50
53
  }
51
54
 
52
55
  appendCycle(cycle: GenesisCycleRecord): void {
@@ -79,11 +82,8 @@ export class GenesisStore {
79
82
  getLastFingerprint(): GenesisFingerprint | null {
80
83
  const fpPath = path.join(this.genesisDir, 'fingerprint.json');
81
84
  if (!fs.existsSync(fpPath)) return null;
82
- try {
83
- return JSON.parse(fs.readFileSync(fpPath, 'utf-8'));
84
- } catch {
85
- return null;
86
- }
85
+ const content = fs.readFileSync(fpPath, 'utf-8');
86
+ return jsonParseOr<GenesisFingerprint | null>(content, null, 'genesis fingerprint');
87
87
  }
88
88
 
89
89
  getRecentOutcomes(count: number): string[] {
@@ -109,11 +109,8 @@ export class GenesisStore {
109
109
  loadEscrowToken(): EscrowToken | null {
110
110
  const tokenPath = path.join(this.genesisDir, 'escrow', 'token.json');
111
111
  if (!fs.existsSync(tokenPath)) return null;
112
- try {
113
- return JSON.parse(fs.readFileSync(tokenPath, 'utf-8'));
114
- } catch {
115
- return null;
116
- }
112
+ const content = fs.readFileSync(tokenPath, 'utf-8');
113
+ return jsonParseOr<EscrowToken | null>(content, null, 'escrow token');
117
114
  }
118
115
 
119
116
  saveEscrowToken(token: EscrowToken): void {
@@ -145,11 +142,8 @@ export class GenesisStore {
145
142
  loadSelfHistory(): GenesisSelfMigrationRecord[] {
146
143
  const histPath = path.join(this.genesisDir, 'self-history.json');
147
144
  if (!fs.existsSync(histPath)) return [];
148
- try {
149
- return JSON.parse(fs.readFileSync(histPath, 'utf-8'));
150
- } catch {
151
- return [];
152
- }
145
+ const content = fs.readFileSync(histPath, 'utf-8');
146
+ return jsonParseOr<GenesisSelfMigrationRecord[]>(content, [], 'genesis self-history');
153
147
  }
154
148
 
155
149
  appendSelfMigration(record: GenesisSelfMigrationRecord): void {
package/src/bot/index.ts CHANGED
@@ -112,6 +112,11 @@ export { GenesisStore } from './genesis-store.js';
112
112
  export { withFileLock } from './file-lock.js';
113
113
  export type { FileLockOptions } from './file-lock.js';
114
114
 
115
+ // Safe utilities
116
+ export { safeJsonParse, jsonParseOr, parseNdjson } from './safe-json.js';
117
+ export type { SafeParseResult } from './safe-json.js';
118
+ export { safePath, safePathOrThrow } from './safe-path.js';
119
+
115
120
  // Shared modules
116
121
  export { callCli, callApi, parseJsonResponse } from './ai-client.js';
117
122
  export { executeStep } from './step-executor.js';
@@ -0,0 +1,59 @@
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
+
6
+ export interface KnowledgeEntry {
7
+ key: string;
8
+ value: string;
9
+ source: string;
10
+ createdAt: number;
11
+ }
12
+
13
+ export class KnowledgeStore {
14
+ private filePath: string;
15
+
16
+ constructor(projectDir?: string) {
17
+ const dir = projectDir
18
+ ? path.join(os.homedir(), '.weaver', 'projects', crypto.createHash('sha256').update(projectDir).digest('hex').slice(0, 8))
19
+ : path.join(os.homedir(), '.weaver');
20
+ this.filePath = path.join(dir, 'knowledge.ndjson');
21
+ }
22
+
23
+ learn(key: string, value: string, source: string): void {
24
+ // Append entry to NDJSON file. If key exists, update it.
25
+ const entries = this.readAll().filter(e => e.key !== key);
26
+ entries.push({ key, value, source, createdAt: Date.now() });
27
+ this.writeAll(entries);
28
+ }
29
+
30
+ recall(query: string): KnowledgeEntry[] {
31
+ // Fuzzy match: return entries whose key contains the query (case-insensitive)
32
+ const lower = query.toLowerCase();
33
+ return this.readAll().filter(e => e.key.toLowerCase().includes(lower) || e.value.toLowerCase().includes(lower));
34
+ }
35
+
36
+ forget(key: string): void {
37
+ const entries = this.readAll().filter(e => e.key !== key);
38
+ this.writeAll(entries);
39
+ }
40
+
41
+ list(): KnowledgeEntry[] {
42
+ return this.readAll();
43
+ }
44
+
45
+ private readAll(): KnowledgeEntry[] {
46
+ if (!fs.existsSync(this.filePath)) return [];
47
+ const content = fs.readFileSync(this.filePath, 'utf-8').trim();
48
+ if (!content) return [];
49
+ return content.split('\n').filter(Boolean).map(line => {
50
+ try { return JSON.parse(line); } catch { return null; }
51
+ }).filter(Boolean) as KnowledgeEntry[];
52
+ }
53
+
54
+ private writeAll(entries: KnowledgeEntry[]): void {
55
+ const dir = path.dirname(this.filePath);
56
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
57
+ fs.writeFileSync(this.filePath, entries.map(e => JSON.stringify(e)).join('\n') + (entries.length > 0 ? '\n' : ''));
58
+ }
59
+ }
@@ -31,7 +31,13 @@ export class PipelineRunner {
31
31
  throw new Error(`Pipeline config not found: ${absPath}`);
32
32
  }
33
33
 
34
- const raw = JSON.parse(fs.readFileSync(absPath, 'utf-8')) as PipelineConfig;
34
+ let raw: PipelineConfig;
35
+ try {
36
+ raw = JSON.parse(fs.readFileSync(absPath, 'utf-8')) as PipelineConfig;
37
+ } catch (err) {
38
+ const msg = err instanceof Error ? err.message : String(err);
39
+ throw new Error(`Invalid JSON in pipeline config: ${absPath}\n ${msg}`);
40
+ }
35
41
  const configDir = path.dirname(absPath);
36
42
 
37
43
  // Resolve workflow paths relative to config file
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Retry utilities for transient error handling with exponential backoff.
3
+ */
4
+
5
+ const TRANSIENT_STATUS_CODES = [429, 502, 503, 504];
6
+ const TRANSIENT_ERROR_CODES = ['ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED', 'EPIPE', 'ENOTFOUND'];
7
+ const TRANSIENT_MESSAGES = ['rate limit', 'too many requests', 'overloaded', 'bad gateway', 'service unavailable'];
8
+
9
+ /**
10
+ * Check if an error is transient (retriable) vs permanent.
11
+ * Transient: network issues, rate limits, server errors.
12
+ * Permanent: auth failures, parse errors, validation errors.
13
+ */
14
+ export function isTransientError(err: unknown): boolean {
15
+ const msg = err instanceof Error ? err.message : String(err);
16
+ const lower = msg.toLowerCase();
17
+
18
+ // Check for HTTP status codes in message
19
+ for (const code of TRANSIENT_STATUS_CODES) {
20
+ if (msg.includes(String(code))) return true;
21
+ }
22
+
23
+ // Check for Node.js error codes
24
+ if (err instanceof Error && 'code' in err) {
25
+ const code = (err as NodeJS.ErrnoException).code;
26
+ if (code && TRANSIENT_ERROR_CODES.includes(code)) return true;
27
+ }
28
+
29
+ // Also check message for error code strings (e.g. "ETIMEDOUT" in message)
30
+ for (const code of TRANSIENT_ERROR_CODES) {
31
+ if (msg.includes(code)) return true;
32
+ }
33
+
34
+ // Check for rate limit / overload messages
35
+ for (const phrase of TRANSIENT_MESSAGES) {
36
+ if (lower.includes(phrase)) return true;
37
+ }
38
+
39
+ // Check for exit code 143 (SIGTERM — likely our timeout killed the process)
40
+ if (msg.includes('exit code 143') || msg.includes('exited with code 143')) return true;
41
+
42
+ return false;
43
+ }
44
+
45
+ /**
46
+ * Run a function with exponential backoff retry on transient errors.
47
+ */
48
+ export async function withRetry<T>(
49
+ fn: () => Promise<T>,
50
+ options?: {
51
+ maxRetries?: number;
52
+ baseDelayMs?: number;
53
+ multiplier?: number;
54
+ onRetry?: (attempt: number, delay: number, err: Error) => void;
55
+ },
56
+ ): Promise<T> {
57
+ const maxRetries = options?.maxRetries ?? 3;
58
+ const baseDelay = options?.baseDelayMs ?? 5_000;
59
+ const multiplier = options?.multiplier ?? 3;
60
+
61
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
62
+ try {
63
+ return await fn();
64
+ } catch (err: unknown) {
65
+ const isLast = attempt >= maxRetries;
66
+ if (isLast || !isTransientError(err)) throw err;
67
+
68
+ const delay = baseDelay * Math.pow(multiplier, attempt);
69
+ options?.onRetry?.(attempt + 1, delay, err instanceof Error ? err : new Error(String(err)));
70
+ await new Promise((r) => setTimeout(r, delay));
71
+ }
72
+ }
73
+
74
+ // Unreachable, but TypeScript needs it
75
+ throw new Error('withRetry: exhausted retries');
76
+ }
@@ -3,6 +3,7 @@ import * as path from 'node:path';
3
3
  import * as crypto from 'node:crypto';
4
4
  import * as os from 'node:os';
5
5
  import type { RunRecord, RunFilter, RetentionPolicy } from './types.js';
6
+ import { parseNdjson } from './safe-json.js';
6
7
 
7
8
  export class RunStore {
8
9
  private readonly dir: string;
@@ -153,17 +154,7 @@ export class RunStore {
153
154
  if (!fs.existsSync(this.filePath)) return [];
154
155
 
155
156
  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
-
157
+ const { records } = parseNdjson<RunRecord>(content, 'history');
167
158
  return records;
168
159
  }
169
160
  }
package/src/bot/runner.ts CHANGED
@@ -58,17 +58,29 @@ function resolveWeaverConfig(
58
58
  const dir = path.dirname(filePath);
59
59
  const localConfig = path.join(dir, '.weaver.json');
60
60
  if (fs.existsSync(localConfig)) {
61
- return JSON.parse(fs.readFileSync(localConfig, 'utf-8'));
61
+ return parseConfigFile(localConfig);
62
62
  }
63
63
 
64
64
  const cwdConfig = path.join(process.cwd(), '.weaver.json');
65
65
  if (fs.existsSync(cwdConfig)) {
66
- return JSON.parse(fs.readFileSync(cwdConfig, 'utf-8'));
66
+ return parseConfigFile(cwdConfig);
67
67
  }
68
68
 
69
69
  return { provider: 'auto' };
70
70
  }
71
71
 
72
+ function parseConfigFile(configPath: string): WeaverConfig {
73
+ const content = fs.readFileSync(configPath, 'utf-8');
74
+ try {
75
+ return JSON.parse(content) as WeaverConfig;
76
+ } catch {
77
+ throw new Error(
78
+ `Invalid JSON in config file: ${configPath}\n` +
79
+ ` Fix the file or delete it to use defaults.`,
80
+ );
81
+ }
82
+ }
83
+
72
84
  function buildSummary(result: unknown): string {
73
85
  if (!result || typeof result !== 'object') return String(result);
74
86
 
@@ -212,6 +224,16 @@ export async function runWorkflow(
212
224
  const summary = buildSummary(result);
213
225
  const outcome = success ? 'completed' : 'failed';
214
226
 
227
+ // Extract stepLog from WeaverContext if available
228
+ let stepLog: import('./types.js').StepLogEntry[] | undefined;
229
+ try {
230
+ const ctxStr = result?.ctx as string | undefined;
231
+ if (ctxStr) {
232
+ const ctx = JSON.parse(ctxStr);
233
+ if (ctx.stepLogJson) stepLog = JSON.parse(ctx.stepLogJson);
234
+ }
235
+ } catch { /* stepLog extraction is best-effort */ }
236
+
215
237
  await notifier({
216
238
  type: 'workflow-complete',
217
239
  workflowFile: absPath,
@@ -225,7 +247,7 @@ export async function runWorkflow(
225
247
  recordRun(store, {
226
248
  id: runId, workflowFile: absPath, startedAt, success, outcome: outcome as RunOutcome, summary,
227
249
  functionName: execResult.functionName, executionTime: execResult.executionTime,
228
- dryRun: false, provider: providerConfig.name, params: options?.params,
250
+ dryRun: false, provider: providerConfig.name, params: options?.params, stepLog,
229
251
  }, verbose);
230
252
 
231
253
  auditEmit('run-complete', { success, outcome, summary });
@@ -270,6 +292,7 @@ function recordRun(
270
292
  outcome: RunOutcome; summary: string; functionName?: string;
271
293
  executionTime?: number; dryRun: boolean; provider?: string;
272
294
  params?: Record<string, unknown>;
295
+ stepLog?: import('./types.js').StepLogEntry[];
273
296
  },
274
297
  verbose: boolean,
275
298
  ): void {
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Safe JSON parsing utilities.
3
+ *
4
+ * Wraps JSON.parse with proper error handling and optional context for
5
+ * meaningful error messages. Prevents crashes from malformed config files,
6
+ * corrupt NDJSON lines, or unexpected AI output.
7
+ */
8
+
9
+ export type SafeParseResult<T> = {
10
+ ok: true;
11
+ value: T;
12
+ } | {
13
+ ok: false;
14
+ error: string;
15
+ };
16
+
17
+ /**
18
+ * Parse JSON safely, returning a discriminated result instead of throwing.
19
+ */
20
+ export function safeJsonParse<T = unknown>(
21
+ text: string,
22
+ context?: string,
23
+ ): SafeParseResult<T> {
24
+ try {
25
+ return { ok: true, value: JSON.parse(text) as T };
26
+ } catch (err: unknown) {
27
+ const msg = err instanceof Error ? err.message : String(err);
28
+ const prefix = context ? `${context}: ` : '';
29
+ return { ok: false, error: `${prefix}Invalid JSON — ${msg}` };
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Parse JSON or return a fallback value on failure.
35
+ * Optionally logs a warning when parsing fails.
36
+ */
37
+ export function jsonParseOr<T>(
38
+ text: string,
39
+ fallback: T,
40
+ context?: string,
41
+ ): T {
42
+ const result = safeJsonParse<T>(text, context);
43
+ if (result.ok) return result.value;
44
+ if (context) {
45
+ console.error(`[weaver] ${result.error}`);
46
+ }
47
+ return fallback;
48
+ }
49
+
50
+ /**
51
+ * Parse NDJSON (newline-delimited JSON) safely.
52
+ * Skips corrupt lines and optionally reports them.
53
+ */
54
+ export function parseNdjson<T>(
55
+ content: string,
56
+ context?: string,
57
+ ): { records: T[]; errors: number } {
58
+ const lines = content.split('\n').filter((line) => line.trim().length > 0);
59
+ const records: T[] = [];
60
+ let errors = 0;
61
+
62
+ for (const line of lines) {
63
+ const result = safeJsonParse<T>(line);
64
+ if (result.ok) {
65
+ records.push(result.value);
66
+ } else {
67
+ errors++;
68
+ }
69
+ }
70
+
71
+ if (errors > 0 && context) {
72
+ console.error(`[weaver] ${context}: skipped ${errors} corrupt line(s)`);
73
+ }
74
+
75
+ return { records, errors };
76
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Path safety utilities.
3
+ *
4
+ * Prevents path traversal attacks and ensures file operations stay within
5
+ * expected boundaries. Critical for any code that constructs paths from
6
+ * user input, AI output, or external configuration.
7
+ */
8
+
9
+ import * as path from 'node:path';
10
+
11
+ /**
12
+ * Validate that a relative path does not escape the base directory.
13
+ * Returns the resolved absolute path if safe, or null if the path
14
+ * attempts traversal.
15
+ */
16
+ export function safePath(baseDir: string, relativePath: string): string | null {
17
+ const normalized = path.normalize(relativePath);
18
+
19
+ // Reject absolute paths and explicit traversal
20
+ if (path.isAbsolute(normalized)) return null;
21
+ if (normalized.startsWith('..')) return null;
22
+
23
+ const resolved = path.resolve(baseDir, normalized);
24
+ const resolvedBase = path.resolve(baseDir);
25
+
26
+ // Ensure resolved path is within baseDir
27
+ if (!resolved.startsWith(resolvedBase + path.sep) && resolved !== resolvedBase) {
28
+ return null;
29
+ }
30
+
31
+ return resolved;
32
+ }
33
+
34
+ /**
35
+ * Validate and resolve a path, throwing a descriptive error on traversal.
36
+ */
37
+ export function safePathOrThrow(baseDir: string, relativePath: string, context?: string): string {
38
+ const resolved = safePath(baseDir, relativePath);
39
+ if (resolved === null) {
40
+ const prefix = context ? `${context}: ` : '';
41
+ throw new Error(`${prefix}Unsafe file path rejected: "${relativePath}"`);
42
+ }
43
+ return resolved;
44
+ }
@@ -40,7 +40,8 @@ export class SessionStore {
40
40
  try {
41
41
  if (!fs.existsSync(this.filePath)) return null;
42
42
  return JSON.parse(fs.readFileSync(this.filePath, 'utf-8')) as SessionState;
43
- } catch {
43
+ } catch (err) {
44
+ if (process.env.WEAVER_VERBOSE) process.stderr.write(`[weaver] session state load failed: ${err}\n`);
44
45
  return null;
45
46
  }
46
47
  }
@@ -13,7 +13,7 @@ export class SteeringController {
13
13
  private controlPath: string;
14
14
 
15
15
  constructor(controlDir?: string) {
16
- const dir = controlDir ?? path.join(os.homedir(), '.weaver');
16
+ const dir = controlDir ?? process.env.WEAVER_STEERING_DIR ?? path.join(os.homedir(), '.weaver');
17
17
  this.controlPath = path.join(dir, 'control.json');
18
18
  }
19
19