@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
package/dist/rest.js ADDED
@@ -0,0 +1,962 @@
1
+ import { CarapaceBlockedError } from './guard.js';
2
+ import { parseSpec, SpecValidationError } from '@agentcoordinationprotocol/spec';
3
+ function getAllowedOrigin(req) {
4
+ const origin = req.headers.origin || '';
5
+ // Allow localhost (any port) for local development
6
+ if (/^https?:\/\/localhost(:\d+)?$/.test(origin))
7
+ return origin;
8
+ // Allow honeyb.dev subdomains
9
+ if (/^https:\/\/[a-z0-9-]+\.honeyb\.dev$/.test(origin))
10
+ return origin;
11
+ // Allow extra origins via INCUBATOR_CORS_ORIGINS (comma-separated)
12
+ const extra = process.env.INCUBATOR_CORS_ORIGINS;
13
+ if (extra) {
14
+ for (const allowed of extra.split(',')) {
15
+ const pattern = allowed.trim();
16
+ if (pattern && origin === pattern)
17
+ return origin;
18
+ // Support wildcard subdomains: *.example.dev
19
+ if (pattern.startsWith('*.')) {
20
+ const suffix = pattern.slice(1); // .example.dev
21
+ const re = new RegExp(`^https://[a-z0-9-]+${suffix.replace(/\./g, '\\.')}(:\\d+)?$`);
22
+ if (re.test(origin))
23
+ return origin;
24
+ }
25
+ }
26
+ }
27
+ return '';
28
+ }
29
+ function json(res, status, data, req) {
30
+ const origin = req ? getAllowedOrigin(req) : 'http://localhost:5173';
31
+ const headers = {
32
+ 'Content-Type': 'application/json',
33
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
34
+ 'Access-Control-Allow-Headers': 'Content-Type, X-Agent-Id, X-Namespace',
35
+ };
36
+ if (origin)
37
+ headers['Access-Control-Allow-Origin'] = origin;
38
+ res.writeHead(status, headers);
39
+ res.end(JSON.stringify(data));
40
+ }
41
+ function getAgentId(req, body) {
42
+ const id = body.agentId ?? req.headers['x-agent-id'] ?? 'rest_anonymous';
43
+ // Reject reserved honeycomb: prefix to prevent loop prevention bypass
44
+ if (id.startsWith('honeycomb:'))
45
+ return 'rest_anonymous';
46
+ return id;
47
+ }
48
+ const MAX_BODY_SIZE = 1024 * 1024; // 1MB
49
+ async function parseBody(req) {
50
+ if (req.method === 'GET')
51
+ return {};
52
+ return new Promise((resolve, reject) => {
53
+ let data = '';
54
+ let size = 0;
55
+ req.on('data', (chunk) => {
56
+ size += chunk.length;
57
+ if (size > MAX_BODY_SIZE) {
58
+ req.destroy();
59
+ reject(new Error('Payload too large'));
60
+ return;
61
+ }
62
+ data += chunk.toString();
63
+ });
64
+ req.on('end', () => {
65
+ if (!data) {
66
+ resolve({});
67
+ return;
68
+ }
69
+ try {
70
+ resolve(JSON.parse(data));
71
+ }
72
+ catch {
73
+ resolve({});
74
+ }
75
+ });
76
+ });
77
+ }
78
+ // Resource keywords that indicate the default namespace (no namespace prefix in URL)
79
+ const RESOURCE_KEYWORDS = new Set([
80
+ 'state', 'claims', 'events', 'discoveries', 'health', 'protocol',
81
+ 'messages', 'help', 'progress', 'conflicts', 'roles', 'reinforcements', 'governance',
82
+ 'control', 'topics', 'runs', 'dance',
83
+ ]);
84
+ const routes = [
85
+ // ─── Health ──────────────────────────────────────────────
86
+ {
87
+ method: 'GET',
88
+ pattern: /^\/api\/health$/,
89
+ handler: async (_req, res, stores) => {
90
+ const claims = await stores.claims.list();
91
+ const events = await stores.events.getEvents();
92
+ const state = await stores.state.query();
93
+ json(res, 200, {
94
+ status: 'ok',
95
+ uptime: process.uptime(),
96
+ agents: new Set([
97
+ ...state.map(e => e.setBy),
98
+ ...claims.map(c => c.owner),
99
+ ...events.events.map(e => e.publishedBy),
100
+ ]).size,
101
+ claims: claims.length,
102
+ events: events.cursor,
103
+ });
104
+ },
105
+ },
106
+ // ─── State ───────────────────────────────────────────────
107
+ {
108
+ method: 'GET',
109
+ pattern: /^\/api\/state$/,
110
+ handler: async (req, res, stores) => {
111
+ const url = new URL(req.url, `http://localhost`);
112
+ const pattern = url.searchParams.get('pattern') ?? undefined;
113
+ const category = url.searchParams.get('category') ?? undefined;
114
+ const entries = await stores.state.query(pattern, category);
115
+ json(res, 200, { count: entries.length, entries });
116
+ },
117
+ },
118
+ {
119
+ method: 'GET',
120
+ pattern: /^\/api\/state\/(.+)$/,
121
+ handler: async (_req, res, stores, match) => {
122
+ const key = decodeURIComponent(match[1]);
123
+ const entry = await stores.state.get(key);
124
+ if (!entry) {
125
+ json(res, 200, { found: false, key });
126
+ return;
127
+ }
128
+ json(res, 200, { found: true, entry });
129
+ },
130
+ },
131
+ {
132
+ method: 'PUT',
133
+ pattern: /^\/api\/state\/(.+)$/,
134
+ handler: async (req, res, stores, match, body) => {
135
+ const key = decodeURIComponent(match[1]);
136
+ if (!('value' in body)) {
137
+ json(res, 400, { error: 'Missing required field: value' });
138
+ return;
139
+ }
140
+ const agentId = getAgentId(req, body);
141
+ const entry = await stores.state.set(key, body.value, agentId, body.category, body.ttlMs);
142
+ json(res, 200, { success: true, entry });
143
+ },
144
+ },
145
+ {
146
+ method: 'DELETE',
147
+ pattern: /^\/api\/state\/(.+)$/,
148
+ handler: async (_req, res, stores, match) => {
149
+ const key = decodeURIComponent(match[1]);
150
+ const deleted = await stores.state.delete(key);
151
+ json(res, 200, { deleted });
152
+ },
153
+ },
154
+ // ─── Claims ──────────────────────────────────────────────
155
+ {
156
+ method: 'POST',
157
+ pattern: /^\/api\/claims$/,
158
+ handler: async (req, res, stores, _match, body) => {
159
+ const resource = body.resource;
160
+ const value = body.value;
161
+ if (!resource || !value) {
162
+ json(res, 400, { error: 'Missing required fields: resource, value' });
163
+ return;
164
+ }
165
+ const agentId = getAgentId(req, body);
166
+ const result = await stores.claims.claim(resource, value, agentId, body.ttlMs);
167
+ json(res, 200, result);
168
+ },
169
+ },
170
+ {
171
+ method: 'DELETE',
172
+ pattern: /^\/api\/claims\/(.+)$/,
173
+ handler: async (req, res, stores, match, body) => {
174
+ const resource = decodeURIComponent(match[1]);
175
+ const agentId = getAgentId(req, body);
176
+ const claim = await stores.claims.release(resource, agentId);
177
+ if (!claim) {
178
+ json(res, 200, { released: false, reason: 'No active claim found or not owner' });
179
+ return;
180
+ }
181
+ json(res, 200, { released: true, claim });
182
+ },
183
+ },
184
+ {
185
+ method: 'GET',
186
+ pattern: /^\/api\/claims\/(.+)$/,
187
+ handler: async (_req, res, stores, match) => {
188
+ const resource = decodeURIComponent(match[1]);
189
+ const claim = await stores.claims.check(resource);
190
+ if (!claim) {
191
+ json(res, 200, { claimed: false, resource });
192
+ return;
193
+ }
194
+ json(res, 200, { claimed: claim.status === 'active', claim });
195
+ },
196
+ },
197
+ {
198
+ method: 'GET',
199
+ pattern: /^\/api\/claims$/,
200
+ handler: async (req, res, stores) => {
201
+ const url = new URL(req.url, `http://localhost`);
202
+ const pattern = url.searchParams.get('pattern') ?? undefined;
203
+ const claims = await stores.claims.list(pattern);
204
+ json(res, 200, { count: claims.length, claims });
205
+ },
206
+ },
207
+ // ─── Events ──────────────────────────────────────────────
208
+ {
209
+ method: 'POST',
210
+ pattern: /^\/api\/events$/,
211
+ handler: async (req, res, stores, _match, body) => {
212
+ const type = body.type;
213
+ if (!type) {
214
+ json(res, 400, { error: 'Missing required field: type' });
215
+ return;
216
+ }
217
+ const agentId = getAgentId(req, body);
218
+ const event = await stores.events.publish(type, body.data ?? null, agentId);
219
+ json(res, 200, { published: true, event });
220
+ },
221
+ },
222
+ {
223
+ method: 'GET',
224
+ pattern: /^\/api\/events$/,
225
+ handler: async (req, res, stores) => {
226
+ const url = new URL(req.url, `http://localhost`);
227
+ const since = url.searchParams.get('since');
228
+ const type = url.searchParams.get('type') ?? undefined;
229
+ const result = await stores.events.getEvents(since ? parseInt(since, 10) : undefined, type);
230
+ json(res, 200, result);
231
+ },
232
+ },
233
+ // ─── Discoveries ─────────────────────────────────────────
234
+ {
235
+ method: 'POST',
236
+ pattern: /^\/api\/discoveries$/,
237
+ handler: async (req, res, stores, _match, body) => {
238
+ const topic = body.topic;
239
+ const content = body.content;
240
+ if (!topic || !content) {
241
+ json(res, 400, { error: 'Missing required fields: topic, content' });
242
+ return;
243
+ }
244
+ const agentId = getAgentId(req, body);
245
+ const discovery = await stores.discoveries.publish(topic, content, agentId, body.category);
246
+ json(res, 200, { published: true, discovery });
247
+ },
248
+ },
249
+ {
250
+ method: 'GET',
251
+ pattern: /^\/api\/discoveries$/,
252
+ handler: async (req, res, stores) => {
253
+ const url = new URL(req.url, `http://localhost`);
254
+ const query = url.searchParams.get('query') ?? undefined;
255
+ const category = url.searchParams.get('category') ?? undefined;
256
+ const results = await stores.discoveries.search(query, category);
257
+ json(res, 200, { count: results.length, discoveries: results });
258
+ },
259
+ },
260
+ // ─── Messages ─────────────────────────────────────────────
261
+ {
262
+ method: 'POST',
263
+ pattern: /^\/api\/messages$/,
264
+ handler: async (req, res, stores, _match, body) => {
265
+ const to = body.to;
266
+ const content = body.content;
267
+ if (!to || !content) {
268
+ json(res, 400, { error: 'Missing required fields: to, content' });
269
+ return;
270
+ }
271
+ const agentId = getAgentId(req, body);
272
+ const msg = await stores.messages.send(agentId, to, content, body.replyTo);
273
+ json(res, 200, { sent: true, message: msg });
274
+ },
275
+ },
276
+ {
277
+ method: 'GET',
278
+ pattern: /^\/api\/messages$/,
279
+ handler: async (req, res, stores) => {
280
+ const url = new URL(req.url, `http://localhost`);
281
+ const agentId = req.headers['x-agent-id'] ?? 'rest_anonymous';
282
+ const since = url.searchParams.get('since') ?? undefined;
283
+ const messages = await stores.messages.getFor(agentId, since);
284
+ json(res, 200, { count: messages.length, messages });
285
+ },
286
+ },
287
+ // ─── Roles ────────────────────────────────────────────────
288
+ {
289
+ method: 'POST',
290
+ pattern: /^\/api\/roles\/request$/,
291
+ handler: async (req, res, stores, _match, body) => {
292
+ const role = body.role;
293
+ if (!role) {
294
+ json(res, 400, { error: 'Missing required field: role' });
295
+ return;
296
+ }
297
+ const agentId = getAgentId(req, body);
298
+ const current = await stores.roles.getByAgent(agentId);
299
+ const fromRole = current?.role;
300
+ // Server-side: assign the new role (protocol validation happens at a higher layer)
301
+ await stores.roles.assign(agentId, role);
302
+ await stores.events.publish('role.transition', { agent: agentId, from: fromRole ?? null, to: role, reason: body.reason ?? null }, agentId);
303
+ json(res, 200, { approved: true, from: fromRole ?? null, to: role });
304
+ },
305
+ },
306
+ {
307
+ method: 'GET',
308
+ pattern: /^\/api\/roles$/,
309
+ handler: async (_req, res, stores) => {
310
+ const assignments = await stores.roles.getAssignments();
311
+ json(res, 200, { count: assignments.length, assignments });
312
+ },
313
+ },
314
+ // ─── Help ─────────────────────────────────────────────────
315
+ {
316
+ method: 'POST',
317
+ pattern: /^\/api\/help$/,
318
+ handler: async (req, res, stores, _match, body) => {
319
+ const problem = body.problem;
320
+ if (!problem) {
321
+ json(res, 400, { error: 'Missing required field: problem' });
322
+ return;
323
+ }
324
+ const agentId = getAgentId(req, body);
325
+ const request = await stores.help.request(agentId, problem, body.needs_capability, body.urgency);
326
+ json(res, 200, { created: true, request });
327
+ },
328
+ },
329
+ {
330
+ method: 'POST',
331
+ pattern: /^\/api\/help\/([^/]+)\/claim$/,
332
+ handler: async (req, res, stores, match, body) => {
333
+ const requestId = decodeURIComponent(match[1]);
334
+ const agentId = getAgentId(req, body);
335
+ const request = await stores.help.claim(requestId, agentId);
336
+ if (!request) {
337
+ json(res, 200, { claimed: false, reason: 'Request not found or not open' });
338
+ return;
339
+ }
340
+ json(res, 200, { claimed: true, request });
341
+ },
342
+ },
343
+ {
344
+ method: 'POST',
345
+ pattern: /^\/api\/help\/([^/]+)\/resolve$/,
346
+ handler: async (req, res, stores, match, body) => {
347
+ const requestId = decodeURIComponent(match[1]);
348
+ const agentId = getAgentId(req, body);
349
+ const request = await stores.help.resolve(requestId, agentId);
350
+ if (!request) {
351
+ json(res, 200, { resolved: false, reason: 'Request not found or not claimed' });
352
+ return;
353
+ }
354
+ json(res, 200, { resolved: true, request });
355
+ },
356
+ },
357
+ {
358
+ method: 'GET',
359
+ pattern: /^\/api\/help$/,
360
+ handler: async (req, res, stores) => {
361
+ const url = new URL(req.url, `http://localhost`);
362
+ const status = url.searchParams.get('status') ?? undefined;
363
+ const requests = await stores.help.list(status);
364
+ json(res, 200, { count: requests.length, requests });
365
+ },
366
+ },
367
+ // ─── Progress ─────────────────────────────────────────────
368
+ {
369
+ method: 'POST',
370
+ pattern: /^\/api\/progress$/,
371
+ handler: async (req, res, stores, _match, body) => {
372
+ const claim = body.claim;
373
+ const progress = body.progress;
374
+ if (!claim || progress === undefined) {
375
+ json(res, 400, { error: 'Missing required fields: claim, progress' });
376
+ return;
377
+ }
378
+ const agentId = getAgentId(req, body);
379
+ const report = await stores.progress.report(claim, agentId, progress, body.note);
380
+ json(res, 200, { reported: true, report });
381
+ },
382
+ },
383
+ {
384
+ method: 'GET',
385
+ pattern: /^\/api\/progress\/(.+)$/,
386
+ handler: async (_req, res, stores, match) => {
387
+ const claim = decodeURIComponent(match[1]);
388
+ const report = await stores.progress.get(claim);
389
+ if (!report) {
390
+ json(res, 200, { found: false, claim });
391
+ return;
392
+ }
393
+ json(res, 200, { found: true, report });
394
+ },
395
+ },
396
+ {
397
+ method: 'GET',
398
+ pattern: /^\/api\/progress$/,
399
+ handler: async (_req, res, stores) => {
400
+ const reports = await stores.progress.list();
401
+ json(res, 200, { count: reports.length, reports });
402
+ },
403
+ },
404
+ // ─── Conflicts ────────────────────────────────────────────
405
+ {
406
+ method: 'POST',
407
+ pattern: /^\/api\/conflicts$/,
408
+ handler: async (req, res, stores, _match, body) => {
409
+ const discovery_a = body.discovery_a;
410
+ const discovery_b = body.discovery_b;
411
+ const reason = body.reason;
412
+ if (!discovery_a || !discovery_b || !reason) {
413
+ json(res, 400, { error: 'Missing required fields: discovery_a, discovery_b, reason' });
414
+ return;
415
+ }
416
+ const agentId = getAgentId(req, body);
417
+ const conflict = await stores.conflicts.flag(agentId, discovery_a, discovery_b, reason);
418
+ json(res, 200, { flagged: true, conflict });
419
+ },
420
+ },
421
+ {
422
+ method: 'POST',
423
+ pattern: /^\/api\/conflicts\/([^/]+)\/resolve$/,
424
+ handler: async (req, res, stores, match, body) => {
425
+ const conflictId = decodeURIComponent(match[1]);
426
+ const resolution = body.resolution;
427
+ if (!resolution) {
428
+ json(res, 400, { error: 'Missing required field: resolution' });
429
+ return;
430
+ }
431
+ const agentId = getAgentId(req, body);
432
+ const conflict = await stores.conflicts.resolve(conflictId, agentId, resolution);
433
+ if (!conflict) {
434
+ json(res, 200, { resolved: false, reason: 'Conflict not found or not open' });
435
+ return;
436
+ }
437
+ json(res, 200, { resolved: true, conflict });
438
+ },
439
+ },
440
+ {
441
+ method: 'GET',
442
+ pattern: /^\/api\/conflicts$/,
443
+ handler: async (req, res, stores) => {
444
+ const url = new URL(req.url, `http://localhost`);
445
+ const status = url.searchParams.get('status') ?? undefined;
446
+ const conflicts = await stores.conflicts.list(status);
447
+ json(res, 200, { count: conflicts.length, conflicts });
448
+ },
449
+ },
450
+ // ─── Reinforcements ──────────────────────────────────────
451
+ {
452
+ method: 'POST',
453
+ pattern: /^\/api\/reinforcements$/,
454
+ handler: async (req, res, stores, _match, body) => {
455
+ const role = body.role;
456
+ if (!role) {
457
+ json(res, 400, { error: 'Missing required field: role' });
458
+ return;
459
+ }
460
+ const agentId = getAgentId(req, body);
461
+ const count = body.count ?? 1;
462
+ const request = await stores.reinforcements.request(agentId, role, count, body.reason);
463
+ json(res, 200, { requested: true, request });
464
+ },
465
+ },
466
+ {
467
+ method: 'GET',
468
+ pattern: /^\/api\/reinforcements$/,
469
+ handler: async (_req, res, stores) => {
470
+ const requests = await stores.reinforcements.list();
471
+ json(res, 200, { count: requests.length, requests });
472
+ },
473
+ },
474
+ // ─── Governance ───────────────────────────────────────────
475
+ {
476
+ method: 'POST',
477
+ pattern: /^\/api\/governance\/approve$/,
478
+ handler: async (req, res, stores, _match, body) => {
479
+ const action = body.action;
480
+ if (!action) {
481
+ json(res, 400, { error: 'Missing required field: action' });
482
+ return;
483
+ }
484
+ const agentId = getAgentId(req, body);
485
+ await stores.events.publish('governance.approval_requested', { action, detail: body.detail, files: body.files, requested_by: agentId }, agentId);
486
+ json(res, 200, { requested: true, action });
487
+ },
488
+ },
489
+ {
490
+ method: 'POST',
491
+ pattern: /^\/api\/governance\/escalate$/,
492
+ handler: async (req, res, stores, _match, body) => {
493
+ const reason = body.reason;
494
+ if (!reason) {
495
+ json(res, 400, { error: 'Missing required field: reason' });
496
+ return;
497
+ }
498
+ const agentId = getAgentId(req, body);
499
+ await stores.events.publish('governance.escalated', { reason, context: body.context, escalated_by: agentId }, agentId);
500
+ json(res, 200, { escalated: true, reason });
501
+ },
502
+ },
503
+ {
504
+ method: 'POST',
505
+ pattern: /^\/api\/governance\/propose$/,
506
+ handler: async (req, res, stores, _match, body) => {
507
+ const action = body.action;
508
+ if (!action) {
509
+ json(res, 400, { error: 'Missing required field: action' });
510
+ return;
511
+ }
512
+ const agentId = getAgentId(req, body);
513
+ const proposal = await stores.proposals.propose(agentId, action, body.detail, body.requires_quorum);
514
+ json(res, 200, { proposed: true, proposal });
515
+ },
516
+ },
517
+ {
518
+ method: 'POST',
519
+ pattern: /^\/api\/governance\/endorse\/([^/]+)$/,
520
+ handler: async (req, res, stores, match, body) => {
521
+ const proposalId = decodeURIComponent(match[1]);
522
+ const agentId = getAgentId(req, body);
523
+ const proposal = await stores.proposals.endorse(proposalId, agentId);
524
+ if (!proposal) {
525
+ json(res, 200, { endorsed: false, reason: 'Proposal not found or not open' });
526
+ return;
527
+ }
528
+ json(res, 200, { endorsed: true, proposal });
529
+ },
530
+ },
531
+ {
532
+ method: 'GET',
533
+ pattern: /^\/api\/governance\/proposals$/,
534
+ handler: async (req, res, stores) => {
535
+ const url = new URL(req.url, `http://localhost`);
536
+ const status = url.searchParams.get('status') ?? undefined;
537
+ const proposals = await stores.proposals.list(status);
538
+ json(res, 200, { count: proposals.length, proposals });
539
+ },
540
+ },
541
+ {
542
+ method: 'POST',
543
+ pattern: /^\/api\/governance\/rollback$/,
544
+ handler: async (req, res, stores, _match, body) => {
545
+ const reason = body.reason;
546
+ if (!reason) {
547
+ json(res, 400, { error: 'Missing required field: reason' });
548
+ return;
549
+ }
550
+ const agentId = getAgentId(req, body);
551
+ await stores.events.publish('governance.rollback.requested', { reason, scope: body.scope, requested_by: agentId }, agentId);
552
+ json(res, 200, { requested: true, reason });
553
+ },
554
+ },
555
+ // ─── Control (halt/pause/resume) ──────────────────────────
556
+ {
557
+ method: 'POST',
558
+ pattern: /^\/api\/control\/halt$/,
559
+ handler: async (req, res, stores, _match, body) => {
560
+ const reason = body.reason;
561
+ if (!reason) {
562
+ json(res, 400, { error: 'Missing required field: reason' });
563
+ return;
564
+ }
565
+ const agentId = getAgentId(req, body);
566
+ const status = body.status ?? 'completed';
567
+ const target = body.target;
568
+ // Per-agent halt: auto-release claims and remove role
569
+ if (target) {
570
+ const claims = await stores.claims.list();
571
+ for (const claim of claims) {
572
+ if (claim.owner === target && claim.status === 'active') {
573
+ await stores.claims.release(claim.resource, target);
574
+ }
575
+ }
576
+ await stores.roles.remove(target);
577
+ }
578
+ const info = await stores.control.halt(reason, agentId, status, target);
579
+ json(res, 200, { halted: true, info });
580
+ },
581
+ },
582
+ {
583
+ method: 'POST',
584
+ pattern: /^\/api\/control\/pause$/,
585
+ handler: async (req, res, stores, _match, body) => {
586
+ const reason = body.reason;
587
+ if (!reason) {
588
+ json(res, 400, { error: 'Missing required field: reason' });
589
+ return;
590
+ }
591
+ const agentId = getAgentId(req, body);
592
+ const target = body.target;
593
+ const info = await stores.control.pause(reason, agentId, target);
594
+ json(res, 200, { paused: true, info });
595
+ },
596
+ },
597
+ {
598
+ method: 'POST',
599
+ pattern: /^\/api\/control\/resume$/,
600
+ handler: async (req, res, stores, _match, body) => {
601
+ const reason = body.reason;
602
+ if (!reason) {
603
+ json(res, 400, { error: 'Missing required field: reason' });
604
+ return;
605
+ }
606
+ const agentId = getAgentId(req, body);
607
+ const target = body.target;
608
+ const resumed = await stores.control.resume(agentId, reason, target);
609
+ json(res, 200, { resumed });
610
+ },
611
+ },
612
+ {
613
+ method: 'GET',
614
+ pattern: /^\/api\/control\/status$/,
615
+ handler: async (req, res, stores) => {
616
+ const url = new URL(req.url, 'http://localhost');
617
+ const agentId = url.searchParams.get('agentId') ?? req.headers['x-agent-id'] ?? undefined;
618
+ const status = stores.control.getStatus(agentId);
619
+ json(res, 200, status);
620
+ },
621
+ },
622
+ // ─── Runs ──────────────────────────────────────────────────
623
+ {
624
+ method: 'GET',
625
+ pattern: /^\/api\/runs\/summary$/,
626
+ handler: async (_req, res, stores) => {
627
+ const summary = await stores.runs.summary();
628
+ json(res, 200, summary);
629
+ },
630
+ },
631
+ {
632
+ method: 'GET',
633
+ pattern: /^\/api\/runs\/([^/]+)$/,
634
+ handler: async (_req, res, stores, match) => {
635
+ const agentId = decodeURIComponent(match[1]);
636
+ const run = await stores.runs.get(agentId);
637
+ if (!run) {
638
+ json(res, 200, { found: false, agentId });
639
+ return;
640
+ }
641
+ json(res, 200, { found: true, run });
642
+ },
643
+ },
644
+ {
645
+ method: 'GET',
646
+ pattern: /^\/api\/runs$/,
647
+ handler: async (req, res, stores) => {
648
+ const url = new URL(req.url, 'http://localhost');
649
+ const status = url.searchParams.get('status') ?? undefined;
650
+ const runs = await stores.runs.list(status);
651
+ json(res, 200, { count: runs.length, runs });
652
+ },
653
+ },
654
+ // ─── Iteration detail per agent ────────────────────────────
655
+ {
656
+ method: 'GET',
657
+ pattern: /^\/api\/runs\/([^/]+)\/iterations$/,
658
+ handler: async (_req, res, stores, match) => {
659
+ const agentId = decodeURIComponent(match[1]);
660
+ const run = await stores.runs.get(agentId);
661
+ if (!run) {
662
+ json(res, 200, { found: false, agentId, iterations: [] });
663
+ return;
664
+ }
665
+ json(res, 200, {
666
+ found: true,
667
+ agentId,
668
+ iterations: run.iterationDetails ?? [],
669
+ });
670
+ },
671
+ },
672
+ ];
673
+ /**
674
+ * Extract namespace and rewritten path from a URL pathname.
675
+ *
676
+ * /api/_ns → admin route (returns null namespace)
677
+ * /api/_ns/:name → admin route (returns null namespace)
678
+ * /api/state/... → default namespace, path unchanged
679
+ * /api/team-a/state/... → namespace "team-a", path rewritten to /api/state/...
680
+ */
681
+ function extractNamespace(pathname) {
682
+ // Strip /api/ prefix to inspect segments
683
+ const afterApi = pathname.slice(5); // after "/api/"
684
+ const slashIdx = afterApi.indexOf('/');
685
+ const firstSegment = slashIdx === -1 ? afterApi : afterApi.slice(0, slashIdx);
686
+ // Admin routes — no namespace
687
+ if (firstSegment === '_ns') {
688
+ return { namespace: null, rewrittenPath: pathname };
689
+ }
690
+ // Known resource keyword → default namespace, path unchanged
691
+ if (RESOURCE_KEYWORDS.has(firstSegment)) {
692
+ return { namespace: 'default', rewrittenPath: pathname };
693
+ }
694
+ // Otherwise, first segment is the namespace — strip it and rewrite
695
+ const rest = slashIdx === -1 ? '' : afterApi.slice(slashIdx);
696
+ return { namespace: firstSegment, rewrittenPath: `/api${rest}` };
697
+ }
698
+ export async function handleRestRequest(req, res, registry, verbose, danceSupport) {
699
+ const url = req.url ?? '/';
700
+ if (!url.startsWith('/api/'))
701
+ return false;
702
+ // CORS preflight
703
+ if (req.method === 'OPTIONS') {
704
+ json(res, 204, '');
705
+ return true;
706
+ }
707
+ const body = await parseBody(req);
708
+ const pathname = new URL(url, 'http://localhost').pathname;
709
+ // ─── Admin routes ─────────────────────────────────────────
710
+ if (pathname === '/api/_ns' && req.method === 'GET') {
711
+ const namespaces = [];
712
+ for (const name of registry.list()) {
713
+ const stores = registry.get(name);
714
+ const state = await stores.state.query();
715
+ const claims = await stores.claims.list();
716
+ const events = await stores.events.getEvents();
717
+ const discoveries = await stores.discoveries.search();
718
+ namespaces.push({
719
+ name,
720
+ state: state.length,
721
+ claims: claims.length,
722
+ events: events.cursor,
723
+ discoveries: discoveries.length,
724
+ });
725
+ }
726
+ json(res, 200, { namespaces });
727
+ return true;
728
+ }
729
+ const nsDeleteMatch = pathname.match(/^\/api\/_ns\/(.+)$/);
730
+ if (nsDeleteMatch && req.method === 'DELETE') {
731
+ const name = decodeURIComponent(nsDeleteMatch[1]);
732
+ const deleted = registry.delete(name);
733
+ json(res, 200, { deleted });
734
+ return true;
735
+ }
736
+ // ─── Namespace extraction ─────────────────────────────────
737
+ const { namespace, rewrittenPath } = extractNamespace(pathname);
738
+ if (namespace === null) {
739
+ json(res, 404, { error: `No route: ${req.method} ${pathname}` });
740
+ return true;
741
+ }
742
+ let stores;
743
+ try {
744
+ stores = registry.get(namespace);
745
+ }
746
+ catch (err) {
747
+ json(res, 400, { error: err.message });
748
+ return true;
749
+ }
750
+ // ─── Protocol routes ──────────────────────────────────────
751
+ if (rewrittenPath === '/api/protocol') {
752
+ if (req.method === 'GET') {
753
+ const spec = registry.getProtocol(namespace);
754
+ if (!spec) {
755
+ json(res, 200, { loaded: false });
756
+ }
757
+ else {
758
+ // Include team assignments if available
759
+ const teamAssignments = await stores.roles.getAssignments();
760
+ json(res, 200, {
761
+ loaded: true,
762
+ spec,
763
+ ...(teamAssignments.length > 0 ? { team: teamAssignments } : {}),
764
+ });
765
+ }
766
+ return true;
767
+ }
768
+ if (req.method === 'PUT') {
769
+ try {
770
+ // Body can be raw YAML/JSON string in "spec" field, or the spec object directly
771
+ let spec;
772
+ if (typeof body.spec === 'string') {
773
+ spec = await parseSpec(body.spec);
774
+ }
775
+ else if (body.acp && body.name) {
776
+ // Direct object — validate it
777
+ spec = await parseSpec(JSON.stringify(body));
778
+ }
779
+ else {
780
+ json(res, 400, { error: 'Missing "spec" field (YAML/JSON string) or direct spec object' });
781
+ return true;
782
+ }
783
+ // Scan protocol spec text content for prompt injection via guarded state
784
+ const specTexts = [];
785
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
786
+ const s = spec;
787
+ if (s.roles && typeof s.roles === 'object') {
788
+ for (const role of Object.values(s.roles)) {
789
+ if (role?.description)
790
+ specTexts.push(String(role.description));
791
+ if (role?.instructions)
792
+ specTexts.push(String(role.instructions));
793
+ }
794
+ }
795
+ if (s.phases && typeof s.phases === 'object') {
796
+ for (const phase of Object.values(s.phases)) {
797
+ if (phase?.description)
798
+ specTexts.push(String(phase.description));
799
+ }
800
+ }
801
+ if (specTexts.length > 0) {
802
+ // Use a state write to trigger carapace guard (write is reverted)
803
+ const scanKey = `__spec_scan_${Date.now()}`;
804
+ await stores.state.set(scanKey, specTexts.join(' '), getAgentId(req, body), '__scan');
805
+ await stores.state.delete(scanKey);
806
+ }
807
+ registry.setProtocol(namespace, spec);
808
+ json(res, 200, { loaded: true, name: spec.name, title: spec.title });
809
+ }
810
+ catch (err) {
811
+ if (err instanceof SpecValidationError) {
812
+ json(res, 400, { error: 'Invalid spec', details: err.errors });
813
+ }
814
+ else {
815
+ json(res, 400, { error: err.message });
816
+ }
817
+ }
818
+ return true;
819
+ }
820
+ }
821
+ // ─── Topic routes (Honeycomb) ──────────────────────────────
822
+ const topicMatch = rewrittenPath.match(/^\/api\/topics(?:\/(.+))?$/);
823
+ if (topicMatch) {
824
+ const router = registry.getRouter();
825
+ if (!router) {
826
+ json(res, 501, { error: 'Honeycomb topic routing not enabled (no notification bus)' });
827
+ return true;
828
+ }
829
+ const subpath = topicMatch[1]; // e.g. "subscribe/code_changed" or "subscribe" or undefined
830
+ // GET /api/topics — get all topics for this namespace
831
+ if (req.method === 'GET' && !subpath) {
832
+ const topics = router.getTopics(namespace);
833
+ json(res, 200, topics);
834
+ return true;
835
+ }
836
+ // POST /api/topics/subscribe — subscribe to a topic
837
+ if (req.method === 'POST' && subpath === 'subscribe') {
838
+ const topic = body.topic;
839
+ if (!topic) {
840
+ json(res, 400, { error: 'Missing required field: topic' });
841
+ return true;
842
+ }
843
+ router.subscribe(namespace, topic);
844
+ json(res, 200, { subscribed: true, topic });
845
+ return true;
846
+ }
847
+ // POST /api/topics/publish — declare a published topic
848
+ if (req.method === 'POST' && subpath === 'publish') {
849
+ const topic = body.topic;
850
+ if (!topic) {
851
+ json(res, 400, { error: 'Missing required field: topic' });
852
+ return true;
853
+ }
854
+ router.publish(namespace, topic);
855
+ json(res, 200, { published: true, topic });
856
+ return true;
857
+ }
858
+ // DELETE /api/topics/subscribe/:topic — unsubscribe from a topic
859
+ const unsubMatch = subpath?.match(/^subscribe\/(.+)$/);
860
+ if (req.method === 'DELETE' && unsubMatch) {
861
+ const topic = decodeURIComponent(unsubMatch[1]);
862
+ router.unsubscribe(namespace, topic);
863
+ json(res, 200, { unsubscribed: true, topic });
864
+ return true;
865
+ }
866
+ // DELETE /api/topics/publish/:topic — remove a published topic
867
+ const unpubMatch = subpath?.match(/^publish\/(.+)$/);
868
+ if (req.method === 'DELETE' && unpubMatch) {
869
+ const topic = decodeURIComponent(unpubMatch[1]);
870
+ router.unpublish(namespace, topic);
871
+ json(res, 200, { unpublished: true, topic });
872
+ return true;
873
+ }
874
+ json(res, 404, { error: `No route: ${req.method} ${pathname}` });
875
+ return true;
876
+ }
877
+ // ─── Dance tool calls ───────────────────────────────────────
878
+ const danceMatch = rewrittenPath.match(/^\/api\/dance\/([a-zA-Z0-9_-]+)$/);
879
+ if (danceMatch && req.method === 'POST') {
880
+ if (!danceSupport) {
881
+ json(res, 404, { error: 'No dance module loaded' }, req);
882
+ return true;
883
+ }
884
+ const toolName = danceMatch[1];
885
+ const agentId = getAgentId(req, body);
886
+ const role = typeof body.role === 'string' ? body.role : 'unknown';
887
+ const args = (typeof body.args === 'object' && body.args !== null ? body.args : {});
888
+ if (verbose) {
889
+ const ts = new Date().toISOString().slice(11, 23);
890
+ console.error(` ${ts} [${agentId}] REST dance_call ${toolName}`);
891
+ }
892
+ try {
893
+ const { callDanceTool } = await import('./dances.js');
894
+ const result = await callDanceTool(danceSupport.module, toolName, args, role, agentId, () => danceSupport.getState(namespace), danceSupport.getAcpHelper(namespace, agentId));
895
+ if ('error' in result) {
896
+ json(res, 400, { error: result.error }, req);
897
+ }
898
+ else {
899
+ json(res, 200, { result: result.result }, req);
900
+ }
901
+ }
902
+ catch (err) {
903
+ if (err instanceof CarapaceBlockedError) {
904
+ json(res, 403, {
905
+ error: 'prompt_injection_detected',
906
+ message: err.message,
907
+ }, req);
908
+ }
909
+ else {
910
+ json(res, 500, { error: 'Dance call failed' }, req);
911
+ }
912
+ }
913
+ return true;
914
+ }
915
+ // POST /api/dance — list available dance tools
916
+ if (rewrittenPath === '/api/dance' && req.method === 'GET') {
917
+ if (!danceSupport) {
918
+ json(res, 200, { tools: [] }, req);
919
+ return true;
920
+ }
921
+ const tools = [];
922
+ for (const [name, def] of danceSupport.module.tools) {
923
+ tools.push({ name, description: def.description, params: def.params });
924
+ }
925
+ json(res, 200, { tools }, req);
926
+ return true;
927
+ }
928
+ for (const route of routes) {
929
+ if (req.method !== route.method)
930
+ continue;
931
+ const match = rewrittenPath.match(route.pattern);
932
+ if (!match)
933
+ continue;
934
+ if (verbose) {
935
+ const ts = new Date().toISOString().slice(11, 23);
936
+ const agentId = getAgentId(req, body);
937
+ const nsLabel = namespace === 'default' ? '' : `@${namespace}`;
938
+ console.error(` ${ts} [${agentId}${nsLabel}] REST ${req.method} ${pathname}`);
939
+ }
940
+ try {
941
+ await route.handler(req, res, stores, match, body);
942
+ }
943
+ catch (err) {
944
+ if (err instanceof CarapaceBlockedError) {
945
+ json(res, 403, {
946
+ error: 'prompt_injection_detected',
947
+ message: err.message,
948
+ score: err.score,
949
+ action: err.action,
950
+ findings: err.findings,
951
+ });
952
+ }
953
+ else {
954
+ throw err;
955
+ }
956
+ }
957
+ return true;
958
+ }
959
+ json(res, 404, { error: `No route: ${req.method} ${pathname}` });
960
+ return true;
961
+ }
962
+ //# sourceMappingURL=rest.js.map