@the-open-engine/zeroshot 5.4.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 (295) hide show
  1. package/CHANGELOG.md +539 -0
  2. package/LICENSE +21 -0
  3. package/README.md +508 -0
  4. package/cli/commands/inspect-render.js +135 -0
  5. package/cli/commands/inspect.js +294 -0
  6. package/cli/commands/providers.js +149 -0
  7. package/cli/index.js +5431 -0
  8. package/cli/lib/first-run.js +211 -0
  9. package/cli/lib/update-checker.js +281 -0
  10. package/cli/message-formatter-utils.js +75 -0
  11. package/cli/message-formatters-normal.js +275 -0
  12. package/cli/message-formatters-watch.js +185 -0
  13. package/cluster-templates/base-templates/debug-workflow.json +422 -0
  14. package/cluster-templates/base-templates/full-workflow.json +727 -0
  15. package/cluster-templates/base-templates/heavy-validation.json +272 -0
  16. package/cluster-templates/base-templates/quick-validation.json +285 -0
  17. package/cluster-templates/base-templates/single-worker.json +71 -0
  18. package/cluster-templates/base-templates/worker-validator.json +230 -0
  19. package/cluster-templates/conductor-bootstrap.json +130 -0
  20. package/docker/zeroshot-cluster/Dockerfile +153 -0
  21. package/docker/zeroshot-cluster/pre-baked-deps.json +28 -0
  22. package/lib/agent-cli-provider/adapters/claude-parser.d.ts +3 -0
  23. package/lib/agent-cli-provider/adapters/claude-parser.d.ts.map +1 -0
  24. package/lib/agent-cli-provider/adapters/claude-parser.js +122 -0
  25. package/lib/agent-cli-provider/adapters/claude-parser.js.map +1 -0
  26. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts +15 -0
  27. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -0
  28. package/lib/agent-cli-provider/adapters/claude-recovery.js +165 -0
  29. package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -0
  30. package/lib/agent-cli-provider/adapters/claude.d.ts +3 -0
  31. package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -0
  32. package/lib/agent-cli-provider/adapters/claude.js +181 -0
  33. package/lib/agent-cli-provider/adapters/claude.js.map +1 -0
  34. package/lib/agent-cli-provider/adapters/codex-parser.d.ts +3 -0
  35. package/lib/agent-cli-provider/adapters/codex-parser.d.ts.map +1 -0
  36. package/lib/agent-cli-provider/adapters/codex-parser.js +192 -0
  37. package/lib/agent-cli-provider/adapters/codex-parser.js.map +1 -0
  38. package/lib/agent-cli-provider/adapters/codex.d.ts +3 -0
  39. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -0
  40. package/lib/agent-cli-provider/adapters/codex.js +151 -0
  41. package/lib/agent-cli-provider/adapters/codex.js.map +1 -0
  42. package/lib/agent-cli-provider/adapters/common.d.ts +28 -0
  43. package/lib/agent-cli-provider/adapters/common.d.ts.map +1 -0
  44. package/lib/agent-cli-provider/adapters/common.js +71 -0
  45. package/lib/agent-cli-provider/adapters/common.js.map +1 -0
  46. package/lib/agent-cli-provider/adapters/gemini.d.ts +3 -0
  47. package/lib/agent-cli-provider/adapters/gemini.d.ts.map +1 -0
  48. package/lib/agent-cli-provider/adapters/gemini.js +176 -0
  49. package/lib/agent-cli-provider/adapters/gemini.js.map +1 -0
  50. package/lib/agent-cli-provider/adapters/index.d.ts +9 -0
  51. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -0
  52. package/lib/agent-cli-provider/adapters/index.js +95 -0
  53. package/lib/agent-cli-provider/adapters/index.js.map +1 -0
  54. package/lib/agent-cli-provider/adapters/opencode.d.ts +3 -0
  55. package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -0
  56. package/lib/agent-cli-provider/adapters/opencode.js +228 -0
  57. package/lib/agent-cli-provider/adapters/opencode.js.map +1 -0
  58. package/lib/agent-cli-provider/contract-actions.d.ts +5 -0
  59. package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -0
  60. package/lib/agent-cli-provider/contract-actions.js +140 -0
  61. package/lib/agent-cli-provider/contract-actions.js.map +1 -0
  62. package/lib/agent-cli-provider/contract-env.d.ts +11 -0
  63. package/lib/agent-cli-provider/contract-env.d.ts.map +1 -0
  64. package/lib/agent-cli-provider/contract-env.js +113 -0
  65. package/lib/agent-cli-provider/contract-env.js.map +1 -0
  66. package/lib/agent-cli-provider/contract-envelope.d.ts +49 -0
  67. package/lib/agent-cli-provider/contract-envelope.d.ts.map +1 -0
  68. package/lib/agent-cli-provider/contract-envelope.js +52 -0
  69. package/lib/agent-cli-provider/contract-envelope.js.map +1 -0
  70. package/lib/agent-cli-provider/contract-errors.d.ts +22 -0
  71. package/lib/agent-cli-provider/contract-errors.d.ts.map +1 -0
  72. package/lib/agent-cli-provider/contract-errors.js +71 -0
  73. package/lib/agent-cli-provider/contract-errors.js.map +1 -0
  74. package/lib/agent-cli-provider/contract-fallback.d.ts +7 -0
  75. package/lib/agent-cli-provider/contract-fallback.d.ts.map +1 -0
  76. package/lib/agent-cli-provider/contract-fallback.js +102 -0
  77. package/lib/agent-cli-provider/contract-fallback.js.map +1 -0
  78. package/lib/agent-cli-provider/contract-invoke.d.ts +5 -0
  79. package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -0
  80. package/lib/agent-cli-provider/contract-invoke.js +89 -0
  81. package/lib/agent-cli-provider/contract-invoke.js.map +1 -0
  82. package/lib/agent-cli-provider/contract-options.d.ts +3 -0
  83. package/lib/agent-cli-provider/contract-options.d.ts.map +1 -0
  84. package/lib/agent-cli-provider/contract-options.js +140 -0
  85. package/lib/agent-cli-provider/contract-options.js.map +1 -0
  86. package/lib/agent-cli-provider/contract-parse.d.ts +15 -0
  87. package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -0
  88. package/lib/agent-cli-provider/contract-parse.js +81 -0
  89. package/lib/agent-cli-provider/contract-parse.js.map +1 -0
  90. package/lib/agent-cli-provider/contract-support.d.ts +18 -0
  91. package/lib/agent-cli-provider/contract-support.d.ts.map +1 -0
  92. package/lib/agent-cli-provider/contract-support.js +154 -0
  93. package/lib/agent-cli-provider/contract-support.js.map +1 -0
  94. package/lib/agent-cli-provider/contract.d.ts +13 -0
  95. package/lib/agent-cli-provider/contract.d.ts.map +1 -0
  96. package/lib/agent-cli-provider/contract.js +30 -0
  97. package/lib/agent-cli-provider/contract.js.map +1 -0
  98. package/lib/agent-cli-provider/env-safety.d.ts +11 -0
  99. package/lib/agent-cli-provider/env-safety.d.ts.map +1 -0
  100. package/lib/agent-cli-provider/env-safety.js +83 -0
  101. package/lib/agent-cli-provider/env-safety.js.map +1 -0
  102. package/lib/agent-cli-provider/errors.d.ts +5 -0
  103. package/lib/agent-cli-provider/errors.d.ts.map +1 -0
  104. package/lib/agent-cli-provider/errors.js +115 -0
  105. package/lib/agent-cli-provider/errors.js.map +1 -0
  106. package/lib/agent-cli-provider/executable.d.ts +3 -0
  107. package/lib/agent-cli-provider/executable.d.ts.map +1 -0
  108. package/lib/agent-cli-provider/executable.js +24 -0
  109. package/lib/agent-cli-provider/executable.js.map +1 -0
  110. package/lib/agent-cli-provider/index.d.ts +8 -0
  111. package/lib/agent-cli-provider/index.d.ts.map +1 -0
  112. package/lib/agent-cli-provider/index.js +31 -0
  113. package/lib/agent-cli-provider/index.js.map +1 -0
  114. package/lib/agent-cli-provider/invoke-evidence.d.ts +4 -0
  115. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -0
  116. package/lib/agent-cli-provider/invoke-evidence.js +17 -0
  117. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -0
  118. package/lib/agent-cli-provider/json.d.ts +16 -0
  119. package/lib/agent-cli-provider/json.d.ts.map +1 -0
  120. package/lib/agent-cli-provider/json.js +110 -0
  121. package/lib/agent-cli-provider/json.js.map +1 -0
  122. package/lib/agent-cli-provider/log-prefix.d.ts +2 -0
  123. package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -0
  124. package/lib/agent-cli-provider/log-prefix.js +22 -0
  125. package/lib/agent-cli-provider/log-prefix.js.map +1 -0
  126. package/lib/agent-cli-provider/process-runner.d.ts +17 -0
  127. package/lib/agent-cli-provider/process-runner.d.ts.map +1 -0
  128. package/lib/agent-cli-provider/process-runner.js +89 -0
  129. package/lib/agent-cli-provider/process-runner.js.map +1 -0
  130. package/lib/agent-cli-provider/redaction.d.ts +9 -0
  131. package/lib/agent-cli-provider/redaction.d.ts.map +1 -0
  132. package/lib/agent-cli-provider/redaction.js +227 -0
  133. package/lib/agent-cli-provider/redaction.js.map +1 -0
  134. package/lib/agent-cli-provider/schema.d.ts +5 -0
  135. package/lib/agent-cli-provider/schema.d.ts.map +1 -0
  136. package/lib/agent-cli-provider/schema.js +136 -0
  137. package/lib/agent-cli-provider/schema.js.map +1 -0
  138. package/lib/agent-cli-provider/single-agent-runtime.d.ts +15 -0
  139. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -0
  140. package/lib/agent-cli-provider/single-agent-runtime.js +228 -0
  141. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -0
  142. package/lib/agent-cli-provider/types.d.ts +194 -0
  143. package/lib/agent-cli-provider/types.d.ts.map +1 -0
  144. package/lib/agent-cli-provider/types.js +12 -0
  145. package/lib/agent-cli-provider/types.js.map +1 -0
  146. package/lib/completion.js +174 -0
  147. package/lib/detached-startup.js +220 -0
  148. package/lib/docker-config.js +220 -0
  149. package/lib/git-remote-utils.js +165 -0
  150. package/lib/id-detector.js +55 -0
  151. package/lib/provider-defaults.js +62 -0
  152. package/lib/provider-detection.js +59 -0
  153. package/lib/provider-names.js +57 -0
  154. package/lib/repo-settings.js +69 -0
  155. package/lib/settings/claude-auth.js +78 -0
  156. package/lib/settings.js +542 -0
  157. package/lib/start-cluster.js +321 -0
  158. package/lib/stream-json-parser.js +67 -0
  159. package/package.json +162 -0
  160. package/scripts/fix-node-pty-permissions.js +75 -0
  161. package/scripts/record-demo.sh +279 -0
  162. package/scripts/setup-merge-queue.sh +170 -0
  163. package/scripts/test-install.sh +40 -0
  164. package/scripts/validate-templates.js +107 -0
  165. package/src/agent/agent-config.js +266 -0
  166. package/src/agent/agent-context-builder.js +189 -0
  167. package/src/agent/agent-context-sections.js +338 -0
  168. package/src/agent/agent-context-sources.js +147 -0
  169. package/src/agent/agent-hook-executor.js +721 -0
  170. package/src/agent/agent-input-injector.js +141 -0
  171. package/src/agent/agent-lifecycle.js +982 -0
  172. package/src/agent/agent-quality-gate-schema.js +93 -0
  173. package/src/agent/agent-quality-gates-context.js +51 -0
  174. package/src/agent/agent-stuck-detector.js +256 -0
  175. package/src/agent/agent-task-executor.js +2028 -0
  176. package/src/agent/agent-trigger-evaluator.js +67 -0
  177. package/src/agent/context-metrics.js +160 -0
  178. package/src/agent/context-pack-builder.js +367 -0
  179. package/src/agent/context-replay-policy.js +51 -0
  180. package/src/agent/guidance-queue.js +77 -0
  181. package/src/agent/output-extraction.js +367 -0
  182. package/src/agent/output-reformatter.js +175 -0
  183. package/src/agent/pr-verification.js +653 -0
  184. package/src/agent/rate-limit-backoff.js +82 -0
  185. package/src/agent/schema-utils.js +146 -0
  186. package/src/agent/validation-platform.js +35 -0
  187. package/src/agent-cli-provider/adapters/claude-parser.ts +133 -0
  188. package/src/agent-cli-provider/adapters/claude-recovery.ts +203 -0
  189. package/src/agent-cli-provider/adapters/claude.ts +247 -0
  190. package/src/agent-cli-provider/adapters/codex-parser.ts +211 -0
  191. package/src/agent-cli-provider/adapters/codex.ts +217 -0
  192. package/src/agent-cli-provider/adapters/common.ts +124 -0
  193. package/src/agent-cli-provider/adapters/gemini.ts +243 -0
  194. package/src/agent-cli-provider/adapters/index.ts +126 -0
  195. package/src/agent-cli-provider/adapters/opencode.ts +286 -0
  196. package/src/agent-cli-provider/contract-actions.ts +150 -0
  197. package/src/agent-cli-provider/contract-env.ts +111 -0
  198. package/src/agent-cli-provider/contract-envelope.ts +110 -0
  199. package/src/agent-cli-provider/contract-errors.ts +66 -0
  200. package/src/agent-cli-provider/contract-fallback.ts +121 -0
  201. package/src/agent-cli-provider/contract-invoke.ts +104 -0
  202. package/src/agent-cli-provider/contract-options.ts +173 -0
  203. package/src/agent-cli-provider/contract-parse.ts +94 -0
  204. package/src/agent-cli-provider/contract-support.ts +167 -0
  205. package/src/agent-cli-provider/contract.ts +56 -0
  206. package/src/agent-cli-provider/env-safety.ts +82 -0
  207. package/src/agent-cli-provider/errors.ts +122 -0
  208. package/src/agent-cli-provider/executable.ts +24 -0
  209. package/src/agent-cli-provider/index.ts +83 -0
  210. package/src/agent-cli-provider/invoke-evidence.ts +18 -0
  211. package/src/agent-cli-provider/json.ts +114 -0
  212. package/src/agent-cli-provider/log-prefix.ts +20 -0
  213. package/src/agent-cli-provider/process-runner.ts +145 -0
  214. package/src/agent-cli-provider/redaction.ts +282 -0
  215. package/src/agent-cli-provider/schema.ts +115 -0
  216. package/src/agent-cli-provider/single-agent-runtime.ts +311 -0
  217. package/src/agent-cli-provider/types.ts +237 -0
  218. package/src/agent-wrapper.js +615 -0
  219. package/src/agents/git-pusher-template.js +705 -0
  220. package/src/attach/attach-client.js +438 -0
  221. package/src/attach/attach-server.js +543 -0
  222. package/src/attach/index.js +37 -0
  223. package/src/attach/protocol.js +220 -0
  224. package/src/attach/ring-buffer.js +121 -0
  225. package/src/attach/send-input.js +88 -0
  226. package/src/attach/socket-discovery.js +267 -0
  227. package/src/claude-task-runner.js +661 -0
  228. package/src/config-router.js +89 -0
  229. package/src/config-validator.js +2202 -0
  230. package/src/copy-worker.js +43 -0
  231. package/src/guidance-topics.js +10 -0
  232. package/src/input-helpers.js +65 -0
  233. package/src/isolation-manager.js +1734 -0
  234. package/src/issue-providers/README.md +305 -0
  235. package/src/issue-providers/azure-devops-provider.js +307 -0
  236. package/src/issue-providers/base-provider.js +232 -0
  237. package/src/issue-providers/github-provider.js +210 -0
  238. package/src/issue-providers/gitlab-provider.js +262 -0
  239. package/src/issue-providers/index.js +196 -0
  240. package/src/issue-providers/jira-provider.js +260 -0
  241. package/src/ledger.js +692 -0
  242. package/src/lib/gc.js +232 -0
  243. package/src/lib/safe-exec.js +88 -0
  244. package/src/logic-engine.js +201 -0
  245. package/src/message-buffer.js +81 -0
  246. package/src/message-bus-bridge.js +144 -0
  247. package/src/message-bus.js +256 -0
  248. package/src/name-generator.js +232 -0
  249. package/src/orchestrator.js +3924 -0
  250. package/src/preflight.js +712 -0
  251. package/src/process-metrics.js +608 -0
  252. package/src/providers/anthropic/index.js +3 -0
  253. package/src/providers/base-provider.js +355 -0
  254. package/src/providers/capabilities.js +60 -0
  255. package/src/providers/google/index.js +3 -0
  256. package/src/providers/index.js +293 -0
  257. package/src/providers/openai/index.js +3 -0
  258. package/src/providers/opencode/index.js +3 -0
  259. package/src/quality-gates.js +143 -0
  260. package/src/schemas/sub-cluster.js +208 -0
  261. package/src/state-snapshot.js +398 -0
  262. package/src/state-snapshotter.js +142 -0
  263. package/src/status-footer.js +1026 -0
  264. package/src/sub-cluster-wrapper.js +693 -0
  265. package/src/task-runner.js +30 -0
  266. package/src/template-resolver.js +425 -0
  267. package/src/template-validation/index.js +338 -0
  268. package/src/template-validation/simulate-consensus-gates.js +324 -0
  269. package/src/template-validation/simulate-random-topology.js +541 -0
  270. package/src/template-validation/simulate-two-stage-validation.js +270 -0
  271. package/src/worktree-claude-config.js +135 -0
  272. package/src/worktree-tooling-env.js +150 -0
  273. package/task-lib/attachable-watcher.js +381 -0
  274. package/task-lib/commands/clean.js +50 -0
  275. package/task-lib/commands/episodes.js +105 -0
  276. package/task-lib/commands/get-log-path.js +23 -0
  277. package/task-lib/commands/kill.js +32 -0
  278. package/task-lib/commands/list.js +105 -0
  279. package/task-lib/commands/logs.js +439 -0
  280. package/task-lib/commands/resume.js +42 -0
  281. package/task-lib/commands/run.js +57 -0
  282. package/task-lib/commands/schedule.js +105 -0
  283. package/task-lib/commands/scheduler-cmd.js +96 -0
  284. package/task-lib/commands/schedules.js +148 -0
  285. package/task-lib/commands/status.js +44 -0
  286. package/task-lib/commands/unschedule.js +16 -0
  287. package/task-lib/completion.js +9 -0
  288. package/task-lib/config.js +11 -0
  289. package/task-lib/name-generator.js +230 -0
  290. package/task-lib/package.json +3 -0
  291. package/task-lib/provider-helper-runtime.js +29 -0
  292. package/task-lib/runner.js +190 -0
  293. package/task-lib/scheduler.js +252 -0
  294. package/task-lib/store.js +529 -0
  295. package/task-lib/watcher.js +305 -0
