@honeybee-ai/incubator 1.1.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 (245) hide show
  1. package/README.md +586 -0
  2. package/dashboard/dist/assets/index-DFb8p7xI.js +9 -0
  3. package/dashboard/dist/assets/index-RKiEoHEo.css +1 -0
  4. package/dashboard/dist/index.html +13 -0
  5. package/dist/agent/acp/claim-manager.d.ts +24 -0
  6. package/dist/agent/acp/claim-manager.js +64 -0
  7. package/dist/agent/acp/claim-manager.js.map +1 -0
  8. package/dist/agent/acp/direct-runtime.d.ts +90 -0
  9. package/dist/agent/acp/direct-runtime.js +364 -0
  10. package/dist/agent/acp/direct-runtime.js.map +1 -0
  11. package/dist/agent/acp/event-client.d.ts +20 -0
  12. package/dist/agent/acp/event-client.js +60 -0
  13. package/dist/agent/acp/event-client.js.map +1 -0
  14. package/dist/agent/acp/event-matcher.d.ts +13 -0
  15. package/dist/agent/acp/event-matcher.js +31 -0
  16. package/dist/agent/acp/event-matcher.js.map +1 -0
  17. package/dist/agent/acp/progress.d.ts +23 -0
  18. package/dist/agent/acp/progress.js +54 -0
  19. package/dist/agent/acp/progress.js.map +1 -0
  20. package/dist/agent/acp/runtime.d.ts +156 -0
  21. package/dist/agent/acp/runtime.js +337 -0
  22. package/dist/agent/acp/runtime.js.map +1 -0
  23. package/dist/agent/acp/ws-event-client.d.ts +64 -0
  24. package/dist/agent/acp/ws-event-client.js +263 -0
  25. package/dist/agent/acp/ws-event-client.js.map +1 -0
  26. package/dist/agent/agent.d.ts +60 -0
  27. package/dist/agent/agent.js +121 -0
  28. package/dist/agent/agent.js.map +1 -0
  29. package/dist/agent/cli.d.ts +2 -0
  30. package/dist/agent/cli.js +311 -0
  31. package/dist/agent/cli.js.map +1 -0
  32. package/dist/agent/mcp-client.d.ts +37 -0
  33. package/dist/agent/mcp-client.js +92 -0
  34. package/dist/agent/mcp-client.js.map +1 -0
  35. package/dist/agent/mock-runner.d.ts +14 -0
  36. package/dist/agent/mock-runner.js +159 -0
  37. package/dist/agent/mock-runner.js.map +1 -0
  38. package/dist/agent/native-client.d.ts +18 -0
  39. package/dist/agent/native-client.js +42 -0
  40. package/dist/agent/native-client.js.map +1 -0
  41. package/dist/agent/prompt.d.ts +45 -0
  42. package/dist/agent/prompt.js +115 -0
  43. package/dist/agent/prompt.js.map +1 -0
  44. package/dist/agent/providers.d.ts +25 -0
  45. package/dist/agent/providers.js +696 -0
  46. package/dist/agent/providers.js.map +1 -0
  47. package/dist/agent/runner.d.ts +15 -0
  48. package/dist/agent/runner.js +625 -0
  49. package/dist/agent/runner.js.map +1 -0
  50. package/dist/agent/tool-client.d.ts +12 -0
  51. package/dist/agent/tool-client.js +2 -0
  52. package/dist/agent/tool-client.js.map +1 -0
  53. package/dist/agent/types.d.ts +116 -0
  54. package/dist/agent/types.js +2 -0
  55. package/dist/agent/types.js.map +1 -0
  56. package/dist/agent-pool.d.ts +44 -0
  57. package/dist/agent-pool.js +228 -0
  58. package/dist/agent-pool.js.map +1 -0
  59. package/dist/bin.d.ts +2 -0
  60. package/dist/bin.js +7 -0
  61. package/dist/bin.js.map +1 -0
  62. package/dist/bus.d.ts +24 -0
  63. package/dist/bus.js +79 -0
  64. package/dist/bus.js.map +1 -0
  65. package/dist/dances.d.ts +73 -0
  66. package/dist/dances.js +122 -0
  67. package/dist/dances.js.map +1 -0
  68. package/dist/guard.d.ts +52 -0
  69. package/dist/guard.js +210 -0
  70. package/dist/guard.js.map +1 -0
  71. package/dist/heartbeat.d.ts +41 -0
  72. package/dist/heartbeat.js +104 -0
  73. package/dist/heartbeat.js.map +1 -0
  74. package/dist/honeycomb.d.ts +63 -0
  75. package/dist/honeycomb.js +222 -0
  76. package/dist/honeycomb.js.map +1 -0
  77. package/dist/index.d.ts +2 -0
  78. package/dist/index.js +601 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/integrations/config.d.ts +15 -0
  81. package/dist/integrations/config.js +62 -0
  82. package/dist/integrations/config.js.map +1 -0
  83. package/dist/integrations/index.d.ts +4 -0
  84. package/dist/integrations/index.js +4 -0
  85. package/dist/integrations/index.js.map +1 -0
  86. package/dist/integrations/loader.d.ts +8 -0
  87. package/dist/integrations/loader.js +27 -0
  88. package/dist/integrations/loader.js.map +1 -0
  89. package/dist/integrations/manager.d.ts +29 -0
  90. package/dist/integrations/manager.js +108 -0
  91. package/dist/integrations/manager.js.map +1 -0
  92. package/dist/log.d.ts +25 -0
  93. package/dist/log.js +67 -0
  94. package/dist/log.js.map +1 -0
  95. package/dist/namespaces.d.ts +28 -0
  96. package/dist/namespaces.js +100 -0
  97. package/dist/namespaces.js.map +1 -0
  98. package/dist/orchestrator.d.ts +119 -0
  99. package/dist/orchestrator.js +463 -0
  100. package/dist/orchestrator.js.map +1 -0
  101. package/dist/persistence.d.ts +7 -0
  102. package/dist/persistence.js +62 -0
  103. package/dist/persistence.js.map +1 -0
  104. package/dist/plugins/index.d.ts +2 -0
  105. package/dist/plugins/index.js +3 -0
  106. package/dist/plugins/index.js.map +1 -0
  107. package/dist/plugins/loader.d.ts +12 -0
  108. package/dist/plugins/loader.js +122 -0
  109. package/dist/plugins/loader.js.map +1 -0
  110. package/dist/plugins/manager.d.ts +76 -0
  111. package/dist/plugins/manager.js +238 -0
  112. package/dist/plugins/manager.js.map +1 -0
  113. package/dist/propolis/guard.d.ts +23 -0
  114. package/dist/propolis/guard.js +49 -0
  115. package/dist/propolis/guard.js.map +1 -0
  116. package/dist/propolis/tools/types.d.ts +9 -0
  117. package/dist/propolis/tools/types.js +9 -0
  118. package/dist/propolis/tools/types.js.map +1 -0
  119. package/dist/rest.d.ts +4 -0
  120. package/dist/rest.js +962 -0
  121. package/dist/rest.js.map +1 -0
  122. package/dist/run-watcher.d.ts +20 -0
  123. package/dist/run-watcher.js +74 -0
  124. package/dist/run-watcher.js.map +1 -0
  125. package/dist/server.d.ts +17 -0
  126. package/dist/server.js +412 -0
  127. package/dist/server.js.map +1 -0
  128. package/dist/stores/backend.d.ts +15 -0
  129. package/dist/stores/backend.js +28 -0
  130. package/dist/stores/backend.js.map +1 -0
  131. package/dist/stores/claims.d.ts +14 -0
  132. package/dist/stores/claims.js +77 -0
  133. package/dist/stores/claims.js.map +1 -0
  134. package/dist/stores/conflicts.d.ts +10 -0
  135. package/dist/stores/conflicts.js +39 -0
  136. package/dist/stores/conflicts.js.map +1 -0
  137. package/dist/stores/control.d.ts +37 -0
  138. package/dist/stores/control.js +105 -0
  139. package/dist/stores/control.js.map +1 -0
  140. package/dist/stores/discoveries.d.ts +11 -0
  141. package/dist/stores/discoveries.js +45 -0
  142. package/dist/stores/discoveries.js.map +1 -0
  143. package/dist/stores/events.d.ts +14 -0
  144. package/dist/stores/events.js +42 -0
  145. package/dist/stores/events.js.map +1 -0
  146. package/dist/stores/help.d.ts +11 -0
  147. package/dist/stores/help.js +46 -0
  148. package/dist/stores/help.js.map +1 -0
  149. package/dist/stores/interfaces.d.ts +125 -0
  150. package/dist/stores/interfaces.js +2 -0
  151. package/dist/stores/interfaces.js.map +1 -0
  152. package/dist/stores/messages.d.ts +8 -0
  153. package/dist/stores/messages.js +29 -0
  154. package/dist/stores/messages.js.map +1 -0
  155. package/dist/stores/progress.d.ts +8 -0
  156. package/dist/stores/progress.js +21 -0
  157. package/dist/stores/progress.js.map +1 -0
  158. package/dist/stores/proposals.d.ts +11 -0
  159. package/dist/stores/proposals.js +46 -0
  160. package/dist/stores/proposals.js.map +1 -0
  161. package/dist/stores/redis/claims.d.ts +16 -0
  162. package/dist/stores/redis/claims.js +126 -0
  163. package/dist/stores/redis/claims.js.map +1 -0
  164. package/dist/stores/redis/db.d.ts +39 -0
  165. package/dist/stores/redis/db.js +34 -0
  166. package/dist/stores/redis/db.js.map +1 -0
  167. package/dist/stores/redis/discoveries.d.ts +13 -0
  168. package/dist/stores/redis/discoveries.js +54 -0
  169. package/dist/stores/redis/discoveries.js.map +1 -0
  170. package/dist/stores/redis/events.d.ts +17 -0
  171. package/dist/stores/redis/events.js +57 -0
  172. package/dist/stores/redis/events.js.map +1 -0
  173. package/dist/stores/redis/index.d.ts +3 -0
  174. package/dist/stores/redis/index.js +31 -0
  175. package/dist/stores/redis/index.js.map +1 -0
  176. package/dist/stores/redis/state.d.ts +14 -0
  177. package/dist/stores/redis/state.js +83 -0
  178. package/dist/stores/redis/state.js.map +1 -0
  179. package/dist/stores/reinforcements.d.ts +11 -0
  180. package/dist/stores/reinforcements.js +42 -0
  181. package/dist/stores/reinforcements.js.map +1 -0
  182. package/dist/stores/roles.d.ts +9 -0
  183. package/dist/stores/roles.js +22 -0
  184. package/dist/stores/roles.js.map +1 -0
  185. package/dist/stores/runs.d.ts +15 -0
  186. package/dist/stores/runs.js +50 -0
  187. package/dist/stores/runs.js.map +1 -0
  188. package/dist/stores/sqlite/claims.d.ts +17 -0
  189. package/dist/stores/sqlite/claims.js +121 -0
  190. package/dist/stores/sqlite/claims.js.map +1 -0
  191. package/dist/stores/sqlite/db.d.ts +16 -0
  192. package/dist/stores/sqlite/db.js +77 -0
  193. package/dist/stores/sqlite/db.js.map +1 -0
  194. package/dist/stores/sqlite/discoveries.d.ts +14 -0
  195. package/dist/stores/sqlite/discoveries.js +66 -0
  196. package/dist/stores/sqlite/discoveries.js.map +1 -0
  197. package/dist/stores/sqlite/events.d.ts +16 -0
  198. package/dist/stores/sqlite/events.js +75 -0
  199. package/dist/stores/sqlite/events.js.map +1 -0
  200. package/dist/stores/sqlite/index.d.ts +2 -0
  201. package/dist/stores/sqlite/index.js +33 -0
  202. package/dist/stores/sqlite/index.js.map +1 -0
  203. package/dist/stores/sqlite/state.d.ts +15 -0
  204. package/dist/stores/sqlite/state.js +99 -0
  205. package/dist/stores/sqlite/state.js.map +1 -0
  206. package/dist/stores/state.d.ts +11 -0
  207. package/dist/stores/state.js +67 -0
  208. package/dist/stores/state.js.map +1 -0
  209. package/dist/transports/broker.d.ts +20 -0
  210. package/dist/transports/broker.js +102 -0
  211. package/dist/transports/broker.js.map +1 -0
  212. package/dist/transports/index.d.ts +3 -0
  213. package/dist/transports/index.js +3 -0
  214. package/dist/transports/index.js.map +1 -0
  215. package/dist/transports/ipc.d.ts +26 -0
  216. package/dist/transports/ipc.js +93 -0
  217. package/dist/transports/ipc.js.map +1 -0
  218. package/dist/transports/types.d.ts +39 -0
  219. package/dist/transports/types.js +8 -0
  220. package/dist/transports/types.js.map +1 -0
  221. package/dist/types.d.ts +45 -0
  222. package/dist/types.js +2 -0
  223. package/dist/types.js.map +1 -0
  224. package/dist/utils.d.ts +3 -0
  225. package/dist/utils.js +36 -0
  226. package/dist/utils.js.map +1 -0
  227. package/dist/waggle/client.d.ts +16 -0
  228. package/dist/waggle/client.js +28 -0
  229. package/dist/waggle/client.js.map +1 -0
  230. package/dist/waggle/compound.d.ts +22 -0
  231. package/dist/waggle/compound.js +194 -0
  232. package/dist/waggle/compound.js.map +1 -0
  233. package/dist/waggle/index.d.ts +25 -0
  234. package/dist/waggle/index.js +77 -0
  235. package/dist/waggle/index.js.map +1 -0
  236. package/dist/waggle/types.d.ts +54 -0
  237. package/dist/waggle/types.js +2 -0
  238. package/dist/waggle/types.js.map +1 -0
  239. package/dist/webhooks.d.ts +26 -0
  240. package/dist/webhooks.js +79 -0
  241. package/dist/webhooks.js.map +1 -0
  242. package/dist/ws.d.ts +33 -0
  243. package/dist/ws.js +195 -0
  244. package/dist/ws.js.map +1 -0
  245. package/package.json +122 -0
