@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
@@ -0,0 +1,693 @@
1
+ /**
2
+ * SubClusterWrapper - Manages nested cluster lifecycle
3
+ *
4
+ * Implements same interface as AgentWrapper but spawns a child Orchestrator
5
+ * instead of a Claude task. Enables recursive cluster composition.
6
+ *
7
+ * Lifecycle:
8
+ * - On trigger match: spawns nested Orchestrator with child cluster config
9
+ * - Passes parent context to child via ISSUE_OPENED equivalent
10
+ * - Listens for child CLUSTER_COMPLETE → executes onComplete hook
11
+ * - Listens for child CLUSTER_FAILED → executes onError hook
12
+ * - Supports maxIterations at subcluster level (restart child on failure)
13
+ */
14
+
15
+ const LogicEngine = require('./logic-engine');
16
+ const MessageBusBridge = require('./message-bus-bridge');
17
+ const { DEFAULT_MAX_ITERATIONS } = require('./agent/agent-config');
18
+ const { bufferMessage, scheduleDrain, drainBufferedMessages } = require('./message-buffer');
19
+
20
+ function normalizeParentTopicConfig(entry) {
21
+ if (typeof entry === 'string') {
22
+ return { topic: entry, amount: 10, strategy: 'latest' };
23
+ }
24
+ if (!entry || typeof entry !== 'object') {
25
+ return null;
26
+ }
27
+ const amount = entry.amount ?? entry.limit;
28
+ const strategy = entry.strategy ?? (amount !== undefined ? 'latest' : 'all');
29
+ return { ...entry, amount, strategy };
30
+ }
31
+
32
+ function selectParentTopicMessages(messageBus, clusterId, topicConfig) {
33
+ const { topic, sender, since, until, amount, strategy } = topicConfig;
34
+ const order = strategy === 'latest' ? 'desc' : 'asc';
35
+ const messages = messageBus.query({
36
+ cluster_id: clusterId,
37
+ topic,
38
+ sender,
39
+ since,
40
+ until,
41
+ limit: amount,
42
+ order,
43
+ });
44
+
45
+ if (strategy === 'latest' && messages.length > 1) {
46
+ return messages.slice().reverse();
47
+ }
48
+
49
+ return messages;
50
+ }
51
+
52
+ class SubClusterWrapper {
53
+ constructor(config, messageBus, parentCluster, options = {}) {
54
+ this.id = config.id;
55
+ this.role = config.role || 'orchestrator';
56
+ this.config = config;
57
+ this.messageBus = messageBus; // Parent message bus
58
+ this.parentCluster = parentCluster;
59
+ this.logicEngine = new LogicEngine(messageBus, parentCluster);
60
+
61
+ this.state = 'idle';
62
+ this.iteration = 0;
63
+ this.maxIterations = config.maxIterations || DEFAULT_MAX_ITERATIONS;
64
+ this.running = false;
65
+ this.unsubscribe = null;
66
+
67
+ // Child cluster state
68
+ this.childCluster = null; // { id, orchestrator, messageBus, bridge }
69
+ this.childClusterId = null;
70
+
71
+ this.quiet = options.quiet || false;
72
+ this.modelOverride = options.modelOverride || null;
73
+ }
74
+
75
+ /**
76
+ * Log message (respects quiet mode)
77
+ * @private
78
+ */
79
+ _log(...args) {
80
+ if (!this.quiet) {
81
+ console.log(...args);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Publish lifecycle event to parent message bus
87
+ * @private
88
+ */
89
+ _publishLifecycle(event, details = {}) {
90
+ this.messageBus.publish({
91
+ cluster_id: this.parentCluster.id,
92
+ topic: 'AGENT_LIFECYCLE',
93
+ sender: this.id,
94
+ receiver: 'system',
95
+ content: {
96
+ text: `${this.id}: ${event}`,
97
+ data: {
98
+ event,
99
+ agent: this.id,
100
+ role: this.role,
101
+ state: this.state,
102
+ type: 'subcluster',
103
+ ...details,
104
+ },
105
+ },
106
+ });
107
+ }
108
+
109
+ /**
110
+ * Start the sub-cluster wrapper (begin listening for triggers)
111
+ */
112
+ start() {
113
+ if (this.running) {
114
+ throw new Error(`SubCluster ${this.id} is already running`);
115
+ }
116
+
117
+ this.running = true;
118
+ this.state = 'idle';
119
+
120
+ // Subscribe to parent cluster messages
121
+ this.unsubscribe = this.messageBus.subscribe((message) => {
122
+ if (message.cluster_id === this.parentCluster.id) {
123
+ this._handleMessage(message).catch((error) => {
124
+ console.error(`\n${'='.repeat(80)}`);
125
+ console.error(`🔴 FATAL: SubCluster ${this.id} message handler crashed`);
126
+ console.error(`${'='.repeat(80)}`);
127
+ console.error(`Topic: ${message.topic}`);
128
+ console.error(`Error: ${error.message}`);
129
+ console.error(`Stack: ${error.stack}`);
130
+ console.error(`${'='.repeat(80)}\n`);
131
+ throw error;
132
+ });
133
+ }
134
+ });
135
+
136
+ this._log(`SubCluster ${this.id} started (role: ${this.role})`);
137
+ this._publishLifecycle('STARTED', {
138
+ triggers: this.config.triggers?.map((t) => t.topic) || [],
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Stop the sub-cluster wrapper
144
+ */
145
+ async stop() {
146
+ if (!this.running) {
147
+ return;
148
+ }
149
+
150
+ this.running = false;
151
+ this.state = 'stopped';
152
+
153
+ if (this.unsubscribe) {
154
+ this.unsubscribe();
155
+ this.unsubscribe = null;
156
+ }
157
+
158
+ // Stop child cluster if running
159
+ if (this.childCluster) {
160
+ await this._stopChildCluster();
161
+ }
162
+
163
+ this._log(`SubCluster ${this.id} stopped`);
164
+ }
165
+
166
+ /**
167
+ * Handle incoming message from parent cluster
168
+ * @private
169
+ */
170
+ async _handleMessage(message) {
171
+ if (!this._bufferedMessages) {
172
+ this._bufferedMessages = [];
173
+ }
174
+
175
+ // Check if any trigger matches
176
+ const matchingTrigger = this._findMatchingTrigger(message);
177
+ if (!matchingTrigger) {
178
+ return;
179
+ }
180
+
181
+ // Check state
182
+ if (!this.running) {
183
+ console.warn(`[${this.id}] ⚠️ DROPPING message (not running): ${message.topic}`);
184
+ return;
185
+ }
186
+ if (this.state !== 'idle') {
187
+ bufferMessage(this, message);
188
+ console.warn(
189
+ `[${this.id}] ⏸️ BUFFERING message (busy, state=${this.state}): ${message.topic}`
190
+ );
191
+ scheduleDrain(
192
+ this,
193
+ () =>
194
+ drainBufferedMessages(this, (next) => this._handleMessage(next), {
195
+ label: 'SubCluster',
196
+ }),
197
+ { label: 'SubCluster' }
198
+ );
199
+ return;
200
+ }
201
+
202
+ // Evaluate trigger logic
203
+ this.state = 'evaluating_logic';
204
+ const shouldExecute = this._evaluateTrigger(matchingTrigger, message);
205
+
206
+ if (!shouldExecute) {
207
+ this.state = 'idle';
208
+ return;
209
+ }
210
+
211
+ // Execute trigger action (spawn child cluster)
212
+ await this._handleTrigger(message);
213
+ }
214
+
215
+ /**
216
+ * Find trigger matching the message topic
217
+ * @private
218
+ */
219
+ _findMatchingTrigger(message) {
220
+ if (!this.config.triggers) {
221
+ return null;
222
+ }
223
+
224
+ return this.config.triggers.find((trigger) => {
225
+ if (trigger.topic === '*' || trigger.topic === message.topic) {
226
+ return true;
227
+ }
228
+ if (trigger.topic.endsWith('*')) {
229
+ const prefix = trigger.topic.slice(0, -1);
230
+ return message.topic.startsWith(prefix);
231
+ }
232
+ return false;
233
+ });
234
+ }
235
+
236
+ /**
237
+ * Evaluate trigger logic
238
+ * @private
239
+ */
240
+ _evaluateTrigger(trigger, message) {
241
+ if (!trigger.logic || !trigger.logic.script) {
242
+ return true;
243
+ }
244
+
245
+ const agent = {
246
+ id: this.id,
247
+ role: this.role,
248
+ iteration: this.iteration,
249
+ cluster_id: this.parentCluster.id,
250
+ };
251
+
252
+ return this.logicEngine.evaluate(trigger.logic.script, agent, message);
253
+ }
254
+
255
+ /**
256
+ * Handle trigger: spawn child cluster
257
+ * @private
258
+ */
259
+ async _handleTrigger(triggeringMessage) {
260
+ // Check max iterations
261
+ if (this.iteration >= this.maxIterations) {
262
+ this._log(`[SubCluster ${this.id}] Hit max iterations (${this.maxIterations}), failing`);
263
+ this._publishLifecycle('MAX_ITERATIONS_REACHED', {
264
+ iteration: this.iteration,
265
+ maxIterations: this.maxIterations,
266
+ });
267
+
268
+ this.messageBus.publish({
269
+ cluster_id: this.parentCluster.id,
270
+ topic: 'CLUSTER_FAILED',
271
+ sender: this.id,
272
+ receiver: 'system',
273
+ content: {
274
+ text: `SubCluster ${this.id} hit max iterations limit (${this.maxIterations})`,
275
+ data: {
276
+ reason: 'max_iterations',
277
+ iteration: this.iteration,
278
+ maxIterations: this.maxIterations,
279
+ },
280
+ },
281
+ });
282
+
283
+ this.state = 'failed';
284
+ return;
285
+ }
286
+
287
+ this.iteration++;
288
+ this.state = 'spawning_child';
289
+
290
+ this._publishLifecycle('SPAWNING_CHILD', {
291
+ iteration: this.iteration,
292
+ triggeredBy: triggeringMessage.topic,
293
+ });
294
+
295
+ try {
296
+ // Build child cluster context from parent messages
297
+ const context = this._buildChildContext(triggeringMessage);
298
+
299
+ // Spawn child cluster
300
+ await this._spawnChildCluster(context);
301
+
302
+ this._publishLifecycle('CHILD_SPAWNED', {
303
+ childClusterId: this.childClusterId,
304
+ iteration: this.iteration,
305
+ });
306
+
307
+ this.state = 'monitoring_child';
308
+ } catch (error) {
309
+ console.error(`\n${'='.repeat(80)}`);
310
+ console.error(`🔴 CHILD CLUSTER SPAWN FAILED - ${this.id}`);
311
+ console.error(`${'='.repeat(80)}`);
312
+ console.error(`Error: ${error.message}`);
313
+ console.error(`Stack: ${error.stack}`);
314
+ console.error(`${'='.repeat(80)}\n`);
315
+
316
+ this.state = 'error';
317
+
318
+ // Execute onError hook
319
+ await this._executeHook('onError', { error, triggeringMessage });
320
+
321
+ // Return to idle if we haven't hit max iterations
322
+ if (this.iteration < this.maxIterations) {
323
+ this.state = 'idle';
324
+ }
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Build context for child cluster from parent messages
330
+ * @private
331
+ */
332
+ _buildChildContext(triggeringMessage) {
333
+ const parentTopics = this.config.contextStrategy?.parentTopics || [];
334
+
335
+ const lines = [
336
+ '# Child Cluster Context',
337
+ '',
338
+ `Parent Cluster: ${this.parentCluster.id}`,
339
+ `SubCluster ID: ${this.id}`,
340
+ `Iteration: ${this.iteration}`,
341
+ '',
342
+ ];
343
+
344
+ this._appendParentTopicContext(lines, parentTopics);
345
+ this._appendTriggeringMessageContext(lines, triggeringMessage);
346
+
347
+ return lines.join('\n');
348
+ }
349
+
350
+ _appendParentTopicContext(lines, parentTopics) {
351
+ if (parentTopics.length === 0) {
352
+ return;
353
+ }
354
+
355
+ lines.push('## Parent Cluster Messages', '');
356
+
357
+ for (const entry of parentTopics) {
358
+ const topicConfig = normalizeParentTopicConfig(entry);
359
+ if (!topicConfig?.topic) {
360
+ continue;
361
+ }
362
+ const topicLines = this._buildTopicContextLines(topicConfig);
363
+ if (topicLines.length === 0) {
364
+ continue;
365
+ }
366
+
367
+ lines.push(...topicLines);
368
+ }
369
+ }
370
+
371
+ _buildTopicContextLines(topicConfig) {
372
+ const messages = selectParentTopicMessages(this.messageBus, this.parentCluster.id, topicConfig);
373
+
374
+ if (messages.length === 0) {
375
+ return [];
376
+ }
377
+
378
+ const lines = [`### Topic: ${topicConfig.topic}`, ''];
379
+
380
+ for (const message of messages) {
381
+ lines.push(...this._buildMessageContextLines(message));
382
+ lines.push('');
383
+ }
384
+
385
+ return lines;
386
+ }
387
+
388
+ _buildMessageContextLines(message) {
389
+ const lines = [`[${new Date(message.timestamp).toISOString()}] ${message.sender}:`];
390
+ const text = message.content?.text;
391
+ const data = message.content?.data;
392
+
393
+ if (text) {
394
+ lines.push(text);
395
+ }
396
+
397
+ if (data) {
398
+ lines.push(`Data: ${JSON.stringify(data, null, 2)}`);
399
+ }
400
+
401
+ return lines;
402
+ }
403
+
404
+ _appendTriggeringMessageContext(lines, triggeringMessage) {
405
+ lines.push(
406
+ '',
407
+ '## Triggering Message',
408
+ '',
409
+ `Topic: ${triggeringMessage.topic}`,
410
+ `Sender: ${triggeringMessage.sender}`
411
+ );
412
+
413
+ const text = triggeringMessage.content?.text;
414
+ if (text) {
415
+ lines.push('', text);
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Spawn child cluster with nested Orchestrator
421
+ * @private
422
+ */
423
+ async _spawnChildCluster(context) {
424
+ const Orchestrator = require('./orchestrator');
425
+ const path = require('path');
426
+
427
+ // Generate child cluster ID (namespaced under parent)
428
+ const childId = `${this.parentCluster.id}.${this.id}`;
429
+ this.childClusterId = childId;
430
+
431
+ // Create child orchestrator with separate database
432
+ const childOrchestrator = await Orchestrator.create({
433
+ quiet: this.quiet,
434
+ skipLoad: true,
435
+ storageDir: path.join(this.parentCluster.ledger.dbPath, '..', 'subclusters', childId),
436
+ });
437
+
438
+ const childConfig = JSON.parse(JSON.stringify(this.config.config));
439
+ const parentConfig = this.parentCluster?.config || {};
440
+
441
+ if (parentConfig.forceProvider) {
442
+ childConfig.forceProvider = parentConfig.forceProvider;
443
+ childConfig.defaultProvider = parentConfig.forceProvider;
444
+ if (parentConfig.forceLevel) {
445
+ childConfig.forceLevel = parentConfig.forceLevel;
446
+ childConfig.defaultLevel = parentConfig.forceLevel;
447
+ }
448
+ } else if (parentConfig.defaultProvider && !childConfig.defaultProvider) {
449
+ childConfig.defaultProvider = parentConfig.defaultProvider;
450
+ }
451
+
452
+ // Start child cluster with text input (context from parent)
453
+ const childCluster = await childOrchestrator.start(
454
+ childConfig, // Child cluster config
455
+ { text: context },
456
+ { testMode: false, modelOverride: this.modelOverride || undefined }
457
+ );
458
+
459
+ // Create message bridge
460
+ const bridge = new MessageBusBridge(this.messageBus, childCluster.messageBus, {
461
+ parentClusterId: this.parentCluster.id,
462
+ childClusterId: childId,
463
+ parentTopics: this.config.contextStrategy?.parentTopics || [],
464
+ });
465
+
466
+ // Store child cluster state
467
+ this.childCluster = {
468
+ id: childId,
469
+ orchestrator: childOrchestrator,
470
+ messageBus: childCluster.messageBus,
471
+ bridge,
472
+ };
473
+
474
+ // Listen for child cluster completion
475
+ childCluster.messageBus.subscribe((message) => {
476
+ if (message.topic === 'CLUSTER_COMPLETE' && message.cluster_id === childId) {
477
+ this._onChildComplete(message).catch((err) => {
478
+ console.error(`Failed to handle child completion: ${err.message}`);
479
+
480
+ // CRITICAL: Hook failure = cluster failure
481
+ this._publishLifecycle('HOOK_FAILED', {
482
+ error: err.message,
483
+ hook: 'onComplete',
484
+ });
485
+
486
+ this.messageBus.publish({
487
+ cluster_id: this.parentCluster.id,
488
+ topic: 'CLUSTER_FAILED',
489
+ sender: this.id,
490
+ content: {
491
+ text: `Hook failed: ${err.message}`,
492
+ data: {
493
+ reason: 'onComplete hook failed',
494
+ error: err.message,
495
+ stack: err.stack,
496
+ },
497
+ },
498
+ });
499
+
500
+ this.state = 'failed';
501
+ throw err;
502
+ });
503
+ }
504
+ });
505
+
506
+ // Listen for child cluster failure
507
+ childCluster.messageBus.subscribe((message) => {
508
+ if (message.topic === 'CLUSTER_FAILED' && message.cluster_id === childId) {
509
+ this._onChildFailed(message).catch((err) => {
510
+ console.error(`Failed to handle child failure: ${err.message}`);
511
+
512
+ // CRITICAL: Hook failure = cluster failure
513
+ this._publishLifecycle('HOOK_FAILED', {
514
+ error: err.message,
515
+ hook: 'onFailed',
516
+ });
517
+
518
+ this.messageBus.publish({
519
+ cluster_id: this.parentCluster.id,
520
+ topic: 'CLUSTER_FAILED',
521
+ sender: this.id,
522
+ content: {
523
+ text: `Hook failed: ${err.message}`,
524
+ data: {
525
+ reason: 'onFailed hook failed',
526
+ error: err.message,
527
+ stack: err.stack,
528
+ },
529
+ },
530
+ });
531
+
532
+ this.state = 'failed';
533
+ throw err;
534
+ });
535
+ }
536
+ });
537
+ }
538
+
539
+ /**
540
+ * Handle child cluster completion
541
+ * @private
542
+ */
543
+ async _onChildComplete(message) {
544
+ this._log(`[SubCluster ${this.id}] Child cluster completed`);
545
+
546
+ this._publishLifecycle('CHILD_COMPLETE', {
547
+ childClusterId: this.childClusterId,
548
+ iteration: this.iteration,
549
+ });
550
+
551
+ // Execute onComplete hook - will throw if verification fails
552
+ await this._executeHook('onComplete', {
553
+ result: message,
554
+ triggeringMessage: null,
555
+ });
556
+
557
+ // Only clean up and transition to idle if hook succeeded
558
+ await this._stopChildCluster();
559
+
560
+ this.state = 'idle';
561
+ }
562
+
563
+ /**
564
+ * Handle child cluster failure
565
+ * @private
566
+ */
567
+ async _onChildFailed(message) {
568
+ this._log(`[SubCluster ${this.id}] Child cluster failed`);
569
+
570
+ this._publishLifecycle('CHILD_FAILED', {
571
+ childClusterId: this.childClusterId,
572
+ iteration: this.iteration,
573
+ error: message.content?.data?.reason,
574
+ });
575
+
576
+ // Execute onError hook
577
+ const error = new Error(message.content?.data?.reason || 'Child cluster failed');
578
+ await this._executeHook('onError', { error, triggeringMessage: null });
579
+
580
+ // Clean up child cluster
581
+ await this._stopChildCluster();
582
+
583
+ // Retry if within max iterations
584
+ if (this.iteration < this.maxIterations) {
585
+ this.state = 'idle';
586
+ } else {
587
+ this.state = 'failed';
588
+ }
589
+ }
590
+
591
+ /**
592
+ * Stop child cluster
593
+ * @private
594
+ */
595
+ async _stopChildCluster() {
596
+ if (!this.childCluster) {
597
+ return;
598
+ }
599
+
600
+ // Close message bridge
601
+ if (this.childCluster.bridge) {
602
+ this.childCluster.bridge.close();
603
+ }
604
+
605
+ // Stop child orchestrator
606
+ try {
607
+ await this.childCluster.orchestrator.stop(this.childCluster.id);
608
+ } catch (err) {
609
+ console.warn(`Warning: Failed to stop child cluster ${this.childCluster.id}: ${err.message}`);
610
+ }
611
+
612
+ this.childCluster = null;
613
+ this.childClusterId = null;
614
+ }
615
+
616
+ /**
617
+ * Execute a hook
618
+ * @private
619
+ */
620
+ _executeHook(hookName, context) {
621
+ const hook = this.config.hooks?.[hookName];
622
+ if (!hook) {
623
+ return;
624
+ }
625
+
626
+ if (hook.action === 'publish_message') {
627
+ const message = this._substituteTemplate(hook.config, context);
628
+ this.messageBus.publish({
629
+ cluster_id: this.parentCluster.id,
630
+ sender: this.id,
631
+ ...message,
632
+ });
633
+ } else {
634
+ throw new Error(`Unknown hook action: ${hook.action}`);
635
+ }
636
+ }
637
+
638
+ /**
639
+ * Substitute template variables in hook config
640
+ * @private
641
+ */
642
+ _substituteTemplate(config, context) {
643
+ if (!config) {
644
+ throw new Error('_substituteTemplate: config is required');
645
+ }
646
+
647
+ const json = JSON.stringify(config);
648
+
649
+ let substituted = json
650
+ .replace(/\{\{cluster\.id\}\}/g, this.parentCluster.id)
651
+ .replace(/\{\{subcluster\.id\}\}/g, this.id)
652
+ .replace(/\{\{child\.id\}\}/g, this.childClusterId || '')
653
+ .replace(/\{\{iteration\}\}/g, String(this.iteration))
654
+ .replace(/\{\{error\.message\}\}/g, context.error?.message || '');
655
+
656
+ // Parse and validate
657
+ let result;
658
+ try {
659
+ result = JSON.parse(substituted);
660
+ } catch (e) {
661
+ console.error('JSON parse failed. Substituted string:');
662
+ console.error(substituted);
663
+ throw new Error(`Template substitution produced invalid JSON: ${e.message}`);
664
+ }
665
+
666
+ return result;
667
+ }
668
+
669
+ /**
670
+ * Resume sub-cluster task (not implemented for subclusters)
671
+ */
672
+ resume(_resumeContext) {
673
+ throw new Error('Resume not implemented for subclusters');
674
+ }
675
+
676
+ /**
677
+ * Get current sub-cluster state
678
+ */
679
+ getState() {
680
+ return {
681
+ id: this.id,
682
+ role: this.role,
683
+ state: this.state,
684
+ iteration: this.iteration,
685
+ maxIterations: this.maxIterations,
686
+ type: 'subcluster',
687
+ childClusterId: this.childClusterId,
688
+ childRunning: this.childCluster !== null,
689
+ };
690
+ }
691
+ }
692
+
693
+ module.exports = SubClusterWrapper;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * TaskRunner - Strategy Pattern interface for executing provider tasks
3
+ *
4
+ * Implementations must provide a `run()` method that executes a task
5
+ * with the given context and options. Different runners can implement various
6
+ * execution strategies (CLI, mock responses, etc).
7
+ */
8
+ class TaskRunner {
9
+ /**
10
+ * Execute a provider task with the given context and options
11
+ *
12
+ * @param {string} _context - Full prompt/context for the provider to process
13
+ * @param {Object} _options - Execution options
14
+ * @param {string} _options.agentId - Identifier for this agent/task
15
+ * @param {string} _options.model - Model to use (provider-specific model id)
16
+ * @param {string} [_options.provider] - Provider to use (claude|codex|gemini)
17
+ * @param {object} [_options.modelSpec] - Resolved model spec (level/model/reasoningEffort)
18
+ * @param {string} [_options.outputFormat] - Output format ('text', 'json', 'stream-json')
19
+ * @param {Object} [_options.jsonSchema] - JSON schema for structured output validation
20
+ * @param {string} [_options.cwd] - Working directory for task execution
21
+ * @param {boolean} [_options.isolation] - Whether to run in isolated container
22
+ *
23
+ * @returns {Promise<{success: boolean, output: string, error: string|null, taskId?: string}>} Result object with success status, output, error message, and optional taskId
24
+ */
25
+ run(_context, _options) {
26
+ throw new Error('TaskRunner.run() not implemented');
27
+ }
28
+ }
29
+
30
+ module.exports = TaskRunner;