@decibelsystems/tools 2.0.2 → 2.1.1

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 (96) hide show
  1. package/README.md +115 -233
  2. package/dist/config/queueableActions.d.ts +11 -0
  3. package/dist/config/queueableActions.d.ts.map +1 -0
  4. package/dist/config/queueableActions.js +22 -0
  5. package/dist/config/queueableActions.js.map +1 -0
  6. package/dist/daemon.d.ts +29 -0
  7. package/dist/daemon.d.ts.map +1 -1
  8. package/dist/daemon.js +64 -0
  9. package/dist/daemon.js.map +1 -1
  10. package/dist/daemonConfig.d.ts +7 -0
  11. package/dist/daemonConfig.d.ts.map +1 -1
  12. package/dist/daemonConfig.js +9 -0
  13. package/dist/daemonConfig.js.map +1 -1
  14. package/dist/facades/definitions.d.ts.map +1 -1
  15. package/dist/facades/definitions.js +130 -33
  16. package/dist/facades/definitions.js.map +1 -1
  17. package/dist/httpQueueDetection.d.ts +16 -0
  18. package/dist/httpQueueDetection.d.ts.map +1 -0
  19. package/dist/httpQueueDetection.js +36 -0
  20. package/dist/httpQueueDetection.js.map +1 -0
  21. package/dist/httpServer.d.ts +4 -0
  22. package/dist/httpServer.d.ts.map +1 -1
  23. package/dist/httpServer.js +224 -97
  24. package/dist/httpServer.js.map +1 -1
  25. package/dist/kernel.d.ts +2 -0
  26. package/dist/kernel.d.ts.map +1 -1
  27. package/dist/kernel.js +1 -0
  28. package/dist/kernel.js.map +1 -1
  29. package/dist/server.js +30 -1
  30. package/dist/server.js.map +1 -1
  31. package/dist/tools/agentic/agentQueue.d.ts +50 -0
  32. package/dist/tools/agentic/agentQueue.d.ts.map +1 -0
  33. package/dist/tools/agentic/agentQueue.js +226 -0
  34. package/dist/tools/agentic/agentQueue.js.map +1 -0
  35. package/dist/tools/agentic/index.d.ts +2 -0
  36. package/dist/tools/agentic/index.d.ts.map +1 -1
  37. package/dist/tools/agentic/index.js +84 -0
  38. package/dist/tools/agentic/index.js.map +1 -1
  39. package/dist/tools/coordinator/coordinator.d.ts +14 -0
  40. package/dist/tools/coordinator/coordinator.d.ts.map +1 -1
  41. package/dist/tools/coordinator/coordinator.js +101 -0
  42. package/dist/tools/coordinator/coordinator.js.map +1 -1
  43. package/dist/tools/coordinator/index.d.ts +2 -0
  44. package/dist/tools/coordinator/index.d.ts.map +1 -1
  45. package/dist/tools/coordinator/index.js +2 -0
  46. package/dist/tools/coordinator/index.js.map +1 -1
  47. package/dist/tools/decibel/index.d.ts +3 -0
  48. package/dist/tools/decibel/index.d.ts.map +1 -0
  49. package/dist/tools/decibel/index.js +150 -0
  50. package/dist/tools/decibel/index.js.map +1 -0
  51. package/dist/tools/decibel.d.ts +13 -0
  52. package/dist/tools/decibel.d.ts.map +1 -0
  53. package/dist/tools/decibel.js +156 -0
  54. package/dist/tools/decibel.js.map +1 -0
  55. package/dist/tools/deck.d.ts +4 -4
  56. package/dist/tools/deck.d.ts.map +1 -1
  57. package/dist/tools/deck.js +1546 -78
  58. package/dist/tools/deck.js.map +1 -1
  59. package/dist/tools/designer/index.d.ts +6 -0
  60. package/dist/tools/designer/index.d.ts.map +1 -1
  61. package/dist/tools/designer/index.js +344 -1
  62. package/dist/tools/designer/index.js.map +1 -1
  63. package/dist/tools/designer.d.ts +143 -0
  64. package/dist/tools/designer.d.ts.map +1 -1
  65. package/dist/tools/designer.js +457 -0
  66. package/dist/tools/designer.js.map +1 -1
  67. package/dist/tools/guardian/index.d.ts +1 -0
  68. package/dist/tools/guardian/index.d.ts.map +1 -1
  69. package/dist/tools/guardian/index.js +40 -1
  70. package/dist/tools/guardian/index.js.map +1 -1
  71. package/dist/tools/guardian.d.ts +30 -0
  72. package/dist/tools/guardian.d.ts.map +1 -1
  73. package/dist/tools/guardian.js +96 -0
  74. package/dist/tools/guardian.js.map +1 -1
  75. package/dist/tools/index.d.ts.map +1 -1
  76. package/dist/tools/index.js +7 -1
  77. package/dist/tools/index.js.map +1 -1
  78. package/dist/tools/mother.d.ts +11 -0
  79. package/dist/tools/mother.d.ts.map +1 -0
  80. package/dist/tools/mother.js +503 -0
  81. package/dist/tools/mother.js.map +1 -0
  82. package/dist/tools/swarm.d.ts +11 -0
  83. package/dist/tools/swarm.d.ts.map +1 -0
  84. package/dist/tools/swarm.js +701 -0
  85. package/dist/tools/swarm.js.map +1 -0
  86. package/dist/tools/terminal.d.ts +17 -0
  87. package/dist/tools/terminal.d.ts.map +1 -0
  88. package/dist/tools/terminal.js +968 -0
  89. package/dist/tools/terminal.js.map +1 -0
  90. package/dist/transports/http.d.ts.map +1 -1
  91. package/dist/transports/http.js +2 -0
  92. package/dist/transports/http.js.map +1 -1
  93. package/dist/transports/types.d.ts +4 -0
  94. package/dist/transports/types.d.ts.map +1 -1
  95. package/package.json +1 -1
  96. package/templates/docs.html +1084 -0
