@synergenius/flow-weaver-pack-weaver 0.9.0 → 0.9.4

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 (235) 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/ansi.d.ts +13 -0
  6. package/dist/bot/ansi.d.ts.map +1 -0
  7. package/dist/bot/ansi.js +13 -0
  8. package/dist/bot/ansi.js.map +1 -0
  9. package/dist/bot/assistant-core.d.ts +25 -0
  10. package/dist/bot/assistant-core.d.ts.map +1 -0
  11. package/dist/bot/assistant-core.js +272 -0
  12. package/dist/bot/assistant-core.js.map +1 -0
  13. package/dist/bot/assistant-tools.d.ts +10 -0
  14. package/dist/bot/assistant-tools.d.ts.map +1 -0
  15. package/dist/bot/assistant-tools.js +324 -0
  16. package/dist/bot/assistant-tools.js.map +1 -0
  17. package/dist/bot/audit-logger.d.ts.map +1 -1
  18. package/dist/bot/audit-logger.js +9 -5
  19. package/dist/bot/audit-logger.js.map +1 -1
  20. package/dist/bot/bot-manager.d.ts +49 -0
  21. package/dist/bot/bot-manager.d.ts.map +1 -0
  22. package/dist/bot/bot-manager.js +279 -0
  23. package/dist/bot/bot-manager.js.map +1 -0
  24. package/dist/bot/child-process-tracker.d.ts +6 -0
  25. package/dist/bot/child-process-tracker.d.ts.map +1 -0
  26. package/dist/bot/child-process-tracker.js +35 -0
  27. package/dist/bot/child-process-tracker.js.map +1 -0
  28. package/dist/bot/cli-provider.d.ts.map +1 -1
  29. package/dist/bot/cli-provider.js +13 -8
  30. package/dist/bot/cli-provider.js.map +1 -1
  31. package/dist/bot/conversation-store.d.ts +40 -0
  32. package/dist/bot/conversation-store.d.ts.map +1 -0
  33. package/dist/bot/conversation-store.js +182 -0
  34. package/dist/bot/conversation-store.js.map +1 -0
  35. package/dist/bot/error-classifier.d.ts +27 -0
  36. package/dist/bot/error-classifier.d.ts.map +1 -0
  37. package/dist/bot/error-classifier.js +71 -0
  38. package/dist/bot/error-classifier.js.map +1 -0
  39. package/dist/bot/error-guide.d.ts +5 -0
  40. package/dist/bot/error-guide.d.ts.map +1 -0
  41. package/dist/bot/error-guide.js +5 -0
  42. package/dist/bot/error-guide.js.map +1 -0
  43. package/dist/bot/knowledge-store.d.ts +17 -0
  44. package/dist/bot/knowledge-store.d.ts.map +1 -0
  45. package/dist/bot/knowledge-store.js +53 -0
  46. package/dist/bot/knowledge-store.js.map +1 -0
  47. package/dist/bot/paths.d.ts +11 -0
  48. package/dist/bot/paths.d.ts.map +1 -0
  49. package/dist/bot/paths.js +26 -0
  50. package/dist/bot/paths.js.map +1 -0
  51. package/dist/bot/retry-utils.d.ts +5 -0
  52. package/dist/bot/retry-utils.d.ts.map +1 -0
  53. package/dist/bot/retry-utils.js +5 -0
  54. package/dist/bot/retry-utils.js.map +1 -0
  55. package/dist/bot/runner.d.ts.map +1 -1
  56. package/dist/bot/runner.js +12 -1
  57. package/dist/bot/runner.js.map +1 -1
  58. package/dist/bot/safety.d.ts +10 -0
  59. package/dist/bot/safety.d.ts.map +1 -0
  60. package/dist/bot/safety.js +14 -0
  61. package/dist/bot/safety.js.map +1 -0
  62. package/dist/bot/session-state.d.ts.map +1 -1
  63. package/dist/bot/session-state.js +3 -1
  64. package/dist/bot/session-state.js.map +1 -1
  65. package/dist/bot/steering.js +2 -2
  66. package/dist/bot/steering.js.map +1 -1
  67. package/dist/bot/step-executor.d.ts +10 -5
  68. package/dist/bot/step-executor.d.ts.map +1 -1
  69. package/dist/bot/step-executor.js +252 -3
  70. package/dist/bot/step-executor.js.map +1 -1
  71. package/dist/bot/system-prompt.d.ts +1 -1
  72. package/dist/bot/system-prompt.d.ts.map +1 -1
  73. package/dist/bot/system-prompt.js +69 -43
  74. package/dist/bot/system-prompt.js.map +1 -1
  75. package/dist/bot/task-decomposer.d.ts +24 -0
  76. package/dist/bot/task-decomposer.d.ts.map +1 -0
  77. package/dist/bot/task-decomposer.js +75 -0
  78. package/dist/bot/task-decomposer.js.map +1 -0
  79. package/dist/bot/task-queue.d.ts +17 -4
  80. package/dist/bot/task-queue.d.ts.map +1 -1
  81. package/dist/bot/task-queue.js +83 -5
  82. package/dist/bot/task-queue.js.map +1 -1
  83. package/dist/bot/terminal-renderer.d.ts +60 -0
  84. package/dist/bot/terminal-renderer.d.ts.map +1 -0
  85. package/dist/bot/terminal-renderer.js +204 -0
  86. package/dist/bot/terminal-renderer.js.map +1 -0
  87. package/dist/bot/tool-registry.d.ts +24 -0
  88. package/dist/bot/tool-registry.d.ts.map +1 -0
  89. package/dist/bot/tool-registry.js +458 -0
  90. package/dist/bot/tool-registry.js.map +1 -0
  91. package/dist/bot/types.d.ts +7 -0
  92. package/dist/bot/types.d.ts.map +1 -1
  93. package/dist/bot/weaver-tools.d.ts +18 -0
  94. package/dist/bot/weaver-tools.d.ts.map +1 -0
  95. package/dist/bot/weaver-tools.js +124 -0
  96. package/dist/bot/weaver-tools.js.map +1 -0
  97. package/dist/cli-bridge.d.ts.map +1 -1
  98. package/dist/cli-bridge.js +5 -1
  99. package/dist/cli-bridge.js.map +1 -1
  100. package/dist/cli-handlers.d.ts +13 -1
  101. package/dist/cli-handlers.d.ts.map +1 -1
  102. package/dist/cli-handlers.js +615 -48
  103. package/dist/cli-handlers.js.map +1 -1
  104. package/dist/mcp-tools.js +2 -2
  105. package/dist/mcp-tools.js.map +1 -1
  106. package/dist/node-types/abort-task.d.ts.map +1 -1
  107. package/dist/node-types/abort-task.js +4 -3
  108. package/dist/node-types/abort-task.js.map +1 -1
  109. package/dist/node-types/agent-execute.d.ts +38 -0
  110. package/dist/node-types/agent-execute.d.ts.map +1 -0
  111. package/dist/node-types/agent-execute.js +252 -0
  112. package/dist/node-types/agent-execute.js.map +1 -0
  113. package/dist/node-types/bot-report.d.ts +5 -3
  114. package/dist/node-types/bot-report.d.ts.map +1 -1
  115. package/dist/node-types/bot-report.js +39 -7
  116. package/dist/node-types/bot-report.js.map +1 -1
  117. package/dist/node-types/build-context.d.ts +3 -3
  118. package/dist/node-types/build-context.d.ts.map +1 -1
  119. package/dist/node-types/build-context.js +108 -24
  120. package/dist/node-types/build-context.js.map +1 -1
  121. package/dist/node-types/detect-provider.d.ts +2 -2
  122. package/dist/node-types/detect-provider.d.ts.map +1 -1
  123. package/dist/node-types/detect-provider.js +3 -1
  124. package/dist/node-types/detect-provider.js.map +1 -1
  125. package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
  126. package/dist/node-types/exec-validate-retry.js +43 -6
  127. package/dist/node-types/exec-validate-retry.js.map +1 -1
  128. package/dist/node-types/execute-plan.d.ts.map +1 -1
  129. package/dist/node-types/execute-plan.js +31 -8
  130. package/dist/node-types/execute-plan.js.map +1 -1
  131. package/dist/node-types/execute-target.d.ts.map +1 -1
  132. package/dist/node-types/execute-target.js +3 -1
  133. package/dist/node-types/execute-target.js.map +1 -1
  134. package/dist/node-types/fix-errors.d.ts.map +1 -1
  135. package/dist/node-types/fix-errors.js +21 -5
  136. package/dist/node-types/fix-errors.js.map +1 -1
  137. package/dist/node-types/genesis-observe.d.ts.map +1 -1
  138. package/dist/node-types/genesis-observe.js +3 -1
  139. package/dist/node-types/genesis-observe.js.map +1 -1
  140. package/dist/node-types/genesis-report.js +4 -1
  141. package/dist/node-types/genesis-report.js.map +1 -1
  142. package/dist/node-types/git-ops.d.ts.map +1 -1
  143. package/dist/node-types/git-ops.js +98 -4
  144. package/dist/node-types/git-ops.js.map +1 -1
  145. package/dist/node-types/index.d.ts +2 -0
  146. package/dist/node-types/index.d.ts.map +1 -1
  147. package/dist/node-types/index.js +2 -0
  148. package/dist/node-types/index.js.map +1 -1
  149. package/dist/node-types/load-config.d.ts +2 -2
  150. package/dist/node-types/load-config.d.ts.map +1 -1
  151. package/dist/node-types/load-config.js.map +1 -1
  152. package/dist/node-types/plan-task.d.ts.map +1 -1
  153. package/dist/node-types/plan-task.js +14 -2
  154. package/dist/node-types/plan-task.js.map +1 -1
  155. package/dist/node-types/read-workflow.js +8 -2
  156. package/dist/node-types/read-workflow.js.map +1 -1
  157. package/dist/node-types/receive-task.d.ts.map +1 -1
  158. package/dist/node-types/receive-task.js +35 -26
  159. package/dist/node-types/receive-task.js.map +1 -1
  160. package/dist/node-types/send-notify.js +2 -1
  161. package/dist/node-types/send-notify.js.map +1 -1
  162. package/dist/node-types/validate-gate.d.ts +18 -0
  163. package/dist/node-types/validate-gate.d.ts.map +1 -0
  164. package/dist/node-types/validate-gate.js +96 -0
  165. package/dist/node-types/validate-gate.js.map +1 -0
  166. package/dist/workflows/genesis-task.d.ts +20 -12
  167. package/dist/workflows/genesis-task.d.ts.map +1 -1
  168. package/dist/workflows/genesis-task.js +20 -12
  169. package/dist/workflows/genesis-task.js.map +1 -1
  170. package/dist/workflows/weaver-agent.d.ts +35 -0
  171. package/dist/workflows/weaver-agent.d.ts.map +1 -0
  172. package/dist/workflows/weaver-agent.js +777 -0
  173. package/dist/workflows/weaver-agent.js.map +1 -0
  174. package/dist/workflows/weaver-bot-batch.d.ts +19 -26
  175. package/dist/workflows/weaver-bot-batch.d.ts.map +1 -1
  176. package/dist/workflows/weaver-bot-batch.js +1043 -27
  177. package/dist/workflows/weaver-bot-batch.js.map +1 -1
  178. package/dist/workflows/weaver-bot.d.ts +21 -35
  179. package/dist/workflows/weaver-bot.d.ts.map +1 -1
  180. package/dist/workflows/weaver-bot.js +1119 -36
  181. package/dist/workflows/weaver-bot.js.map +1 -1
  182. package/flowweaver.manifest.json +21 -1
  183. package/package.json +5 -2
  184. package/src/bot/ai-client.ts +180 -19
  185. package/src/bot/ansi.ts +12 -0
  186. package/src/bot/assistant-core.ts +312 -0
  187. package/src/bot/assistant-tools.ts +318 -0
  188. package/src/bot/audit-logger.ts +6 -5
  189. package/src/bot/bot-manager.ts +293 -0
  190. package/src/bot/child-process-tracker.ts +40 -0
  191. package/src/bot/cli-provider.ts +13 -8
  192. package/src/bot/conversation-store.ts +222 -0
  193. package/src/bot/error-classifier.ts +90 -0
  194. package/src/bot/error-guide.ts +4 -0
  195. package/src/bot/knowledge-store.ts +59 -0
  196. package/src/bot/paths.ts +27 -0
  197. package/src/bot/retry-utils.ts +4 -0
  198. package/src/bot/runner.ts +12 -1
  199. package/src/bot/safety.ts +16 -0
  200. package/src/bot/session-state.ts +2 -1
  201. package/src/bot/steering.ts +2 -2
  202. package/src/bot/step-executor.ts +313 -5
  203. package/src/bot/system-prompt.ts +70 -47
  204. package/src/bot/task-decomposer.ts +100 -0
  205. package/src/bot/task-queue.ts +100 -8
  206. package/src/bot/terminal-renderer.ts +238 -0
  207. package/src/bot/tool-registry.ts +477 -0
  208. package/src/bot/types.ts +8 -0
  209. package/src/bot/weaver-tools.ts +134 -0
  210. package/src/cli-bridge.ts +7 -1
  211. package/src/cli-handlers.ts +624 -48
  212. package/src/mcp-tools.ts +2 -2
  213. package/src/node-types/abort-task.ts +5 -4
  214. package/src/node-types/agent-execute.ts +303 -0
  215. package/src/node-types/bot-report.ts +40 -9
  216. package/src/node-types/build-context.ts +112 -25
  217. package/src/node-types/detect-provider.ts +4 -3
  218. package/src/node-types/exec-validate-retry.ts +47 -8
  219. package/src/node-types/execute-plan.ts +32 -8
  220. package/src/node-types/execute-target.ts +2 -1
  221. package/src/node-types/fix-errors.ts +20 -5
  222. package/src/node-types/genesis-observe.ts +2 -1
  223. package/src/node-types/genesis-report.ts +1 -1
  224. package/src/node-types/git-ops.ts +93 -4
  225. package/src/node-types/index.ts +2 -0
  226. package/src/node-types/load-config.ts +3 -3
  227. package/src/node-types/plan-task.ts +15 -3
  228. package/src/node-types/read-workflow.ts +2 -2
  229. package/src/node-types/receive-task.ts +31 -26
  230. package/src/node-types/send-notify.ts +1 -1
  231. package/src/node-types/validate-gate.ts +112 -0
  232. package/src/workflows/genesis-task.ts +20 -12
  233. package/src/workflows/weaver-agent.ts +799 -0
  234. package/src/workflows/weaver-bot-batch.ts +1049 -27
  235. package/src/workflows/weaver-bot.ts +1123 -36
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Unified error classification — merges error-guide.ts and retry-utils.ts
3
+ * into a single source of truth for error handling.
4
+ */
5
+
6
+ export interface ErrorClassification {
7
+ isTransient: boolean;
8
+ guidance: string | null;
9
+ category: 'auth' | 'network' | 'rate-limit' | 'timeout' | 'parse' | 'system' | 'unknown';
10
+ }
11
+
12
+ const PATTERNS: Array<{ pattern: RegExp; isTransient: boolean; guidance: string; category: ErrorClassification['category'] }> = [
13
+ { pattern: /ETIMEDOUT/i, isTransient: true, guidance: 'Network timeout. Check internet or increase timeout.', category: 'timeout' },
14
+ { pattern: /ECONNRESET/i, isTransient: true, guidance: 'Connection reset. Retry in a few seconds.', category: 'network' },
15
+ { pattern: /ECONNREFUSED/i, isTransient: true, guidance: 'Connection refused. Is the service running?', category: 'network' },
16
+ { pattern: /EPIPE|ENOTFOUND/i, isTransient: true, guidance: 'Network error.', category: 'network' },
17
+ { pattern: /401|authentication|invalid.*key/i, isTransient: false, guidance: 'Authentication failed. Check API key or run "weaver init".', category: 'auth' },
18
+ { pattern: /403|forbidden/i, isTransient: false, guidance: 'Access denied. API key may lack permissions.', category: 'auth' },
19
+ { pattern: /429|rate.?limit|too many requests/i, isTransient: true, guidance: 'Rate limited. Wait a few minutes or reduce --parallel.', category: 'rate-limit' },
20
+ { pattern: /502|bad gateway/i, isTransient: true, guidance: 'Server error (502). Will auto-retry.', category: 'network' },
21
+ { pattern: /503|service unavailable|overloaded/i, isTransient: true, guidance: 'Service overloaded. Will auto-retry.', category: 'network' },
22
+ { pattern: /504|gateway timeout/i, isTransient: true, guidance: 'Gateway timeout (504). Will auto-retry.', category: 'timeout' },
23
+ { pattern: /exit(?:ed with)? code 143/i, isTransient: true, guidance: 'Process killed (SIGTERM). Likely timeout or Ctrl+C.', category: 'timeout' },
24
+ { pattern: /exit code 137/i, isTransient: false, guidance: 'Process killed (OOM). System may be low on memory.', category: 'system' },
25
+ { pattern: /ENOMEM/i, isTransient: false, guidance: 'Out of memory.', category: 'system' },
26
+ { pattern: /ENOSPC/i, isTransient: false, guidance: 'Disk full.', category: 'system' },
27
+ { pattern: /lock.*retries|failed to acquire.*lock/i, isTransient: true, guidance: 'File lock contention. Another weaver process may be running.', category: 'system' },
28
+ { pattern: /not a workflow|No @flowWeaver/i, isTransient: false, guidance: 'Not a Flow Weaver workflow. Ensure @flowWeaver annotations exist.', category: 'parse' },
29
+ { pattern: /parse.*json|unexpected token/i, isTransient: false, guidance: 'JSON parse error. AI may have returned malformed output.', category: 'parse' },
30
+ { pattern: /Queue full/i, isTransient: false, guidance: 'Too many pending tasks (200 max). Process or clear first.', category: 'system' },
31
+ ];
32
+
33
+ /**
34
+ * Classify an error into transient/permanent with guidance and category.
35
+ */
36
+ export function classifyError(err: unknown): ErrorClassification {
37
+ const msg = err instanceof Error ? err.message : String(err);
38
+ // Also check Node.js error codes
39
+ const code = err instanceof Error && 'code' in err ? (err as NodeJS.ErrnoException).code : undefined;
40
+ const fullMsg = code ? `${msg} ${code}` : msg;
41
+
42
+ for (const p of PATTERNS) {
43
+ if (p.pattern.test(fullMsg)) {
44
+ return { isTransient: p.isTransient, guidance: p.guidance, category: p.category };
45
+ }
46
+ }
47
+ return { isTransient: false, guidance: null, category: 'unknown' };
48
+ }
49
+
50
+ /** Convenience: check if an error is transient (retriable). */
51
+ export function isTransientError(err: unknown): boolean {
52
+ return classifyError(err).isTransient;
53
+ }
54
+
55
+ /** Convenience: get actionable guidance for an error message. */
56
+ export function getErrorGuidance(msg: string): string | null {
57
+ return classifyError(new Error(msg)).guidance;
58
+ }
59
+
60
+ /**
61
+ * Run a function with exponential backoff retry on transient errors.
62
+ */
63
+ export async function withRetry<T>(
64
+ fn: () => Promise<T>,
65
+ options?: {
66
+ maxRetries?: number;
67
+ baseDelayMs?: number;
68
+ multiplier?: number;
69
+ onRetry?: (attempt: number, delay: number, err: Error) => void;
70
+ },
71
+ ): Promise<T> {
72
+ const maxRetries = options?.maxRetries ?? 3;
73
+ const baseDelay = options?.baseDelayMs ?? 5_000;
74
+ const multiplier = options?.multiplier ?? 3;
75
+
76
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
77
+ try {
78
+ return await fn();
79
+ } catch (err: unknown) {
80
+ const isLast = attempt >= maxRetries;
81
+ if (isLast || !isTransientError(err)) throw err;
82
+ const delay = baseDelay * Math.pow(multiplier, attempt);
83
+ options?.onRetry?.(attempt + 1, delay, err instanceof Error ? err : new Error(String(err)));
84
+ await new Promise((r) => setTimeout(r, delay));
85
+ }
86
+ }
87
+
88
+ // Unreachable, but TypeScript needs it
89
+ throw new Error('withRetry: exhausted retries');
90
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @deprecated Use error-classifier.ts instead. This file re-exports for backward compatibility.
3
+ */
4
+ export { getErrorGuidance } from './error-classifier.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
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Shared path resolution — single source of truth for .weaver directory layout.
3
+ */
4
+ import * as path from 'node:path';
5
+ import * as os from 'node:os';
6
+ import * as crypto from 'node:crypto';
7
+
8
+ /**
9
+ * Hash a directory path into a short filesystem-safe string.
10
+ * Used for per-project isolation under ~/.weaver/projects/.
11
+ */
12
+ export function hashDir(dir: string): string {
13
+ return crypto.createHash('sha256').update(dir).digest('hex').slice(0, 8);
14
+ }
15
+
16
+ /**
17
+ * Resolve the weaver working directory.
18
+ * Priority: explicit > WEAVER_QUEUE_DIR > WEAVER_STEERING_DIR > project-scoped > global fallback.
19
+ */
20
+ export function resolveWeaverDir(explicit?: string): string {
21
+ return explicit
22
+ ?? process.env.WEAVER_QUEUE_DIR
23
+ ?? process.env.WEAVER_STEERING_DIR
24
+ ?? (process.env.WEAVER_PROJECT_DIR
25
+ ? path.join(os.homedir(), '.weaver', 'projects', hashDir(process.env.WEAVER_PROJECT_DIR))
26
+ : path.join(os.homedir(), '.weaver'));
27
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @deprecated Use error-classifier.ts instead. This file re-exports for backward compatibility.
3
+ */
4
+ export { isTransientError, withRetry } from './error-classifier.js';
package/src/bot/runner.ts CHANGED
@@ -224,6 +224,16 @@ export async function runWorkflow(
224
224
  const summary = buildSummary(result);
225
225
  const outcome = success ? 'completed' : 'failed';
226
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
+
227
237
  await notifier({
228
238
  type: 'workflow-complete',
229
239
  workflowFile: absPath,
@@ -237,7 +247,7 @@ export async function runWorkflow(
237
247
  recordRun(store, {
238
248
  id: runId, workflowFile: absPath, startedAt, success, outcome: outcome as RunOutcome, summary,
239
249
  functionName: execResult.functionName, executionTime: execResult.executionTime,
240
- dryRun: false, provider: providerConfig.name, params: options?.params,
250
+ dryRun: false, provider: providerConfig.name, params: options?.params, stepLog,
241
251
  }, verbose);
242
252
 
243
253
  auditEmit('run-complete', { success, outcome, summary });
@@ -282,6 +292,7 @@ function recordRun(
282
292
  outcome: RunOutcome; summary: string; functionName?: string;
283
293
  executionTime?: number; dryRun: boolean; provider?: string;
284
294
  params?: Record<string, unknown>;
295
+ stepLog?: import('./types.js').StepLogEntry[];
285
296
  },
286
297
  verbose: boolean,
287
298
  ): void {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shared safety constants and checks — single source of truth.
3
+ */
4
+
5
+ export const BLOCKED_COMMANDS = ['rm -rf', 'git push', 'npm publish', 'sudo', 'curl|sh', 'wget|sh'];
6
+ export const BLOCKED_URL_PATTERN = /localhost|127\.0\.0\.1|0\.0\.0\.0|10\.\d|172\.(1[6-9]|2\d|3[01])\.|192\.168\./i;
7
+ export const MAX_READ_SIZE = 1_048_576;
8
+ export const CHARS_PER_TOKEN = 4;
9
+
10
+ export function isBlockedCommand(cmd: string): boolean {
11
+ return BLOCKED_COMMANDS.some(b => cmd.includes(b));
12
+ }
13
+
14
+ export function isBlockedUrl(url: string): boolean {
15
+ return BLOCKED_URL_PATTERN.test(url);
16
+ }
@@ -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
  }
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
- import * as os from 'node:os';
4
3
  import { withFileLock } from './file-lock.js';
4
+ import { resolveWeaverDir } from './paths.js';
5
5
 
6
6
  export interface SteeringCommand {
7
7
  command: 'pause' | 'resume' | 'cancel' | 'redirect' | 'queue';
@@ -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 ?? resolveWeaverDir();
17
17
  this.controlPath = path.join(dir, 'control.json');
18
18
  }
19
19
 
@@ -1,27 +1,335 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
+ import { execSync } from 'node:child_process';
3
4
  import { runCommand } from '@synergenius/flow-weaver';
4
5
 
6
+ // ---------------------------------------------------------------------------
7
+ // Safety thresholds
8
+ // ---------------------------------------------------------------------------
9
+
10
+ /** Refuse writes that shrink an existing file by more than this ratio (0-1). */
11
+ const MAX_SHRINK_RATIO = 0.5;
12
+
13
+ /** Minimum file size (bytes) before shrink guard kicks in. */
14
+ const SHRINK_GUARD_MIN_SIZE = 100;
15
+
16
+ /** Maximum number of files that can be written in a single plan. */
17
+ const MAX_FILES_PER_PLAN = 50;
18
+
19
+ /** Maximum shell command execution time (ms). */
20
+ const SHELL_TIMEOUT = 60_000;
21
+
22
+ /** Maximum file size for read/patch operations (1MB). */
23
+ const MAX_READ_SIZE = 1_048_576;
24
+
25
+ /** Maximum files returned by list-files. */
26
+ const MAX_LIST_FILES = 1000;
27
+
28
+ /** Shell commands that are NEVER allowed (destructive operations). */
29
+ const BLOCKED_SHELL_PATTERNS = [
30
+ /\brm\s+(-[a-z]*r|-[a-z]*f)[a-z]*\s/i, // rm with -r or -f flags
31
+ /\bgit\s+push\b/i, // git push (no remote ops)
32
+ /\bgit\s+reset\s+--hard\b/i, // git reset --hard
33
+ /\bnpm\s+publish\b/i, // npm publish
34
+ /\bcurl\b.*\|\s*(sh|bash)\b/i, // curl | sh/bash
35
+ /\bwget\b.*\|\s*(sh|bash)\b/i, // wget | sh/bash
36
+ /\bsudo\b/i, // sudo
37
+ /\bchmod\s+777\b/i, // chmod 777
38
+ /\bkill\s+-9\b/i, // kill -9
39
+ /\bmkfs\b/i, // format disk
40
+ /\bdd\s+if=/i, // dd (disk destroyer)
41
+ />\s*\/dev\/sd/i, // write to raw disk
42
+ ];
43
+
44
+ /** Track files written in this process to enforce the per-plan cap. */
45
+ let filesWrittenThisPlan = 0;
46
+
47
+ /** Reset the per-plan counter between plans. */
48
+ export function resetPlanFileCounter(): void {
49
+ filesWrittenThisPlan = 0;
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Path safety
54
+ // ---------------------------------------------------------------------------
55
+
56
+ function assertSafePath(filePath: string, projectDir: string): void {
57
+ const resolved = path.resolve(projectDir, filePath);
58
+ if (!resolved.startsWith(path.resolve(projectDir))) {
59
+ throw new Error(
60
+ `Path traversal blocked: "${filePath}" resolves outside project directory.`,
61
+ );
62
+ }
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Write safety
67
+ // ---------------------------------------------------------------------------
68
+
69
+ interface WriteGuardResult {
70
+ allowed: boolean;
71
+ reason?: string;
72
+ }
73
+
74
+ function checkWriteSafety(filePath: string, content: string): WriteGuardResult {
75
+ // Guard 1: Empty content
76
+ if (!content || content.trim().length === 0) {
77
+ return {
78
+ allowed: false,
79
+ reason:
80
+ `Refusing to write empty content to ${path.basename(filePath)}. ` +
81
+ `Use read-file first, then write the complete modified file back.`,
82
+ };
83
+ }
84
+
85
+ // Guard 2: Per-plan file cap
86
+ if (filesWrittenThisPlan >= MAX_FILES_PER_PLAN) {
87
+ return {
88
+ allowed: false,
89
+ reason: `File write limit reached (${MAX_FILES_PER_PLAN} files per plan).`,
90
+ };
91
+ }
92
+
93
+ // Guard 3: Shrink detection
94
+ if (fs.existsSync(filePath)) {
95
+ const existingSize = fs.statSync(filePath).size;
96
+ const newSize = Buffer.byteLength(content, 'utf-8');
97
+ if (existingSize > SHRINK_GUARD_MIN_SIZE && newSize < existingSize * MAX_SHRINK_RATIO) {
98
+ const shrinkPct = Math.round((1 - newSize / existingSize) * 100);
99
+ return {
100
+ allowed: false,
101
+ reason:
102
+ `Refusing to write ${path.basename(filePath)}: new content (${newSize}B) ` +
103
+ `is ${shrinkPct}% smaller than existing (${existingSize}B). ` +
104
+ `Use read-file first, make targeted changes, write complete file back.`,
105
+ };
106
+ }
107
+ }
108
+
109
+ return { allowed: true };
110
+ }
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // Shell safety
114
+ // ---------------------------------------------------------------------------
115
+
116
+ function checkShellSafety(command: string): WriteGuardResult {
117
+ for (const pattern of BLOCKED_SHELL_PATTERNS) {
118
+ if (pattern.test(command)) {
119
+ return {
120
+ allowed: false,
121
+ reason: `Shell command blocked by safety policy: matches "${pattern.source}"`,
122
+ };
123
+ }
124
+ }
125
+ return { allowed: true };
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Main executor
130
+ // ---------------------------------------------------------------------------
131
+
132
+ export interface StepResult {
133
+ file?: string;
134
+ files?: string[];
135
+ created?: boolean;
136
+ output?: string;
137
+ blocked?: boolean;
138
+ blockReason?: string;
139
+ }
140
+
5
141
  export async function executeStep(
6
142
  step: { operation: string; args: Record<string, unknown> },
7
143
  projectDir: string,
8
- ): Promise<{ file?: string; files?: string[]; created?: boolean; output?: string }> {
144
+ ): Promise<StepResult> {
9
145
  const args = step.args;
10
146
  const file = args.file as string | undefined;
11
147
 
12
148
  switch (step.operation) {
149
+ // -----------------------------------------------------------------
150
+ // File write operations (with safety guards)
151
+ // -----------------------------------------------------------------
13
152
  case 'write-file':
14
153
  case 'create-workflow':
15
154
  case 'modify-source':
16
155
  case 'implement-node': {
17
- const filePath = path.resolve(projectDir, file!);
156
+ if (!file) {
157
+ return { blocked: true, blockReason: `${step.operation} requires a "file" argument.` };
158
+ }
159
+ assertSafePath(file, projectDir);
160
+ const filePath = path.resolve(projectDir, file);
161
+ const content = (args.content as string) ?? (args.body as string) ?? '';
162
+
163
+ const guard = checkWriteSafety(filePath, content);
164
+ if (!guard.allowed) {
165
+ return { file: filePath, blocked: true, blockReason: guard.reason };
166
+ }
167
+
18
168
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
19
169
  const existed = fs.existsSync(filePath);
20
- fs.writeFileSync(filePath, (args.content as string) ?? (args.body as string) ?? '', 'utf-8');
170
+ fs.writeFileSync(filePath, content, 'utf-8');
171
+ filesWrittenThisPlan++;
21
172
  return { file: filePath, created: !existed };
22
173
  }
23
- case 'read-file':
24
- return {};
174
+
175
+ // -----------------------------------------------------------------
176
+ // Patch file: surgical find-and-replace (no full rewrite needed)
177
+ // -----------------------------------------------------------------
178
+ case 'patch-file': {
179
+ if (!file) {
180
+ return { blocked: true, blockReason: 'patch-file requires a "file" argument.' };
181
+ }
182
+ assertSafePath(file, projectDir);
183
+ const filePath = path.resolve(projectDir, file);
184
+
185
+ if (!fs.existsSync(filePath)) {
186
+ return { blocked: true, blockReason: `File not found: ${file}` };
187
+ }
188
+
189
+ // File size guard
190
+ const fileSize = fs.statSync(filePath).size;
191
+ if (fileSize > MAX_READ_SIZE) {
192
+ return { blocked: true, blockReason: `File too large for patch-file (${fileSize} bytes, max ${MAX_READ_SIZE}).` };
193
+ }
194
+
195
+ let content = fs.readFileSync(filePath, 'utf-8');
196
+ const patches = (args.patches as Array<{ find: string; replace: string }>) ?? [];
197
+
198
+ if (!patches.length && args.find && args.replace !== undefined) {
199
+ patches.push({ find: args.find as string, replace: args.replace as string });
200
+ }
201
+
202
+ if (!patches.length) {
203
+ return { blocked: true, blockReason: 'patch-file requires "patches" array or "find"+"replace" args.' };
204
+ }
205
+
206
+ let applied = 0;
207
+ const notFound: string[] = [];
208
+
209
+ for (const patch of patches) {
210
+ if (content.includes(patch.find)) {
211
+ content = content.split(patch.find).join(patch.replace); // replaceAll without regex
212
+ applied++;
213
+ } else {
214
+ notFound.push(patch.find.substring(0, 60));
215
+ }
216
+ }
217
+
218
+ if (applied === 0) {
219
+ return {
220
+ file: filePath,
221
+ output: `No patches applied. Search strings not found: ${notFound.join('; ')}`,
222
+ };
223
+ }
224
+
225
+ fs.writeFileSync(filePath, content, 'utf-8');
226
+ filesWrittenThisPlan++;
227
+
228
+ const summary = `Applied ${applied}/${patches.length} patches` +
229
+ (notFound.length ? `. Not found: ${notFound.join('; ')}` : '');
230
+ return { file: filePath, output: summary };
231
+ }
232
+
233
+ // -----------------------------------------------------------------
234
+ // Read file: return content for AI context
235
+ // -----------------------------------------------------------------
236
+ case 'read-file': {
237
+ if (!file) {
238
+ return { output: '' };
239
+ }
240
+ assertSafePath(file, projectDir);
241
+ const filePath = path.resolve(projectDir, file);
242
+ if (!fs.existsSync(filePath)) {
243
+ return { output: `File not found: ${file}` };
244
+ }
245
+ if (fs.statSync(filePath).isDirectory()) {
246
+ const entries = fs.readdirSync(filePath, { encoding: 'utf-8' });
247
+ return { output: `"${file}" is a directory. Contents:\n${entries.join('\n')}` };
248
+ }
249
+ const fileSize = fs.statSync(filePath).size;
250
+ if (fileSize > MAX_READ_SIZE) {
251
+ return { output: `File too large to read (${fileSize} bytes, max ${MAX_READ_SIZE}). Use run-shell with head/tail instead.` };
252
+ }
253
+ const content = fs.readFileSync(filePath, 'utf-8');
254
+ return { file: filePath, output: content };
255
+ }
256
+
257
+ // -----------------------------------------------------------------
258
+ // Shell command: run arbitrary command with safety guards
259
+ // -----------------------------------------------------------------
260
+ case 'run-shell': {
261
+ const command = (args.command as string) ?? '';
262
+ if (!command.trim()) {
263
+ return { blocked: true, blockReason: 'run-shell requires a "command" argument.' };
264
+ }
265
+
266
+ const shellGuard = checkShellSafety(command);
267
+ if (!shellGuard.allowed) {
268
+ return { blocked: true, blockReason: shellGuard.reason };
269
+ }
270
+
271
+ try {
272
+ const output = execSync(command, {
273
+ cwd: projectDir,
274
+ encoding: 'utf-8',
275
+ timeout: SHELL_TIMEOUT,
276
+ maxBuffer: 1024 * 1024, // 1MB output cap
277
+ stdio: ['pipe', 'pipe', 'pipe'],
278
+ });
279
+ return { output: output.trim() };
280
+ } catch (err: unknown) {
281
+ // Shell commands that exit non-zero still return useful output
282
+ const execErr = err as { stdout?: string; stderr?: string; status?: number };
283
+ const stdout = (execErr.stdout ?? '').trim();
284
+ const stderr = (execErr.stderr ?? '').trim();
285
+ const combined = [stdout, stderr].filter(Boolean).join('\n');
286
+ return {
287
+ output: combined || (err instanceof Error ? err.message : String(err)),
288
+ };
289
+ }
290
+ }
291
+
292
+ // -----------------------------------------------------------------
293
+ // List files: glob-like directory listing
294
+ // -----------------------------------------------------------------
295
+ case 'list-files': {
296
+ const dir = (args.directory as string) ?? (args.dir as string) ?? '.';
297
+ const pattern = (args.pattern as string) ?? '';
298
+ assertSafePath(dir, projectDir);
299
+ const targetDir = path.resolve(projectDir, dir);
300
+
301
+ if (!fs.existsSync(targetDir)) {
302
+ return { output: `Directory not found: ${dir}` };
303
+ }
304
+
305
+ const entries = fs.readdirSync(targetDir, { recursive: true, encoding: 'utf-8' }) as string[];
306
+ let files = entries
307
+ .filter(e => {
308
+ if (e.includes('node_modules') || e.includes('.git')) return false;
309
+ const full = path.join(targetDir, e);
310
+ try { return fs.statSync(full).isFile(); } catch { return false; }
311
+ })
312
+ .sort();
313
+
314
+ if (pattern) {
315
+ try {
316
+ const regex = new RegExp(pattern);
317
+ files = files.filter(f => regex.test(f));
318
+ } catch {
319
+ return { output: `Invalid regex pattern: ${pattern}` };
320
+ }
321
+ }
322
+
323
+ if (files.length > MAX_LIST_FILES) {
324
+ files = files.slice(0, MAX_LIST_FILES);
325
+ }
326
+
327
+ return { files: files.map(f => path.join(dir, f)), output: files.join('\n') };
328
+ }
329
+
330
+ // -----------------------------------------------------------------
331
+ // Flow-weaver CLI commands (via programmatic API)
332
+ // -----------------------------------------------------------------
25
333
  default: {
26
334
  const result = await runCommand(step.operation, { ...args, cwd: projectDir });
27
335
  return {