@decibelsystems/tools 2.0.1 → 2.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.
- package/README.md +115 -233
- package/dist/client/facade-client.d.ts +9 -3
- package/dist/client/facade-client.d.ts.map +1 -1
- package/dist/client/facade-client.js +58 -29
- package/dist/client/facade-client.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/transports.d.ts +3 -0
- package/dist/client/transports.d.ts.map +1 -1
- package/dist/client/transports.js +12 -0
- package/dist/client/transports.js.map +1 -1
- package/dist/client/types.d.ts +18 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/daemon.d.ts +29 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +64 -0
- package/dist/daemon.js.map +1 -1
- package/dist/daemonConfig.d.ts +7 -0
- package/dist/daemonConfig.d.ts.map +1 -1
- package/dist/daemonConfig.js +9 -0
- package/dist/daemonConfig.js.map +1 -1
- package/dist/facades/definitions.d.ts.map +1 -1
- package/dist/facades/definitions.js +116 -35
- package/dist/facades/definitions.js.map +1 -1
- package/dist/facades/types.d.ts +2 -0
- package/dist/facades/types.d.ts.map +1 -1
- package/dist/httpServer.d.ts +4 -0
- package/dist/httpServer.d.ts.map +1 -1
- package/dist/httpServer.js +183 -13
- package/dist/httpServer.js.map +1 -1
- package/dist/kernel.d.ts +9 -0
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +12 -7
- package/dist/kernel.js.map +1 -1
- package/dist/server.js +30 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/coordinator/coordinator.d.ts +14 -0
- package/dist/tools/coordinator/coordinator.d.ts.map +1 -1
- package/dist/tools/coordinator/coordinator.js +101 -0
- package/dist/tools/coordinator/coordinator.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts +2 -0
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +2 -0
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/deck.d.ts +4 -4
- package/dist/tools/deck.d.ts.map +1 -1
- package/dist/tools/deck.js +1546 -78
- package/dist/tools/deck.js.map +1 -1
- package/dist/tools/designer/index.js +1 -1
- package/dist/tools/designer/index.js.map +1 -1
- package/dist/tools/guardian/index.d.ts +1 -0
- package/dist/tools/guardian/index.d.ts.map +1 -1
- package/dist/tools/guardian/index.js +40 -1
- package/dist/tools/guardian/index.js.map +1 -1
- package/dist/tools/guardian.d.ts +30 -0
- package/dist/tools/guardian.d.ts.map +1 -1
- package/dist/tools/guardian.js +96 -0
- package/dist/tools/guardian.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +5 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/mother.d.ts +11 -0
- package/dist/tools/mother.d.ts.map +1 -0
- package/dist/tools/mother.js +503 -0
- package/dist/tools/mother.js.map +1 -0
- package/dist/tools/swarm.d.ts +11 -0
- package/dist/tools/swarm.d.ts.map +1 -0
- package/dist/tools/swarm.js +701 -0
- package/dist/tools/swarm.js.map +1 -0
- package/dist/tools/terminal.d.ts +17 -0
- package/dist/tools/terminal.d.ts.map +1 -0
- package/dist/tools/terminal.js +968 -0
- package/dist/tools/terminal.js.map +1 -0
- package/dist/transports/http.d.ts.map +1 -1
- package/dist/transports/http.js +2 -0
- package/dist/transports/http.js.map +1 -1
- package/dist/transports/mcp.d.ts.map +1 -1
- package/dist/transports/mcp.js +3 -0
- package/dist/transports/mcp.js.map +1 -1
- package/dist/transports/types.d.ts +4 -0
- package/dist/transports/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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
|