package/src/lib/gc.js ADDED
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Garbage collection for orphaned zeroshot worktrees and database files.
3
+ *
4
+ * Standalone module with ZERO dependencies on Orchestrator or IsolationManager.
5
+ * All operations are synchronous so it can be called from any context
6
+ * (CLI, createWorktree pre-flight, etc.) without async concerns.
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const os = require('os');
12
+
13
+ const DEFAULT_STORAGE_DIR = path.join(os.homedir(), '.zeroshot');
14
+
15
+ /** Cluster ID pattern: adjective-noun-number (e.g., "flying-jungle-51") */
16
+ const CLUSTER_ID_PATTERN = /^[a-z]+-[a-z]+-\d+$/;
17
+
18
+ function isClusterDir(entry) {
19
+ return entry.isDirectory() && CLUSTER_ID_PATTERN.test(entry.name);
20
+ }
21
+
22
+ function resolveActiveClusterIdFromEnv() {
23
+ const clusterId = process.env.ZEROSHOT_CLUSTER_ID;
24
+ if (typeof clusterId !== 'string' || clusterId.trim().length === 0) {
25
+ return null;
26
+ }
27
+ const normalized = clusterId.trim();
28
+ return CLUSTER_ID_PATTERN.test(normalized) ? normalized : null;
29
+ }
30
+
31
+ /**
32
+ * Read known cluster IDs from clusters.json (synchronous, no locking).
33
+ * @param {string} storageDir
34
+ * @returns {Set<string>}
35
+ */
36
+ function readKnownClusterIds(storageDir) {
37
+ const ids = new Set();
38
+ const clustersFile = path.join(storageDir, 'clusters.json');
39
+ try {
40
+ if (!fs.existsSync(clustersFile)) return ids;
41
+ const raw = JSON.parse(fs.readFileSync(clustersFile, 'utf8'));
42
+ for (const id of Object.keys(raw)) ids.add(id);
43
+ } catch {
44
+ // Corrupt/missing — treat as empty (safe: nothing deleted incorrectly)
45
+ }
46
+ return ids;
47
+ }
48
+
49
+ function resolveStorageAndKnownIds(storageDirOrOptions = DEFAULT_STORAGE_DIR) {
50
+ const options =
51
+ typeof storageDirOrOptions === 'string'
52
+ ? { storageDir: storageDirOrOptions }
53
+ : storageDirOrOptions || {};
54
+ const storageDir = options.storageDir || DEFAULT_STORAGE_DIR;
55
+ const knownIds = readKnownClusterIds(storageDir);
56
+ const activeClusterId = resolveActiveClusterIdFromEnv();
57
+ if (activeClusterId) {
58
+ knownIds.add(activeClusterId);
59
+ }
60
+ if (options.extraKnownIds) {
61
+ for (const id of options.extraKnownIds) knownIds.add(id);
62
+ }
63
+ return { storageDir, knownIds };
64
+ }
65
+
66
+ /**
67
+ * Count orphaned worktree directories (for error messages).
68
+ * @param {string|{storageDir?: string, extraKnownIds?: Set<string>}} [storageDirOrOptions]
69
+ */
70
+ function countOrphanedWorktrees(storageDirOrOptions = DEFAULT_STORAGE_DIR) {
71
+ const { storageDir, knownIds } = resolveStorageAndKnownIds(storageDirOrOptions);
72
+ const worktreeDir = path.join(storageDir, 'worktrees');
73
+ if (!fs.existsSync(worktreeDir)) return 0;
74
+ try {
75
+ return fs
76
+ .readdirSync(worktreeDir, { withFileTypes: true })
77
+ .filter((e) => isClusterDir(e) && !knownIds.has(e.name)).length;
78
+ } catch {
79
+ return 0;
80
+ }
81
+ }
82
+
83
+ /** Try to remove a single file. Returns error string or null. */
84
+ function tryUnlink(filePath) {
85
+ try {
86
+ fs.unlinkSync(filePath);
87
+ return null;
88
+ } catch (err) {
89
+ return err.message;
90
+ }
91
+ }
92
+
93
+ /** Try to remove a directory tree. Returns error string or null. */
94
+ function tryRmdir(dirPath) {
95
+ try {
96
+ fs.rmSync(dirPath, { recursive: true, force: true });
97
+ return null;
98
+ } catch (err) {
99
+ return err.message;
100
+ }
101
+ }
102
+
103
+ /** Find repo root from a worktree's .git file (gitdir pointer). */
104
+ function findRepoRootFromWorktree(worktreeDir) {
105
+ let entries;
106
+ try {
107
+ entries = fs.readdirSync(worktreeDir, { withFileTypes: true });
108
+ } catch {
109
+ return null;
110
+ }
111
+ for (const entry of entries) {
112
+ if (!entry.isDirectory()) continue;
113
+ const dotGit = path.join(worktreeDir, entry.name, '.git');
114
+ try {
115
+ const content = fs.readFileSync(dotGit, 'utf8').trim();
116
+ const match = content.match(/^gitdir:\s*(.+)/);
117
+ if (!match) continue;
118
+ // gitdir: /repo/.git/worktrees/<name> → resolve to /repo
119
+ const repoRoot = path.resolve(match[1].trim(), '..', '..', '..');
120
+ if (fs.existsSync(path.join(repoRoot, '.git'))) return repoRoot;
121
+ } catch {
122
+ continue;
123
+ }
124
+ }
125
+ return null;
126
+ }
127
+
128
+ /** Best-effort git worktree prune. */
129
+ function pruneGitWorktrees(worktreeDir) {
130
+ const repoRoot = findRepoRootFromWorktree(worktreeDir);
131
+ if (!repoRoot) return;
132
+ try {
133
+ require('child_process').execSync('git worktree prune', {
134
+ cwd: repoRoot,
135
+ encoding: 'utf8',
136
+ stdio: 'pipe',
137
+ timeout: 10000,
138
+ });
139
+ } catch {
140
+ // Best effort
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Garbage-collect orphaned worktree directories and database files.
146
+ *
147
+ * @param {object} [options]
148
+ * @param {string} [options.storageDir]
149
+ * @param {Set<string>} [options.extraKnownIds]
150
+ * @param {boolean} [options.dryRun=false]
151
+ * @param {boolean} [options.removeDbFiles] - Defaults to false when ZEROSHOT_CLUSTER_ID is set, else true
152
+ * @returns {{ orphanedWorktrees: string[], orphanedDbs: string[], errors: string[] }}
153
+ */
154
+ function gcOrphanedWorktrees(options = {}) {
155
+ const storageDir = options.storageDir || DEFAULT_STORAGE_DIR;
156
+ const dryRun = options.dryRun || false;
157
+ const activeClusterId = resolveActiveClusterIdFromEnv();
158
+ const removeDbFiles =
159
+ typeof options.removeDbFiles === 'boolean' ? options.removeDbFiles : activeClusterId === null;
160
+ const worktreeDir = path.join(storageDir, 'worktrees');
161
+ const result = { orphanedWorktrees: [], orphanedDbs: [], errors: [] };
162
+
163
+ const { knownIds } = resolveStorageAndKnownIds({
164
+ storageDir,
165
+ extraKnownIds: options.extraKnownIds,
166
+ });
167
+
168
+ collectOrphanedWorktrees(worktreeDir, knownIds, dryRun, result);
169
+ if (removeDbFiles) {
170
+ collectOrphanedDbFiles(storageDir, knownIds, dryRun, result);
171
+ }
172
+
173
+ if (!dryRun && result.orphanedWorktrees.length > 0) {
174
+ pruneGitWorktrees(worktreeDir);
175
+ }
176
+
177
+ return result;
178
+ }
179
+
180
+ function collectOrphanedWorktrees(worktreeDir, knownIds, dryRun, result) {
181
+ if (!fs.existsSync(worktreeDir)) return;
182
+ let entries;
183
+ try {
184
+ entries = fs.readdirSync(worktreeDir, { withFileTypes: true });
185
+ } catch (err) {
186
+ result.errors.push(`Failed to read worktree dir: ${err.message}`);
187
+ return;
188
+ }
189
+ for (const entry of entries) {
190
+ if (!isClusterDir(entry) || knownIds.has(entry.name)) continue;
191
+ result.orphanedWorktrees.push(entry.name);
192
+ if (dryRun) continue;
193
+ const err = tryRmdir(path.join(worktreeDir, entry.name));
194
+ if (err) result.errors.push(`Failed to remove worktree ${entry.name}: ${err}`);
195
+ }
196
+ }
197
+
198
+ function collectOrphanedDbFiles(storageDir, knownIds, dryRun, result) {
199
+ let entries;
200
+ try {
201
+ entries = fs.readdirSync(storageDir);
202
+ } catch {
203
+ return;
204
+ }
205
+ for (const entry of entries) {
206
+ const match = entry.match(/^(.+)\.(db|db-wal|db-shm)$/);
207
+ if (!match || knownIds.has(match[1])) continue;
208
+ result.orphanedDbs.push(entry);
209
+ if (dryRun) continue;
210
+ const err = tryUnlink(path.join(storageDir, entry));
211
+ if (err) result.errors.push(`Failed to remove db file ${entry}: ${err}`);
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Get disk space info for a path.
217
+ * @param {string} dirPath
218
+ * @returns {{ available: number, total: number, usagePercent: number } | null}
219
+ */
220
+ function getDiskSpace(dirPath) {
221
+ try {
222
+ const stats = fs.statfsSync(dirPath);
223
+ const available = stats.bavail * stats.bsize;
224
+ const total = stats.blocks * stats.bsize;
225
+ const usagePercent = total > 0 ? ((total - available) / total) * 100 : 0;
226
+ return { available, total, usagePercent };
227
+ } catch {
228
+ return null;
229
+ }
230
+ }
231
+
232
+ module.exports = { gcOrphanedWorktrees, countOrphanedWorktrees, getDiskSpace, CLUSTER_ID_PATTERN };
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Safe subprocess execution with mandatory timeouts.
3
+ *
4
+ * NEVER use child_process.exec() or execSync() directly.
5
+ * These wrappers enforce timeouts to prevent infinite hangs.
6
+ */
7
+
8
+ const { exec: nodeExec, execSync: nodeExecSync } = require('child_process');
9
+
10
+ /** Default timeout: 30 seconds */
11
+ const DEFAULT_TIMEOUT_MS = 30000;
12
+
13
+ /**
14
+ * Execute command with mandatory timeout.
15
+ * Supports both Promise and callback styles for gradual migration.
16
+ *
17
+ * @param {string} command - Command to execute
18
+ * @param {object} [options] - Options (timeout uses default if not specified)
19
+ * @param {function} [callback] - Optional callback(error, stdout, stderr)
20
+ * @returns {Promise<{stdout: string, stderr: string}>|void} Promise if no callback, void if callback
21
+ *
22
+ * @example
23
+ * // Promise style (preferred)
24
+ * const { stdout } = await exec('ls -la', { timeout: 5000 });
25
+ *
26
+ * @example
27
+ * // Callback style (for legacy code migration)
28
+ * exec('ls -la', { timeout: 5000 }, (error, stdout) => { ... });
29
+ */
30
+ function exec(command, optionsOrCallback = {}, callbackArg = null) {
31
+ // Handle overloaded signature: exec(cmd, callback)
32
+ const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : callbackArg;
33
+ const options = typeof optionsOrCallback === 'function' ? {} : optionsOrCallback;
34
+
35
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
36
+
37
+ if (timeout <= 0) {
38
+ const err = new Error('exec() timeout must be > 0. Infinite waits are forbidden.');
39
+ if (callback) {
40
+ callback(err);
41
+ return;
42
+ }
43
+ return Promise.reject(err);
44
+ }
45
+
46
+ // Callback style
47
+ if (callback) {
48
+ nodeExec(command, { ...options, timeout }, (error, stdout, stderr) => {
49
+ if (error && error.killed && error.signal === 'SIGTERM') {
50
+ error.message = `Command timed out after ${timeout}ms: ${command}`;
51
+ }
52
+ callback(error, stdout, stderr);
53
+ });
54
+ return;
55
+ }
56
+
57
+ // Promise style
58
+ return new Promise((resolve, reject) => {
59
+ nodeExec(command, { ...options, timeout }, (error, stdout, stderr) => {
60
+ if (error) {
61
+ if (error.killed && error.signal === 'SIGTERM') {
62
+ error.message = `Command timed out after ${timeout}ms: ${command}`;
63
+ }
64
+ reject(error);
65
+ } else {
66
+ resolve({ stdout, stderr });
67
+ }
68
+ });
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Execute command with mandatory timeout (sync)
74
+ * @param {string} command - Command to execute
75
+ * @param {object} [options] - Options (timeout required or uses default)
76
+ * @returns {string} stdout
77
+ */
78
+ function execSync(command, options = {}) {
79
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
80
+
81
+ if (timeout <= 0) {
82
+ throw new Error('execSync() timeout must be > 0. Infinite waits are forbidden.');
83
+ }
84
+
85
+ return nodeExecSync(command, { ...options, timeout });
86
+ }
87
+
88
+ module.exports = { exec, execSync, DEFAULT_TIMEOUT_MS };
@@ -0,0 +1,201 @@
1
+ /**
2
+ * LogicEngine - JavaScript sandbox for agent decision logic
3
+ *
4
+ * Provides:
5
+ * - Isolated VM for executing agent trigger logic
6
+ * - Timeout enforcement (1 second)
7
+ * - Ledger API access for queries
8
+ * - Helper functions for common patterns
9
+ * - Sandbox security (no fs, network, child_process)
10
+ */
11
+
12
+ const vm = require('vm');
13
+
14
+ function buildLedgerAPI(messageBus, clusterId) {
15
+ return {
16
+ query: (criteria) => {
17
+ return messageBus.query({ ...criteria, cluster_id: clusterId });
18
+ },
19
+
20
+ findLast: (criteria) => {
21
+ return messageBus.findLast({ ...criteria, cluster_id: clusterId });
22
+ },
23
+
24
+ count: (criteria) => {
25
+ return messageBus.count({ ...criteria, cluster_id: clusterId });
26
+ },
27
+
28
+ since: (timestamp) => {
29
+ return messageBus.since({ cluster_id: clusterId, timestamp });
30
+ },
31
+ };
32
+ }
33
+
34
+ function buildHelpers(ledgerAPI) {
35
+ return {
36
+ allResponded: (agents, topic, since) => {
37
+ const responses = ledgerAPI.query({ topic, since });
38
+ const responders = new Set(responses.map((r) => r.sender));
39
+ return agents.every((a) => responders.has(a.id || a));
40
+ },
41
+
42
+ hasConsensus: (topic, since) => {
43
+ const responses = ledgerAPI.query({ topic, since });
44
+ if (responses.length === 0) return false;
45
+ return responses.every((r) => r.content?.data?.approved === true);
46
+ },
47
+
48
+ timeSinceLastMessage: (topic) => {
49
+ const last = ledgerAPI.findLast({ topic });
50
+ if (!last) return Infinity;
51
+ return Date.now() - last.timestamp;
52
+ },
53
+
54
+ hasMessagesSince: (topic, since) => {
55
+ const count = ledgerAPI.count({ topic, since });
56
+ return count > 0;
57
+ },
58
+
59
+ getConfig: require('./config-router').getConfig,
60
+ };
61
+ }
62
+
63
+ function buildClusterAPI(cluster, clusterId) {
64
+ const getAgents = () => (cluster ? cluster.agents || [] : []);
65
+
66
+ return {
67
+ id: clusterId,
68
+ getAgents,
69
+ getAgentsByRole: (role) => getAgents().filter((a) => a.role === role),
70
+ getAgent: (id) => getAgents().find((a) => a.id === id) || null,
71
+ };
72
+ }
73
+
74
+ function buildAgentContext(agent) {
75
+ return {
76
+ id: agent.id,
77
+ role: agent.role,
78
+ iteration: agent.iteration || 0,
79
+ requiredQualityGates: Array.isArray(agent.requiredQualityGates)
80
+ ? agent.requiredQualityGates
81
+ : [],
82
+ };
83
+ }
84
+
85
+ function getSafeBuiltins() {
86
+ return { Set, Map, Array, Object, String, Number, Boolean, Math, Date, JSON };
87
+ }
88
+
89
+ function getQuietConsole() {
90
+ return {
91
+ log: () => {},
92
+ error: () => {},
93
+ warn: () => {},
94
+ info: () => {},
95
+ };
96
+ }
97
+
98
+ class LogicEngine {
99
+ constructor(messageBus, cluster) {
100
+ this.messageBus = messageBus;
101
+ this.cluster = cluster;
102
+ this.timeout = 1000; // 1 second
103
+ }
104
+
105
+ /**
106
+ * Evaluate a trigger logic script
107
+ * @param {String} script - JavaScript code to evaluate
108
+ * @param {Object} agent - Agent context
109
+ * @param {Object} message - Triggering message
110
+ * @returns {Boolean} Whether agent should wake up
111
+ */
112
+ evaluate(script, agent, message) {
113
+ try {
114
+ // Build sandbox context
115
+ const context = this._buildContext(agent, message);
116
+
117
+ // Create isolated context with frozen prototypes
118
+ // This prevents prototype pollution attacks
119
+ const isolatedContext = {};
120
+
121
+ // Freeze Object, Array, Function prototypes in the sandbox
122
+ isolatedContext.Object = Object.freeze({ ...Object });
123
+ isolatedContext.Array = Array;
124
+ isolatedContext.Function = Function;
125
+
126
+ // Copy safe context properties
127
+ Object.assign(isolatedContext, context);
128
+
129
+ // Wrap script to prevent prototype access
130
+ const wrappedScript = `(function() {
131
+ 'use strict';
132
+ // Prevent prototype pollution
133
+ const frozenObject = Object;
134
+ const frozenArray = Array;
135
+ Object.freeze(frozenObject.prototype);
136
+ Object.freeze(frozenArray.prototype);
137
+
138
+ ${script}
139
+ })()`;
140
+
141
+ // Create and run in context
142
+ vm.createContext(isolatedContext);
143
+ const result = vm.runInContext(wrappedScript, isolatedContext, {
144
+ timeout: this.timeout,
145
+ displayErrors: true,
146
+ });
147
+
148
+ // Coerce to boolean
149
+ return Boolean(result);
150
+ } catch (error) {
151
+ console.error(`Logic evaluation error for agent ${agent.id}:`, error.message);
152
+ return false; // Default to false on error
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Build sandbox context with APIs and helpers
158
+ * @private
159
+ */
160
+ _buildContext(agent, message) {
161
+ const clusterId = agent.cluster_id;
162
+ const ledgerAPI = buildLedgerAPI(this.messageBus, clusterId);
163
+
164
+ // Build context
165
+ return {
166
+ agent: buildAgentContext(agent),
167
+ message: message || null,
168
+ ledger: ledgerAPI,
169
+ cluster: buildClusterAPI(this.cluster, clusterId),
170
+ helpers: buildHelpers(ledgerAPI),
171
+ ...getSafeBuiltins(),
172
+ console: getQuietConsole(),
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Validate script syntax without executing
178
+ * @param {String} script - JavaScript code
179
+ * @returns {Object} { valid: Boolean, error: String }
180
+ */
181
+ validateScript(script) {
182
+ try {
183
+ // Wrap in function like evaluate() does
184
+ const wrappedScript = `(function() { ${script} })()`;
185
+ new vm.Script(wrappedScript);
186
+ return { valid: true, error: null };
187
+ } catch (error) {
188
+ return { valid: false, error: error.message };
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Set timeout for script execution
194
+ * @param {Number} ms - Timeout in milliseconds
195
+ */
196
+ setTimeout(ms) {
197
+ this.timeout = ms;
198
+ }
199
+ }
200
+
201
+ module.exports = LogicEngine;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Message buffering helper
3
+ *
4
+ * Ensures trigger-matching messages are never dropped just because an agent/subcluster is busy.
5
+ * Dropped workflow signals (e.g. VALIDATION_RESULT) can wedge clusters in "running" state.
6
+ */
7
+
8
+ function bufferMessage(target, message, options = {}) {
9
+ const maxBuffered = options.maxBuffered ?? 200;
10
+
11
+ if (!target._bufferedMessages) {
12
+ target._bufferedMessages = [];
13
+ }
14
+
15
+ if (target._bufferedMessages.length >= maxBuffered) {
16
+ target._bufferedMessages.shift();
17
+ }
18
+
19
+ target._bufferedMessages.push(message);
20
+ }
21
+
22
+ function scheduleDrain(target, drainFn, options = {}) {
23
+ if (target._bufferDrainScheduled) {
24
+ return;
25
+ }
26
+
27
+ target._bufferDrainScheduled = true;
28
+
29
+ const label = options.label || 'MessageBuffer';
30
+ const id = target.id || 'unknown';
31
+
32
+ const run = () => {
33
+ target._bufferDrainScheduled = false;
34
+ drainFn().catch((error) => {
35
+ console.error(`\n${'='.repeat(80)}`);
36
+ console.error(`🔴 FATAL: ${label} drain crashed (${id})`);
37
+ console.error(`${'='.repeat(80)}`);
38
+ console.error(`Error: ${error.message}`);
39
+ console.error(`Stack: ${error.stack}`);
40
+ console.error(`${'='.repeat(80)}\n`);
41
+ setImmediate(() => {
42
+ throw error;
43
+ });
44
+ });
45
+ };
46
+
47
+ const current = target._currentExecution;
48
+ if (current && typeof current.finally === 'function') {
49
+ current.finally(() => setImmediate(run));
50
+ return;
51
+ }
52
+
53
+ setImmediate(run);
54
+ }
55
+
56
+ async function drainBufferedMessages(target, handleFn, options = {}) {
57
+ if (!target.running) {
58
+ return;
59
+ }
60
+
61
+ const buffer = target._bufferedMessages;
62
+ if (!buffer || buffer.length === 0) {
63
+ return;
64
+ }
65
+
66
+ if (target.state !== 'idle') {
67
+ scheduleDrain(target, () => drainBufferedMessages(target, handleFn, options), options);
68
+ return;
69
+ }
70
+
71
+ while (target.running && target.state === 'idle' && buffer.length > 0) {
72
+ const next = buffer.shift();
73
+ await handleFn(next);
74
+ }
75
+ }
76
+
77
+ module.exports = {
78
+ bufferMessage,
79
+ scheduleDrain,
80
+ drainBufferedMessages,
81
+ };