@@ -0,0 +1,625 @@
1
+ import { chatCompletion, getToolCallArgs } from './providers.js';
2
+ import { generateSystemPrompt, generateFallbackPrompt } from './prompt.js';
3
+ import { createAcpClient } from '@agentcoordinationprotocol/sdk';
4
+ // ─── ANSI colors for agent logs ─────────────────────────────────────
5
+ const COLORS = [
6
+ '\x1b[36m', // cyan
7
+ '\x1b[33m', // yellow
8
+ '\x1b[35m', // magenta
9
+ '\x1b[32m', // green
10
+ '\x1b[34m', // blue
11
+ '\x1b[91m', // bright red
12
+ '\x1b[92m', // bright green
13
+ '\x1b[93m', // bright yellow
14
+ '\x1b[94m', // bright blue
15
+ '\x1b[95m', // bright magenta
16
+ '\x1b[96m', // bright cyan
17
+ ];
18
+ const RESET = '\x1b[0m';
19
+ const DIM = '\x1b[2m';
20
+ let colorIndex = 0;
21
+ // ─── File write tools that trigger auto-claims ──────────────────────
22
+ const FILE_WRITE_TOOLS = new Set(['write_file', 'patch_file']);
23
+ // ─── Coordination tool presets ───────────────────────────────────────
24
+ /** Core ACP primitives — minimal coordination set */
25
+ const LITE_TOOLS = new Set([
26
+ 'incubator_claim',
27
+ 'incubator_releaseClaim',
28
+ 'incubator_setState',
29
+ 'incubator_publishEvent',
30
+ ]);
31
+ // ─── Synthetic ACP tools (invisible mode) ───────────────────────────
32
+ /** Tool names handled by the ACP runtime, not by MCP clients */
33
+ const SYNTHETIC_TOOL_NAMES = new Set(['publish_event', 'set_state', 'get_state', 'claim_resource', 'release_resource']);
34
+ const SYNTHETIC_TOOLS = [
35
+ {
36
+ type: 'function',
37
+ function: {
38
+ name: 'publish_event',
39
+ description: 'Publish a coordination event to notify other agents of progress or milestones',
40
+ parameters: {
41
+ type: 'object',
42
+ properties: {
43
+ type: { type: 'string', description: 'Event type (e.g. section.written, review.complete)' },
44
+ data: { type: 'string', description: 'JSON object with event payload data (optional)' },
45
+ },
46
+ required: ['type'],
47
+ },
48
+ },
49
+ },
50
+ {
51
+ type: 'function',
52
+ function: {
53
+ name: 'set_state',
54
+ description: 'Set a shared state key visible to all agents in this hive',
55
+ parameters: {
56
+ type: 'object',
57
+ properties: {
58
+ key: { type: 'string', description: 'State key name' },
59
+ value: { type: 'string', description: 'Value to set (string or JSON)' },
60
+ },
61
+ required: ['key', 'value'],
62
+ },
63
+ },
64
+ },
65
+ {
66
+ type: 'function',
67
+ function: {
68
+ name: 'get_state',
69
+ description: 'Get shared state for this hive. Use key="all" to get everything, or a specific key name.',
70
+ parameters: {
71
+ type: 'object',
72
+ properties: {
73
+ key: { type: 'string', description: 'State key to retrieve, or "all" for all state' },
74
+ },
75
+ required: ['key'],
76
+ },
77
+ },
78
+ },
79
+ {
80
+ type: 'function',
81
+ function: {
82
+ name: 'claim_resource',
83
+ description: 'Claim exclusive ownership of a resource (e.g. a topic, file, or task). Returns rejected if another agent already holds the claim.',
84
+ parameters: {
85
+ type: 'object',
86
+ properties: {
87
+ resource: { type: 'string', description: 'Resource identifier to claim (e.g. "topic:quantum-computing")' },
88
+ reason: { type: 'string', description: 'Why you need this resource' },
89
+ },
90
+ required: ['resource'],
91
+ },
92
+ },
93
+ },
94
+ {
95
+ type: 'function',
96
+ function: {
97
+ name: 'release_resource',
98
+ description: 'Release a previously claimed resource so other agents can use it',
99
+ parameters: {
100
+ type: 'object',
101
+ properties: {
102
+ resource: { type: 'string', description: 'Resource identifier to release' },
103
+ },
104
+ required: ['resource'],
105
+ },
106
+ },
107
+ },
108
+ ];
109
+ // ─── Start trigger helpers ──────────────────────────────────────────
110
+ export function describeStartOn(config) {
111
+ const events = config.conditions
112
+ .map(c => c.count > 1 ? `${c.count}x ${c.event}` : c.event)
113
+ .join(' + ');
114
+ return config.timeout > 0
115
+ ? `${events} (timeout: ${config.timeout}s)`
116
+ : `${events} (no timeout)`;
117
+ }
118
+ export async function waitForEvents(config, log) {
119
+ const { startOn } = config;
120
+ if (!startOn)
121
+ return;
122
+ const client = createAcpClient({
123
+ server: config.serverUrl,
124
+ agentId: config.agentId,
125
+ namespace: config.namespace,
126
+ });
127
+ const deadline = startOn.timeout > 0 ? Date.now() + startOn.timeout * 1000 : 0;
128
+ const POLL_INTERVAL = 2000;
129
+ while (deadline === 0 || Date.now() < deadline) {
130
+ let allMet = true;
131
+ for (const cond of startOn.conditions) {
132
+ const res = await client.getEvents(0, { type: cond.event });
133
+ if (!res.ok) {
134
+ allMet = false;
135
+ break;
136
+ }
137
+ // Incubator returns { events: [...], cursor: N }
138
+ const body = res.data;
139
+ const events = Array.isArray(body) ? body : body?.events;
140
+ if (!events || events.length < cond.count) {
141
+ allMet = false;
142
+ break;
143
+ }
144
+ }
145
+ if (allMet)
146
+ return;
147
+ await new Promise(r => setTimeout(r, POLL_INTERVAL));
148
+ }
149
+ log(`Timeout (${startOn.timeout}s) — starting anyway`);
150
+ }
151
+ // ─── Default context window sizes by provider ──────────────────────
152
+ const DEFAULT_CONTEXT_WINDOWS = {
153
+ cerebras: 128_000,
154
+ groq: 128_000,
155
+ ollama: 8_000,
156
+ openai: 128_000,
157
+ anthropic: 200_000,
158
+ };
159
+ const DEFAULT_COMPACT_THRESHOLD = 0.8;
160
+ const DEFAULT_KEEP_LAST_N = 6;
161
+ const DEFAULT_MAX_RETRIES = 3;
162
+ // ─── AgentRunner (renamed from DroneRunner) ─────────────────────────
163
+ export class AgentRunner {
164
+ stopFlag = false;
165
+ stop() {
166
+ this.stopFlag = true;
167
+ }
168
+ async run(config, toolClient, incubatorClient, runtime, protocolOverride, telemetry) {
169
+ const color = COLORS[colorIndex++ % COLORS.length];
170
+ const { agentId, role, provider, maxIterations } = config;
171
+ let iterations = 0;
172
+ let wakeCount = 0;
173
+ const startTime = Date.now();
174
+ const totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
175
+ const iterationUsage = [];
176
+ const log = (msg) => {
177
+ if (config.verbose) {
178
+ const ts = new Date().toISOString().slice(11, 23);
179
+ console.error(`${color} ${ts} [${agentId}] ${msg}${RESET}`);
180
+ }
181
+ };
182
+ let exitReason = 'unknown';
183
+ try {
184
+ // 0a. Connect ACP runtime (invisible coordination)
185
+ if (runtime) {
186
+ await runtime.connect();
187
+ log('ACP runtime connected');
188
+ }
189
+ // 0b. Wait for start trigger if configured
190
+ if (config.startOn) {
191
+ log(`Waiting for trigger: ${describeStartOn(config.startOn)}`);
192
+ await waitForEvents(config, log);
193
+ log('Trigger conditions met — starting agent');
194
+ }
195
+ // 1. Build tool definitions (with optional filtering)
196
+ let toolDefs = [...toolClient.getToolDefs()];
197
+ // Filter tools if whitelist provided (only for MCP clients — NativeToolClient pre-filters)
198
+ if (config.toolFilter && config.mode === 'drone') {
199
+ const filterSet = new Set(config.toolFilter);
200
+ toolDefs = toolDefs.filter(t => filterSet.has(t.function.name));
201
+ }
202
+ // In --no-acp mode, also include incubator tools (with coordination filtering)
203
+ if (incubatorClient) {
204
+ let incTools = incubatorClient.getToolDefs();
205
+ const coord = config.coordination ?? 'full';
206
+ if (coord === 'lite') {
207
+ incTools = incTools.filter(t => LITE_TOOLS.has(t.function.name));
208
+ }
209
+ else if (coord === 'none') {
210
+ incTools = [];
211
+ }
212
+ else if (Array.isArray(coord)) {
213
+ const coordSet = new Set(coord);
214
+ incTools = incTools.filter(t => coordSet.has(t.function.name));
215
+ }
216
+ // 'full' = no filtering
217
+ toolDefs.push(...incTools);
218
+ log(`Tools: ${toolDefs.length} (env: ${toolDefs.length - incTools.length}, incubator: ${incTools.length})`);
219
+ }
220
+ else if (runtime && !config.noAcpInject) {
221
+ // Invisible ACP mode: add synthetic coordination tools
222
+ toolDefs.push(...SYNTHETIC_TOOLS);
223
+ log(`Tools: ${toolDefs.length} (env: ${toolDefs.length - SYNTHETIC_TOOLS.length}, acp: ${SYNTHETIC_TOOLS.length})`);
224
+ }
225
+ else {
226
+ log(`Tools: ${toolDefs.length} (env only)`);
227
+ }
228
+ // 2. Fetch protocol and generate system prompt
229
+ let protocolData = protocolOverride ?? null;
230
+ if (!protocolData && runtime) {
231
+ protocolData = await runtime.fetchProtocol();
232
+ }
233
+ // Check for dance tools from server — these REPLACE synthetic ACP tools
234
+ let danceToolNames = null;
235
+ if (runtime) {
236
+ const danceTools = runtime.getDanceTools();
237
+ if (danceTools && danceTools.length > 0) {
238
+ // Dance tools replace ALL other tools — the LLM only sees dance-defined tools
239
+ toolDefs = [...danceTools];
240
+ danceToolNames = new Set(danceTools.map(t => t.function.name));
241
+ log(`Dance tools: ${danceTools.map(t => t.function.name).join(', ')} (exclusive — replaced all env/acp tools)`);
242
+ }
243
+ }
244
+ // Derive wakeOn from spec wait field if not explicitly configured
245
+ let effectiveWakeOn = config.wakeOn ?? null;
246
+ if (protocolData && !effectiveWakeOn && protocolData.wait) {
247
+ effectiveWakeOn = {
248
+ types: protocolData.wait.types ?? null,
249
+ timeout: protocolData.wait.max_timeout ?? 0,
250
+ maxWakes: 50,
251
+ };
252
+ log(`Derived wakeOn from spec: types=${protocolData.wait.types?.join(',') ?? 'any'}`);
253
+ }
254
+ // Derive context retention from spec (0 = stateless, N = keep last N exchanges, undefined = full history)
255
+ const contextRetention = protocolData?.context;
256
+ if (contextRetention !== undefined) {
257
+ log(`Context retention: ${contextRetention === 0 ? 'stateless' : `last ${contextRetention} exchanges`}`);
258
+ }
259
+ // Spec temperature overrides config
260
+ const effectiveTemperature = protocolData?.temperature ?? config.temperature;
261
+ const disableReasoning = protocolData?.reasoning === false;
262
+ const reasoningEffort = protocolData?.reasoning_effort;
263
+ const systemPrompt = protocolData
264
+ ? generateSystemPrompt(agentId, role, toolDefs, protocolData, { disableReasoning })
265
+ : generateFallbackPrompt(agentId, role, toolDefs);
266
+ // 3. Build initial messages
267
+ const messages = [
268
+ { role: 'system', content: systemPrompt },
269
+ { role: 'user', content: config.prompt ?? 'Begin your work. Explore the codebase, understand the task, and implement the required changes.' },
270
+ ];
271
+ log(`Starting (role=${role}, model=${provider.model}, mode=${config.mode})`);
272
+ // SIGTERM handler for kill signal
273
+ const onKill = () => {
274
+ log('SIGTERM received — killing agent');
275
+ this.stopFlag = true;
276
+ };
277
+ process.on('SIGTERM', onKill);
278
+ // Context compaction config
279
+ const contextWindow = config.contextWindow ?? DEFAULT_CONTEXT_WINDOWS[provider.type] ?? 128_000;
280
+ const compactThreshold = Math.floor(contextWindow * DEFAULT_COMPACT_THRESHOLD);
281
+ const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
282
+ // 4. Initial sleep — wait for first wake event before running
283
+ if (effectiveWakeOn && runtime) {
284
+ log(`Sleeping — waiting for initial wake event`);
285
+ const wakeEvents = await runtime.waitForWake(effectiveWakeOn);
286
+ if (wakeEvents.length === 0) {
287
+ log('Initial wake timeout — no events, exiting');
288
+ if (runtime)
289
+ await runtime.onComplete('Wake timeout', totalUsage);
290
+ process.removeListener('SIGTERM', onKill);
291
+ return { iterations: 0, usage: totalUsage, agentId, role, status: 'completed' };
292
+ }
293
+ log(`Woke up with ${wakeEvents.length} event(s)`);
294
+ const inject = runtime.getLastInject();
295
+ if (inject) {
296
+ messages.push({ role: 'user', content: `[SYSTEM] ${inject}` });
297
+ log('Injected dance state context');
298
+ }
299
+ for (const evt of wakeEvents) {
300
+ messages.push({ role: 'user', content: `[SYSTEM] ${evt}` });
301
+ }
302
+ }
303
+ // 5. ReAct loop
304
+ while (iterations < maxIterations && !this.stopFlag) {
305
+ iterations++;
306
+ log(`Iteration ${iterations}/${maxIterations}`);
307
+ // Cost controls: maxTotalTokens
308
+ if (config.maxTotalTokens && config.maxTotalTokens > 0 && totalUsage.totalTokens >= config.maxTotalTokens) {
309
+ log(`Token budget exhausted (${totalUsage.totalTokens.toLocaleString()}/${config.maxTotalTokens.toLocaleString()})`);
310
+ break;
311
+ }
312
+ // Cost controls: maxRuntime
313
+ if (config.maxRuntime && config.maxRuntime > 0 && (Date.now() - startTime) >= config.maxRuntime) {
314
+ log(`Runtime limit reached (${config.maxRuntime}ms)`);
315
+ break;
316
+ }
317
+ // Inject coordination context (invisible ACP events + dance state)
318
+ if (runtime) {
319
+ const contextMessages = await runtime.beforeIteration();
320
+ const inject = runtime.getLastInject();
321
+ // Context retention: trim history based on spec setting
322
+ if (contextRetention !== undefined && inject) {
323
+ if (contextRetention === 0) {
324
+ messages.length = 1; // stateless — keep system prompt only
325
+ }
326
+ else if (messages.length > 1 + contextRetention * 2) {
327
+ // Keep system prompt + last N exchanges (assistant + tool pairs)
328
+ const kept = messages.slice(-(contextRetention * 2));
329
+ messages.length = 1;
330
+ messages.push(...kept);
331
+ }
332
+ }
333
+ for (const msg of contextMessages) {
334
+ messages.push({ role: 'user', content: `[SYSTEM] ${msg}` });
335
+ }
336
+ if (inject) {
337
+ messages.push({ role: 'user', content: `[SYSTEM] ${inject}` });
338
+ }
339
+ }
340
+ // Call LLM with retry logic
341
+ let response;
342
+ let usage;
343
+ let retryCount = 0;
344
+ while (true) {
345
+ const llmStart = Date.now();
346
+ try {
347
+ const result = await chatCompletion(provider, messages, toolDefs, effectiveTemperature, {
348
+ ...(disableReasoning ? { disableReasoning: true } : {}),
349
+ ...(reasoningEffort ? { reasoningEffort } : {}),
350
+ });
351
+ response = result.message;
352
+ usage = result.usage;
353
+ telemetry?.record('llm_call', {
354
+ agentId, role, provider: provider.type, model: provider.model,
355
+ promptTokens: usage.promptTokens, completionTokens: usage.completionTokens,
356
+ latency_ms: Date.now() - llmStart,
357
+ });
358
+ break;
359
+ }
360
+ catch (err) {
361
+ retryCount++;
362
+ telemetry?.record('llm_error', {
363
+ agentId, role, provider: provider.type, model: provider.model,
364
+ error: err.message, retryCount,
365
+ latency_ms: Date.now() - llmStart,
366
+ });
367
+ if (retryCount > maxRetries)
368
+ throw err;
369
+ const delay = 1000 * Math.pow(2, retryCount - 1);
370
+ log(`LLM call failed (attempt ${retryCount}/${maxRetries}): ${err.message} — retrying in ${delay}ms`);
371
+ await new Promise(r => setTimeout(r, delay));
372
+ }
373
+ }
374
+ messages.push(response);
375
+ // Track token usage
376
+ totalUsage.promptTokens += usage.promptTokens;
377
+ totalUsage.completionTokens += usage.completionTokens;
378
+ totalUsage.totalTokens += usage.totalTokens;
379
+ iterationUsage.push(usage);
380
+ if (usage.totalTokens > 0) {
381
+ log(`Tokens: +${usage.promptTokens}/${usage.completionTokens} (total: ${totalUsage.totalTokens.toLocaleString()})`);
382
+ }
383
+ // Context compaction: when prompt tokens exceed threshold, trim messages
384
+ if (usage.promptTokens > compactThreshold && messages.length > DEFAULT_KEEP_LAST_N + 2) {
385
+ log(`Context compaction triggered (${usage.promptTokens.toLocaleString()} > ${compactThreshold.toLocaleString()} threshold)`);
386
+ // Keep system prompt (index 0) + last N messages
387
+ const kept = messages.slice(-DEFAULT_KEEP_LAST_N);
388
+ messages.length = 1; // keep system prompt
389
+ messages.push(...kept);
390
+ // Reload state if runtime available
391
+ if (runtime) {
392
+ const freshProtocol = await runtime.fetchProtocol();
393
+ if (freshProtocol) {
394
+ messages[0] = { role: 'system', content: generateSystemPrompt(agentId, role, toolDefs, freshProtocol) };
395
+ }
396
+ const stateStr = await runtime.getState();
397
+ messages.push({ role: 'user', content: `[SYSTEM] Context was compacted. Current state: ${stateStr}` });
398
+ }
399
+ else {
400
+ messages.push({ role: 'user', content: '[SYSTEM] Context was compacted. Previous messages were trimmed.' });
401
+ }
402
+ telemetry?.record('context_compaction', {
403
+ agentId, role, iteration: iterations,
404
+ promptTokensBefore: usage.promptTokens, messagesAfter: messages.length,
405
+ });
406
+ log(`Compacted to ${messages.length} messages`);
407
+ }
408
+ // Log assistant response (full text so we can see LLM reasoning)
409
+ if (response.content) {
410
+ log(`${DIM}${response.content}${RESET}${color}`);
411
+ }
412
+ // Check for "DONE" in content — always exits, even in reactive mode
413
+ if (response.content && /\bDONE\b/.test(response.content)) {
414
+ log('Agent said DONE — finishing');
415
+ exitReason = 'done';
416
+ break;
417
+ }
418
+ // No tool calls → sleep/wake or exit
419
+ if (!response.tool_calls || response.tool_calls.length === 0) {
420
+ if (effectiveWakeOn && runtime) {
421
+ const maxWakes = effectiveWakeOn.maxWakes ?? 0;
422
+ if (maxWakes > 0 && ++wakeCount >= maxWakes) {
423
+ log(`Max wakes reached (${maxWakes}) — exiting`);
424
+ exitReason = 'max_wakes';
425
+ break;
426
+ }
427
+ if (maxWakes === 0)
428
+ wakeCount++;
429
+ log(`Sleeping — waiting for wake events (cycle ${wakeCount})`);
430
+ const wakeEvents = await runtime.waitForWake(effectiveWakeOn);
431
+ if (wakeEvents.length === 0) {
432
+ log('Wake timeout — no events, exiting');
433
+ exitReason = 'wake_timeout';
434
+ break;
435
+ }
436
+ log(`Woke up with ${wakeEvents.length} event(s)`);
437
+ // Inject dance state context if available
438
+ const inject = runtime.getLastInject();
439
+ if (inject) {
440
+ messages.push({ role: 'user', content: `[SYSTEM] ${inject}` });
441
+ log('Injected dance state context');
442
+ }
443
+ for (const evt of wakeEvents) {
444
+ messages.push({ role: 'user', content: `[SYSTEM] ${evt}` });
445
+ }
446
+ continue;
447
+ }
448
+ log('No tool calls — agent finished');
449
+ exitReason = 'no_tool_calls';
450
+ break;
451
+ }
452
+ // Execute each tool call
453
+ let halted = false;
454
+ for (const toolCall of response.tool_calls) {
455
+ const name = toolCall.function.name;
456
+ const args = getToolCallArgs(toolCall);
457
+ const argsStr = Object.keys(args).length > 0
458
+ ? `(${Object.entries(args).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(', ')})`
459
+ : '()';
460
+ log(`Tool: ${name}${argsStr}`);
461
+ // Intercept file writes for auto-claims
462
+ if (runtime && FILE_WRITE_TOOLS.has(name) && args.path) {
463
+ const claimResult = await runtime.onFileWrite(args.path);
464
+ if (claimResult) {
465
+ // Claim conflict — return error to LLM
466
+ messages.push({
467
+ role: 'tool',
468
+ content: claimResult,
469
+ tool_call_id: toolCall.id ?? `call_${iterations}`,
470
+ });
471
+ log(`${DIM}→ claim conflict: ${claimResult}${RESET}${color}`);
472
+ continue;
473
+ }
474
+ }
475
+ // Route tool call to the right handler
476
+ let result;
477
+ if (danceToolNames && danceToolNames.has(name) && runtime) {
478
+ // Dance tool — route via WS to server
479
+ try {
480
+ const danceResult = await runtime.callDanceTool(name, args);
481
+ result = JSON.stringify(danceResult);
482
+ }
483
+ catch (err) {
484
+ result = JSON.stringify({ error: `Dance tool failed: ${err.message}` });
485
+ }
486
+ }
487
+ else if (runtime && SYNTHETIC_TOOL_NAMES.has(name)) {
488
+ // Synthetic ACP tools — route through runtime
489
+ if (name === 'publish_event') {
490
+ let eventData = {};
491
+ if (args.data) {
492
+ try {
493
+ eventData = typeof args.data === 'string' ? JSON.parse(args.data) : args.data;
494
+ }
495
+ catch { /* keep empty */ }
496
+ }
497
+ result = await runtime.publishEvent(args.type, eventData);
498
+ }
499
+ else if (name === 'set_state') {
500
+ result = await runtime.setState(args.key, args.value);
501
+ }
502
+ else if (name === 'get_state') {
503
+ result = await runtime.getState();
504
+ }
505
+ else if (name === 'claim_resource') {
506
+ result = await runtime.claimResource(args.resource, args.reason);
507
+ }
508
+ else if (name === 'release_resource') {
509
+ result = await runtime.releaseResource(args.resource);
510
+ }
511
+ else {
512
+ result = JSON.stringify({ error: `Unknown synthetic tool: ${name}` });
513
+ }
514
+ }
515
+ else if (incubatorClient && incubatorClient.hasToolName(name)) {
516
+ result = await incubatorClient.callTool(name, args);
517
+ }
518
+ else {
519
+ try {
520
+ result = await toolClient.callTool(name, args);
521
+ }
522
+ catch (err) {
523
+ result = JSON.stringify({ error: `Tool call failed: ${err.message}` });
524
+ }
525
+ }
526
+ telemetry?.record('tool_call', { agentId, role, tool: name });
527
+ // Log result
528
+ log(`${DIM}→ ${result}${RESET}${color}`);
529
+ messages.push({
530
+ role: 'tool',
531
+ content: result,
532
+ tool_call_id: toolCall.id ?? `call_${iterations}`,
533
+ });
534
+ // Check halt/pause status (via ACP runtime)
535
+ if (runtime) {
536
+ const controlStatus = await runtime.checkControl();
537
+ if (controlStatus.halted) {
538
+ log(`HALTED: ${controlStatus.reason ?? 'no reason'}`);
539
+ halted = true;
540
+ break;
541
+ }
542
+ if (controlStatus.paused) {
543
+ log(`PAUSED: ${controlStatus.reason ?? 'no reason'} — waiting...`);
544
+ const resumeReason = await runtime.waitForResume();
545
+ log(`RESUMED: ${resumeReason}`);
546
+ messages.push({
547
+ role: 'user',
548
+ content: `[SYSTEM] You were paused (reason: ${controlStatus.reason ?? 'unknown'}). You have been resumed (reason: ${resumeReason}). Continue your work.`,
549
+ });
550
+ }
551
+ }
552
+ }
553
+ if (halted) {
554
+ log('Agent halted — exiting loop');
555
+ exitReason = 'halted';
556
+ if (runtime)
557
+ await runtime.onComplete('Agent halted', totalUsage);
558
+ telemetry?.record('agent_complete', {
559
+ agentId, role, status: 'completed', iterations, exitReason,
560
+ provider: provider.type, model: provider.model,
561
+ totalTokens: totalUsage.totalTokens, duration_ms: Date.now() - startTime, wakeCount,
562
+ });
563
+ return { agentId, role, status: 'completed', iterations, usage: totalUsage, iterationUsage };
564
+ }
565
+ // Periodic prompt refresh (every 10 iterations)
566
+ if (iterations % 10 === 0 && runtime) {
567
+ const refreshed = await runtime.fetchProtocol();
568
+ if (refreshed) {
569
+ const newPrompt = generateSystemPrompt(agentId, role, toolDefs, refreshed);
570
+ messages[0] = { role: 'system', content: newPrompt };
571
+ log('Refreshed system prompt');
572
+ }
573
+ }
574
+ }
575
+ if (this.stopFlag) {
576
+ exitReason = 'stopped';
577
+ log('Stopped by runner');
578
+ }
579
+ else if (iterations >= maxIterations) {
580
+ exitReason = 'max_iterations';
581
+ log(`Hit max iterations (${maxIterations})`);
582
+ }
583
+ // Cleanup SIGTERM handler
584
+ process.off('SIGTERM', onKill);
585
+ // Complete
586
+ const summary = `Agent ${agentId} completed after ${iterations} iterations (${totalUsage.totalTokens.toLocaleString()} tokens)`;
587
+ if (runtime)
588
+ await runtime.onComplete(summary, totalUsage);
589
+ telemetry?.record('agent_complete', {
590
+ agentId, role, status: 'completed', iterations, exitReason,
591
+ provider: provider.type, model: provider.model,
592
+ promptTokens: totalUsage.promptTokens, completionTokens: totalUsage.completionTokens,
593
+ totalTokens: totalUsage.totalTokens, duration_ms: Date.now() - startTime, wakeCount,
594
+ });
595
+ log(`Done (${iterations} iterations, ${totalUsage.totalTokens.toLocaleString()} tokens)`);
596
+ return { agentId, role, status: 'completed', iterations, usage: totalUsage, iterationUsage };
597
+ }
598
+ catch (err) {
599
+ // Cleanup SIGTERM handler on error path too
600
+ try {
601
+ process.off('SIGTERM', () => { });
602
+ }
603
+ catch { /* ignore */ }
604
+ const errMsg = err.message;
605
+ if (config.verbose) {
606
+ console.error(`${color} [${agentId}] ERROR: ${errMsg}${RESET}`);
607
+ }
608
+ if (runtime) {
609
+ try {
610
+ await runtime.onComplete(`Error: ${errMsg}`, totalUsage);
611
+ }
612
+ catch { /* ignore */ }
613
+ }
614
+ telemetry?.record('agent_complete', {
615
+ agentId, role, status: 'error', iterations, exitReason: 'error', error: errMsg,
616
+ provider: provider.type, model: provider.model,
617
+ totalTokens: totalUsage.totalTokens, duration_ms: Date.now() - startTime,
618
+ });
619
+ return { agentId, role, status: 'error', iterations, error: errMsg, usage: totalUsage, iterationUsage };
620
+ }
621
+ }
622
+ }
623
+ /** Backwards-compatible alias */
624
+ export { AgentRunner as DroneRunner };
625
+ //# sourceMappingURL=runner.js.map