@@ -0,0 +1,701 @@
1
+ /**
2
+ * Swarm Tools — Shared Agent Context Layer
3
+ *
4
+ * Multi-agent coordination via sessions, signals, artifacts, and snapshots.
5
+ * Phase 1: Sessions + Signals. Backed by Supabase (Core project).
6
+ *
7
+ * Env var: DECK_SUPABASE_KEY (reuses deck's anon key — same Supabase project)
8
+ */
9
+ import { createClient } from '@supabase/supabase-js';
10
+ const CORE_SUPABASE_URL = 'https://dqftcixhsvcjtyxsxoao.supabase.co';
11
+ const CORE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRxZnRjaXhoc3ZjanR5eHN4b2FvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjcyMTgzMTYsImV4cCI6MjA4Mjc5NDMxNn0.erZq0zJMF-tATX7b0mofGsajFUvcqlZE5PQ4qcggmGc';
12
+ let client = null;
13
+ function getClient() {
14
+ if (!client) {
15
+ const key = process.env.DECK_SUPABASE_KEY || CORE_ANON_KEY;
16
+ client = createClient(CORE_SUPABASE_URL, key, {
17
+ auth: { persistSession: false, autoRefreshToken: false },
18
+ });
19
+ }
20
+ return client;
21
+ }
22
+ function jsonResult(data, isError = false) {
23
+ return {
24
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
25
+ isError,
26
+ };
27
+ }
28
+ // ============================================================================
29
+ // Session Tools
30
+ // ============================================================================
31
+ const swarmStartSession = {
32
+ definition: {
33
+ name: 'swarm_start_session',
34
+ description: 'Create a new multi-agent session. Returns the session ID for participants to join.',
35
+ annotations: {
36
+ title: 'Start Session',
37
+ readOnlyHint: false,
38
+ destructiveHint: false,
39
+ openWorldHint: true,
40
+ },
41
+ inputSchema: {
42
+ type: 'object',
43
+ properties: {
44
+ mission: {
45
+ type: 'string',
46
+ description: 'What this session aims to accomplish',
47
+ },
48
+ coordinator: {
49
+ type: 'string',
50
+ description: 'ID or name of the coordinating agent',
51
+ },
52
+ },
53
+ required: ['mission'],
54
+ },
55
+ },
56
+ handler: async (args) => {
57
+ try {
58
+ const db = getClient();
59
+ const { data, error } = await db
60
+ .from('agent_sessions')
61
+ .insert({
62
+ mission: args.mission,
63
+ coordinator: args.coordinator || null,
64
+ status: 'active',
65
+ participants: [],
66
+ })
67
+ .select('id, mission, status, created_at')
68
+ .single();
69
+ if (error)
70
+ throw new Error(error.message);
71
+ return jsonResult({ success: true, session: data });
72
+ }
73
+ catch (error) {
74
+ const msg = error instanceof Error ? error.message : String(error);
75
+ return jsonResult({ success: false, error: msg }, true);
76
+ }
77
+ },
78
+ };
79
+ const swarmJoinSession = {
80
+ definition: {
81
+ name: 'swarm_join_session',
82
+ description: 'Join an active session. Declares your role, capabilities, and topic subscriptions.',
83
+ annotations: {
84
+ title: 'Join Session',
85
+ readOnlyHint: false,
86
+ destructiveHint: false,
87
+ openWorldHint: true,
88
+ },
89
+ inputSchema: {
90
+ type: 'object',
91
+ properties: {
92
+ session_id: {
93
+ type: 'string',
94
+ description: 'Session ID to join',
95
+ },
96
+ peer_id: {
97
+ type: 'string',
98
+ description: 'Your peer ID (from claude-peers)',
99
+ },
100
+ repo: {
101
+ type: 'string',
102
+ description: 'Repository name you are working in',
103
+ },
104
+ role: {
105
+ type: 'string',
106
+ description: 'Your role in this session (e.g. "Data layer — Supabase cards, prices")',
107
+ },
108
+ capabilities: {
109
+ type: 'array',
110
+ items: { type: 'string' },
111
+ description: 'What you can do (e.g. ["query_supabase", "run_scryfall_sync"])',
112
+ },
113
+ subscriptions: {
114
+ type: 'array',
115
+ items: { type: 'string' },
116
+ description: 'Topics you want signals for (e.g. ["card_data", "api_contracts"])',
117
+ },
118
+ },
119
+ required: ['session_id', 'peer_id'],
120
+ },
121
+ },
122
+ handler: async (args) => {
123
+ try {
124
+ const db = getClient();
125
+ // Read current participants
126
+ const { data: session, error: readErr } = await db
127
+ .from('agent_sessions')
128
+ .select('participants, status')
129
+ .eq('id', args.session_id)
130
+ .single();
131
+ if (readErr)
132
+ throw new Error(readErr.message);
133
+ if (!session)
134
+ throw new Error(`Session ${args.session_id} not found`);
135
+ if (session.status !== 'active' && session.status !== 'paused') {
136
+ throw new Error(`Session is ${session.status}, cannot join`);
137
+ }
138
+ const participants = session.participants || [];
139
+ const existing = participants.findIndex((p) => p.peer_id === args.peer_id);
140
+ const participant = {
141
+ peer_id: args.peer_id,
142
+ repo: args.repo || null,
143
+ role: args.role || null,
144
+ capabilities: args.capabilities || [],
145
+ subscriptions: args.subscriptions || ['*'],
146
+ joined_at: new Date().toISOString(),
147
+ };
148
+ if (existing >= 0) {
149
+ participants[existing] = participant;
150
+ }
151
+ else {
152
+ participants.push(participant);
153
+ }
154
+ const { error: updateErr } = await db
155
+ .from('agent_sessions')
156
+ .update({
157
+ participants,
158
+ status: 'active', // resume if paused
159
+ paused_at: null,
160
+ })
161
+ .eq('id', args.session_id);
162
+ if (updateErr)
163
+ throw new Error(updateErr.message);
164
+ return jsonResult({
165
+ success: true,
166
+ joined: args.session_id,
167
+ participant_count: participants.length,
168
+ });
169
+ }
170
+ catch (error) {
171
+ const msg = error instanceof Error ? error.message : String(error);
172
+ return jsonResult({ success: false, error: msg }, true);
173
+ }
174
+ },
175
+ };
176
+ const swarmSessionStatus = {
177
+ definition: {
178
+ name: 'swarm_session_status',
179
+ description: 'Read current session state: mission, participants, signal count, status.',
180
+ annotations: {
181
+ title: 'Session Status',
182
+ readOnlyHint: true,
183
+ destructiveHint: false,
184
+ openWorldHint: true,
185
+ },
186
+ inputSchema: {
187
+ type: 'object',
188
+ properties: {
189
+ session_id: {
190
+ type: 'string',
191
+ description: 'Session ID (omit to list active sessions)',
192
+ },
193
+ },
194
+ required: [],
195
+ },
196
+ },
197
+ handler: async (args) => {
198
+ try {
199
+ const db = getClient();
200
+ if (!args.session_id) {
201
+ // List active/paused sessions
202
+ const { data, error } = await db
203
+ .from('agent_sessions')
204
+ .select('id, mission, coordinator, status, participants, created_at')
205
+ .in('status', ['active', 'paused'])
206
+ .order('created_at', { ascending: false })
207
+ .limit(20);
208
+ if (error)
209
+ throw new Error(error.message);
210
+ return jsonResult({ success: true, sessions: data, count: data?.length || 0 });
211
+ }
212
+ // Get specific session with signal count
213
+ const { data: session, error: sessErr } = await db
214
+ .from('agent_sessions')
215
+ .select('*')
216
+ .eq('id', args.session_id)
217
+ .single();
218
+ if (sessErr)
219
+ throw new Error(sessErr.message);
220
+ const { count, error: countErr } = await db
221
+ .from('agent_signals')
222
+ .select('*', { count: 'exact', head: true })
223
+ .eq('session_id', args.session_id);
224
+ if (countErr)
225
+ throw new Error(countErr.message);
226
+ return jsonResult({
227
+ success: true,
228
+ session,
229
+ signal_count: count || 0,
230
+ });
231
+ }
232
+ catch (error) {
233
+ const msg = error instanceof Error ? error.message : String(error);
234
+ return jsonResult({ success: false, error: msg }, true);
235
+ }
236
+ },
237
+ };
238
+ const swarmPauseSession = {
239
+ definition: {
240
+ name: 'swarm_pause_session',
241
+ description: 'Pause a session for later continuation. Does not trigger learnings extraction.',
242
+ annotations: {
243
+ title: 'Pause Session',
244
+ readOnlyHint: false,
245
+ destructiveHint: false,
246
+ openWorldHint: true,
247
+ },
248
+ inputSchema: {
249
+ type: 'object',
250
+ properties: {
251
+ session_id: { type: 'string', description: 'Session ID to pause' },
252
+ },
253
+ required: ['session_id'],
254
+ },
255
+ },
256
+ handler: async (args) => {
257
+ try {
258
+ const db = getClient();
259
+ const { error } = await db
260
+ .from('agent_sessions')
261
+ .update({ status: 'paused', paused_at: new Date().toISOString() })
262
+ .eq('id', args.session_id);
263
+ if (error)
264
+ throw new Error(error.message);
265
+ return jsonResult({ success: true, paused: args.session_id });
266
+ }
267
+ catch (error) {
268
+ const msg = error instanceof Error ? error.message : String(error);
269
+ return jsonResult({ success: false, error: msg }, true);
270
+ }
271
+ },
272
+ };
273
+ const swarmCloseSession = {
274
+ definition: {
275
+ name: 'swarm_close_session',
276
+ description: 'Close a session. Returns a summary of signals and artifacts for learnings extraction.',
277
+ annotations: {
278
+ title: 'Close Session',
279
+ readOnlyHint: false,
280
+ destructiveHint: false,
281
+ openWorldHint: true,
282
+ },
283
+ inputSchema: {
284
+ type: 'object',
285
+ properties: {
286
+ session_id: { type: 'string', description: 'Session ID to close' },
287
+ },
288
+ required: ['session_id'],
289
+ },
290
+ },
291
+ handler: async (args) => {
292
+ try {
293
+ const db = getClient();
294
+ // Close the session
295
+ const { error: closeErr } = await db
296
+ .from('agent_sessions')
297
+ .update({ status: 'completed', closed_at: new Date().toISOString() })
298
+ .eq('id', args.session_id);
299
+ if (closeErr)
300
+ throw new Error(closeErr.message);
301
+ // Gather summary for learnings extraction
302
+ const { data: signals } = await db
303
+ .from('agent_signals')
304
+ .select('type, topic, summary, resolved, resolution')
305
+ .eq('session_id', args.session_id)
306
+ .order('created_at', { ascending: true });
307
+ const { data: session } = await db
308
+ .from('agent_sessions')
309
+ .select('mission, participants, created_at, closed_at')
310
+ .eq('id', args.session_id)
311
+ .single();
312
+ const decisions = signals?.filter((s) => s.type === 'decision') || [];
313
+ const unresolved = signals?.filter((s) => s.type === 'request' && !s.resolved) || [];
314
+ const bugFixPairs = signals?.filter((s) => s.type === 'bug_found' || s.type === 'fix_applied') || [];
315
+ return jsonResult({
316
+ success: true,
317
+ closed: args.session_id,
318
+ summary: {
319
+ mission: session?.mission,
320
+ participants: session?.participants,
321
+ duration_minutes: session?.created_at && session?.closed_at
322
+ ? Math.round((new Date(session.closed_at).getTime() -
323
+ new Date(session.created_at).getTime()) /
324
+ 60000)
325
+ : null,
326
+ total_signals: signals?.length || 0,
327
+ decisions,
328
+ unresolved_requests: unresolved,
329
+ bug_fix_pairs: bugFixPairs,
330
+ },
331
+ });
332
+ }
333
+ catch (error) {
334
+ const msg = error instanceof Error ? error.message : String(error);
335
+ return jsonResult({ success: false, error: msg }, true);
336
+ }
337
+ },
338
+ };
339
+ // ============================================================================
340
+ // Signal Tools
341
+ // ============================================================================
342
+ const SIGNAL_TYPES = [
343
+ 'bug_found', 'fix_applied', 'blocker', 'decision', 'finding', 'request',
344
+ 'api_changed', 'breaking_change', 'schema_changed', 'new_capability',
345
+ 'gate', 'intent',
346
+ ];
347
+ const swarmEmitSignal = {
348
+ definition: {
349
+ name: 'swarm_emit_signal',
350
+ description: 'Publish a signal to the session. Types: bug_found, fix_applied, blocker, decision, finding, request, api_changed, breaking_change, schema_changed, new_capability, gate, intent.',
351
+ annotations: {
352
+ title: 'Emit Signal',
353
+ readOnlyHint: false,
354
+ destructiveHint: false,
355
+ openWorldHint: true,
356
+ },
357
+ inputSchema: {
358
+ type: 'object',
359
+ properties: {
360
+ session_id: { type: 'string', description: 'Session ID' },
361
+ type: {
362
+ type: 'string',
363
+ enum: [...SIGNAL_TYPES],
364
+ description: 'Signal type',
365
+ },
366
+ topic: { type: 'string', description: 'Domain tag for routing (e.g. "deck_search", "card_data")' },
367
+ emitter_id: { type: 'string', description: 'Your peer ID' },
368
+ emitter_role: { type: 'string', description: 'Your role in the session' },
369
+ severity: {
370
+ type: 'string',
371
+ enum: ['high', 'medium', 'low'],
372
+ description: 'Signal severity (default: medium)',
373
+ },
374
+ summary: { type: 'string', description: 'One-line summary of what happened' },
375
+ payload: {
376
+ type: 'object',
377
+ description: 'Structured data (typed JSONB — include relevant details)',
378
+ },
379
+ needs: { type: 'string', description: 'What is needed from other participants' },
380
+ requires_ack: {
381
+ type: 'boolean',
382
+ description: 'If true, surfaces prominently until acknowledged (use for breaking changes)',
383
+ },
384
+ tags: {
385
+ type: 'array',
386
+ items: { type: 'string' },
387
+ description: 'Ad-hoc tags for filtering beyond topic',
388
+ },
389
+ },
390
+ required: ['session_id', 'type', 'topic', 'emitter_id', 'summary'],
391
+ },
392
+ },
393
+ handler: async (args) => {
394
+ try {
395
+ const db = getClient();
396
+ // Check for intent conflicts
397
+ let conflict_warning = null;
398
+ if (args.type === 'intent') {
399
+ const target = args.payload?.target;
400
+ if (target) {
401
+ const { data: existing } = await db
402
+ .from('agent_signals')
403
+ .select('emitter_id, summary, created_at')
404
+ .eq('session_id', args.session_id)
405
+ .eq('type', 'intent')
406
+ .eq('resolved', false)
407
+ .contains('payload', { target });
408
+ if (existing && existing.length > 0) {
409
+ conflict_warning = `Conflict: ${existing[0].emitter_id} has active intent on "${target}" — coordinate before proceeding`;
410
+ }
411
+ }
412
+ }
413
+ const { data, error } = await db
414
+ .from('agent_signals')
415
+ .insert({
416
+ session_id: args.session_id,
417
+ type: args.type,
418
+ topic: args.topic,
419
+ emitter_id: args.emitter_id,
420
+ emitter_role: args.emitter_role || null,
421
+ severity: args.severity || 'medium',
422
+ summary: args.summary,
423
+ payload: args.payload || {},
424
+ needs: args.needs || null,
425
+ requires_ack: args.requires_ack || false,
426
+ tags: args.tags || [],
427
+ })
428
+ .select('id, type, topic, summary, created_at')
429
+ .single();
430
+ if (error)
431
+ throw new Error(error.message);
432
+ const result = { success: true, signal: data };
433
+ if (conflict_warning)
434
+ result.conflict_warning = conflict_warning;
435
+ return jsonResult(result);
436
+ }
437
+ catch (error) {
438
+ const msg = error instanceof Error ? error.message : String(error);
439
+ return jsonResult({ success: false, error: msg }, true);
440
+ }
441
+ },
442
+ };
443
+ const swarmReadSignals = {
444
+ definition: {
445
+ name: 'swarm_read_signals',
446
+ description: 'Read signals from a session. Filter by type, topic, resolved status, or timestamp.',
447
+ annotations: {
448
+ title: 'Read Signals',
449
+ readOnlyHint: true,
450
+ destructiveHint: false,
451
+ openWorldHint: true,
452
+ },
453
+ inputSchema: {
454
+ type: 'object',
455
+ properties: {
456
+ session_id: { type: 'string', description: 'Session ID' },
457
+ type: { type: 'string', description: 'Filter by signal type' },
458
+ topic: { type: 'string', description: 'Filter by topic' },
459
+ resolved: { type: 'boolean', description: 'Filter by resolved status' },
460
+ since: { type: 'string', description: 'ISO timestamp — only signals after this time' },
461
+ requires_ack: { type: 'boolean', description: 'Filter to signals needing acknowledgment' },
462
+ limit: { type: 'number', description: 'Max signals to return (default: 50)' },
463
+ },
464
+ required: ['session_id'],
465
+ },
466
+ },
467
+ handler: async (args) => {
468
+ try {
469
+ const db = getClient();
470
+ let query = db
471
+ .from('agent_signals')
472
+ .select('*')
473
+ .eq('session_id', args.session_id)
474
+ .order('created_at', { ascending: true })
475
+ .limit(args.limit || 50);
476
+ if (args.type)
477
+ query = query.eq('type', args.type);
478
+ if (args.topic)
479
+ query = query.eq('topic', args.topic);
480
+ if (args.resolved !== undefined)
481
+ query = query.eq('resolved', args.resolved);
482
+ if (args.since)
483
+ query = query.gt('created_at', args.since);
484
+ if (args.requires_ack)
485
+ query = query.eq('requires_ack', true);
486
+ const { data, error } = await query;
487
+ if (error)
488
+ throw new Error(error.message);
489
+ return jsonResult({
490
+ success: true,
491
+ signals: data,
492
+ count: data?.length || 0,
493
+ });
494
+ }
495
+ catch (error) {
496
+ const msg = error instanceof Error ? error.message : String(error);
497
+ return jsonResult({ success: false, error: msg }, true);
498
+ }
499
+ },
500
+ };
501
+ const swarmAckSignal = {
502
+ definition: {
503
+ name: 'swarm_ack_signal',
504
+ description: 'Acknowledge a signal: seen, will_act, or not_relevant. Lightweight — no full response needed.',
505
+ annotations: {
506
+ title: 'Ack Signal',
507
+ readOnlyHint: false,
508
+ destructiveHint: false,
509
+ openWorldHint: true,
510
+ },
511
+ inputSchema: {
512
+ type: 'object',
513
+ properties: {
514
+ signal_id: { type: 'string', description: 'Signal ID to acknowledge' },
515
+ peer_id: { type: 'string', description: 'Your peer ID' },
516
+ status: {
517
+ type: 'string',
518
+ enum: ['seen', 'will_act', 'not_relevant'],
519
+ description: 'Acknowledgment status',
520
+ },
521
+ },
522
+ required: ['signal_id', 'peer_id', 'status'],
523
+ },
524
+ },
525
+ handler: async (args) => {
526
+ try {
527
+ const db = getClient();
528
+ const { data: signal, error: readErr } = await db
529
+ .from('agent_signals')
530
+ .select('acks')
531
+ .eq('id', args.signal_id)
532
+ .single();
533
+ if (readErr)
534
+ throw new Error(readErr.message);
535
+ const acks = signal?.acks || [];
536
+ const existing = acks.findIndex((a) => a.peer_id === args.peer_id);
537
+ const ack = {
538
+ peer_id: args.peer_id,
539
+ status: args.status,
540
+ at: new Date().toISOString(),
541
+ };
542
+ if (existing >= 0) {
543
+ acks[existing] = ack;
544
+ }
545
+ else {
546
+ acks.push(ack);
547
+ }
548
+ const { error: updateErr } = await db
549
+ .from('agent_signals')
550
+ .update({ acks })
551
+ .eq('id', args.signal_id);
552
+ if (updateErr)
553
+ throw new Error(updateErr.message);
554
+ return jsonResult({ success: true, acked: args.signal_id, status: args.status });
555
+ }
556
+ catch (error) {
557
+ const msg = error instanceof Error ? error.message : String(error);
558
+ return jsonResult({ success: false, error: msg }, true);
559
+ }
560
+ },
561
+ };
562
+ const swarmClaimSignal = {
563
+ definition: {
564
+ name: 'swarm_claim_signal',
565
+ description: 'Claim a request signal ("I\'m on it"). First-claim-wins. Use release to unclaim.',
566
+ annotations: {
567
+ title: 'Claim Signal',
568
+ readOnlyHint: false,
569
+ destructiveHint: false,
570
+ openWorldHint: true,
571
+ },
572
+ inputSchema: {
573
+ type: 'object',
574
+ properties: {
575
+ signal_id: { type: 'string', description: 'Signal ID to claim' },
576
+ peer_id: { type: 'string', description: 'Your peer ID' },
577
+ },
578
+ required: ['signal_id', 'peer_id'],
579
+ },
580
+ },
581
+ handler: async (args) => {
582
+ try {
583
+ const db = getClient();
584
+ const { data: signal, error: readErr } = await db
585
+ .from('agent_signals')
586
+ .select('claimed_by, type')
587
+ .eq('id', args.signal_id)
588
+ .single();
589
+ if (readErr)
590
+ throw new Error(readErr.message);
591
+ if (signal?.claimed_by) {
592
+ return jsonResult({
593
+ success: false,
594
+ error: `Already claimed by ${signal.claimed_by}`,
595
+ }, true);
596
+ }
597
+ const { error: updateErr } = await db
598
+ .from('agent_signals')
599
+ .update({ claimed_by: args.peer_id })
600
+ .eq('id', args.signal_id)
601
+ .is('claimed_by', null); // Optimistic concurrency
602
+ if (updateErr)
603
+ throw new Error(updateErr.message);
604
+ return jsonResult({ success: true, claimed: args.signal_id, by: args.peer_id });
605
+ }
606
+ catch (error) {
607
+ const msg = error instanceof Error ? error.message : String(error);
608
+ return jsonResult({ success: false, error: msg }, true);
609
+ }
610
+ },
611
+ };
612
+ const swarmReleaseSignal = {
613
+ definition: {
614
+ name: 'swarm_release_signal',
615
+ description: 'Release a claimed signal back to the pool.',
616
+ annotations: {
617
+ title: 'Release Signal',
618
+ readOnlyHint: false,
619
+ destructiveHint: false,
620
+ openWorldHint: true,
621
+ },
622
+ inputSchema: {
623
+ type: 'object',
624
+ properties: {
625
+ signal_id: { type: 'string', description: 'Signal ID to release' },
626
+ peer_id: { type: 'string', description: 'Your peer ID (must be the claimant)' },
627
+ },
628
+ required: ['signal_id', 'peer_id'],
629
+ },
630
+ },
631
+ handler: async (args) => {
632
+ try {
633
+ const db = getClient();
634
+ const { error } = await db
635
+ .from('agent_signals')
636
+ .update({ claimed_by: null })
637
+ .eq('id', args.signal_id)
638
+ .eq('claimed_by', args.peer_id);
639
+ if (error)
640
+ throw new Error(error.message);
641
+ return jsonResult({ success: true, released: args.signal_id });
642
+ }
643
+ catch (error) {
644
+ const msg = error instanceof Error ? error.message : String(error);
645
+ return jsonResult({ success: false, error: msg }, true);
646
+ }
647
+ },
648
+ };
649
+ const swarmResolveSignal = {
650
+ definition: {
651
+ name: 'swarm_resolve_signal',
652
+ description: 'Mark a signal as resolved with a resolution note.',
653
+ annotations: {
654
+ title: 'Resolve Signal',
655
+ readOnlyHint: false,
656
+ destructiveHint: false,
657
+ openWorldHint: true,
658
+ },
659
+ inputSchema: {
660
+ type: 'object',
661
+ properties: {
662
+ signal_id: { type: 'string', description: 'Signal ID to resolve' },
663
+ resolution: { type: 'string', description: 'How it was resolved' },
664
+ },
665
+ required: ['signal_id', 'resolution'],
666
+ },
667
+ },
668
+ handler: async (args) => {
669
+ try {
670
+ const db = getClient();
671
+ const { error } = await db
672
+ .from('agent_signals')
673
+ .update({ resolved: true, resolution: args.resolution })
674
+ .eq('id', args.signal_id);
675
+ if (error)
676
+ throw new Error(error.message);
677
+ return jsonResult({ success: true, resolved: args.signal_id });
678
+ }
679
+ catch (error) {
680
+ const msg = error instanceof Error ? error.message : String(error);
681
+ return jsonResult({ success: false, error: msg }, true);
682
+ }
683
+ },
684
+ };
685
+ // ============================================================================
686
+ // Export
687
+ // ============================================================================
688
+ export const swarmTools = [
689
+ swarmStartSession,
690
+ swarmJoinSession,
691
+ swarmSessionStatus,
692
+ swarmPauseSession,
693
+ swarmCloseSession,
694
+ swarmEmitSignal,
695
+ swarmReadSignals,
696
+ swarmAckSignal,
697
+ swarmClaimSignal,
698
+ swarmReleaseSignal,
699
+ swarmResolveSignal,
700
+ ];
701
+ //# sourceMappingURL=swarm.js.map