swarm-mail 0.1.0 → 0.1.3
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 +28 -0
- package/dist/adapter.d.ts +36 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16710 -0
- package/{src/pglite.ts → dist/pglite.d.ts} +7 -93
- package/dist/pglite.d.ts.map +1 -0
- package/dist/streams/agent-mail.d.ts +139 -0
- package/dist/streams/agent-mail.d.ts.map +1 -0
- package/dist/streams/debug.d.ts +173 -0
- package/dist/streams/debug.d.ts.map +1 -0
- package/dist/streams/effect/ask.d.ts +124 -0
- package/dist/streams/effect/ask.d.ts.map +1 -0
- package/dist/streams/effect/cursor.d.ts +87 -0
- package/dist/streams/effect/cursor.d.ts.map +1 -0
- package/dist/streams/effect/deferred.d.ts +108 -0
- package/dist/streams/effect/deferred.d.ts.map +1 -0
- package/{src/streams/effect/index.ts → dist/streams/effect/index.d.ts} +1 -0
- package/dist/streams/effect/index.d.ts.map +1 -0
- package/{src/streams/effect/layers.ts → dist/streams/effect/layers.d.ts} +8 -33
- package/dist/streams/effect/layers.d.ts.map +1 -0
- package/dist/streams/effect/lock.d.ts +137 -0
- package/dist/streams/effect/lock.d.ts.map +1 -0
- package/dist/streams/effect/mailbox.d.ts +98 -0
- package/dist/streams/effect/mailbox.d.ts.map +1 -0
- package/dist/streams/events.d.ts +487 -0
- package/dist/streams/events.d.ts.map +1 -0
- package/dist/streams/index.d.ts +106 -0
- package/dist/streams/index.d.ts.map +1 -0
- package/dist/streams/migrations.d.ts +102 -0
- package/dist/streams/migrations.d.ts.map +1 -0
- package/dist/streams/projections.d.ts +173 -0
- package/dist/streams/projections.d.ts.map +1 -0
- package/dist/streams/store.d.ts +171 -0
- package/dist/streams/store.d.ts.map +1 -0
- package/dist/streams/swarm-mail.d.ts +153 -0
- package/dist/streams/swarm-mail.d.ts.map +1 -0
- package/dist/types/adapter.d.ts +267 -0
- package/dist/types/adapter.d.ts.map +1 -0
- package/dist/types/database.d.ts +117 -0
- package/dist/types/database.d.ts.map +1 -0
- package/{src/types/index.ts → dist/types/index.d.ts} +2 -15
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +21 -5
- package/src/adapter.ts +0 -306
- package/src/index.ts +0 -57
- package/src/streams/agent-mail.test.ts +0 -777
- package/src/streams/agent-mail.ts +0 -535
- package/src/streams/debug.test.ts +0 -500
- package/src/streams/debug.ts +0 -727
- package/src/streams/effect/ask.integration.test.ts +0 -314
- package/src/streams/effect/ask.ts +0 -202
- package/src/streams/effect/cursor.integration.test.ts +0 -418
- package/src/streams/effect/cursor.ts +0 -288
- package/src/streams/effect/deferred.test.ts +0 -357
- package/src/streams/effect/deferred.ts +0 -445
- package/src/streams/effect/lock.test.ts +0 -385
- package/src/streams/effect/lock.ts +0 -399
- package/src/streams/effect/mailbox.test.ts +0 -260
- package/src/streams/effect/mailbox.ts +0 -318
- package/src/streams/events.test.ts +0 -924
- package/src/streams/events.ts +0 -329
- package/src/streams/index.test.ts +0 -229
- package/src/streams/index.ts +0 -578
- package/src/streams/migrations.test.ts +0 -359
- package/src/streams/migrations.ts +0 -362
- package/src/streams/projections.test.ts +0 -611
- package/src/streams/projections.ts +0 -564
- package/src/streams/store.integration.test.ts +0 -658
- package/src/streams/store.ts +0 -1129
- package/src/streams/swarm-mail.ts +0 -552
- package/src/types/adapter.ts +0 -392
- package/src/types/database.ts +0 -127
- package/tsconfig.json +0 -22
package/src/streams/debug.ts
DELETED
|
@@ -1,727 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Swarm Mail Debug Tools - Event inspection and state debugging
|
|
3
|
-
*
|
|
4
|
-
* Tools for inspecting the event store, agent state, and system health.
|
|
5
|
-
* Useful for debugging issues and understanding system behavior.
|
|
6
|
-
*/
|
|
7
|
-
import { getDatabase, getDatabaseStats } from "./index";
|
|
8
|
-
import { readEvents, getLatestSequence, replayEventsBatched } from "./store";
|
|
9
|
-
import { getAgent, getActiveReservations, getMessage } from "./projections";
|
|
10
|
-
import type { AgentEvent } from "./events";
|
|
11
|
-
|
|
12
|
-
// ============================================================================
|
|
13
|
-
// Types
|
|
14
|
-
// ============================================================================
|
|
15
|
-
|
|
16
|
-
export interface DebugEventsOptions {
|
|
17
|
-
projectPath: string;
|
|
18
|
-
types?: AgentEvent["type"][];
|
|
19
|
-
agentName?: string;
|
|
20
|
-
limit?: number;
|
|
21
|
-
since?: number;
|
|
22
|
-
until?: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface DebugEventResult {
|
|
26
|
-
id: number;
|
|
27
|
-
sequence: number;
|
|
28
|
-
type: AgentEvent["type"];
|
|
29
|
-
timestamp: number;
|
|
30
|
-
timestamp_human: string;
|
|
31
|
-
agent_name?: string;
|
|
32
|
-
from_agent?: string;
|
|
33
|
-
to_agents?: string[];
|
|
34
|
-
[key: string]: unknown;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface DebugEventsResult {
|
|
38
|
-
events: DebugEventResult[];
|
|
39
|
-
total: number;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface DebugAgentOptions {
|
|
43
|
-
projectPath: string;
|
|
44
|
-
agentName: string;
|
|
45
|
-
includeEvents?: boolean;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface DebugAgentResult {
|
|
49
|
-
agent: {
|
|
50
|
-
name: string;
|
|
51
|
-
program: string;
|
|
52
|
-
model: string;
|
|
53
|
-
task_description: string | null;
|
|
54
|
-
registered_at: number;
|
|
55
|
-
last_active_at: number;
|
|
56
|
-
} | null;
|
|
57
|
-
stats: {
|
|
58
|
-
messagesSent: number;
|
|
59
|
-
messagesReceived: number;
|
|
60
|
-
};
|
|
61
|
-
reservations: Array<{
|
|
62
|
-
id: number;
|
|
63
|
-
path: string;
|
|
64
|
-
reason: string | null;
|
|
65
|
-
expires_at: number;
|
|
66
|
-
}>;
|
|
67
|
-
recentEvents?: DebugEventResult[];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface DebugMessageOptions {
|
|
71
|
-
projectPath: string;
|
|
72
|
-
messageId: number;
|
|
73
|
-
includeEvents?: boolean;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface DebugMessageResult {
|
|
77
|
-
message: {
|
|
78
|
-
id: number;
|
|
79
|
-
from_agent: string;
|
|
80
|
-
subject: string;
|
|
81
|
-
body: string;
|
|
82
|
-
thread_id: string | null;
|
|
83
|
-
importance: string;
|
|
84
|
-
created_at: number;
|
|
85
|
-
} | null;
|
|
86
|
-
recipients: Array<{
|
|
87
|
-
agent_name: string;
|
|
88
|
-
read_at: number | null;
|
|
89
|
-
acked_at: number | null;
|
|
90
|
-
}>;
|
|
91
|
-
events?: DebugEventResult[];
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export interface DebugReservationsOptions {
|
|
95
|
-
projectPath: string;
|
|
96
|
-
checkConflicts?: boolean;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface DebugReservationsResult {
|
|
100
|
-
reservations: Array<{
|
|
101
|
-
id: number;
|
|
102
|
-
agent_name: string;
|
|
103
|
-
path_pattern: string;
|
|
104
|
-
reason: string | null;
|
|
105
|
-
expires_at: number;
|
|
106
|
-
expires_in_human: string;
|
|
107
|
-
}>;
|
|
108
|
-
byAgent: Record<string, Array<{ path: string; expires_at: number }>>;
|
|
109
|
-
conflicts?: Array<{
|
|
110
|
-
path1: string;
|
|
111
|
-
agent1: string;
|
|
112
|
-
path2: string;
|
|
113
|
-
agent2: string;
|
|
114
|
-
}>;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export interface TimelineEntry {
|
|
118
|
-
time: string;
|
|
119
|
-
type: AgentEvent["type"];
|
|
120
|
-
summary: string;
|
|
121
|
-
agent: string;
|
|
122
|
-
sequence: number;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export interface TimelineResult {
|
|
126
|
-
timeline: TimelineEntry[];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export interface InspectStateOptions {
|
|
130
|
-
projectPath: string;
|
|
131
|
-
format?: "object" | "json";
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export interface InspectStateResult {
|
|
135
|
-
agents: Array<{
|
|
136
|
-
name: string;
|
|
137
|
-
program: string;
|
|
138
|
-
model: string;
|
|
139
|
-
task_description: string | null;
|
|
140
|
-
}>;
|
|
141
|
-
messages: Array<{
|
|
142
|
-
id: number;
|
|
143
|
-
from_agent: string;
|
|
144
|
-
subject: string;
|
|
145
|
-
thread_id: string | null;
|
|
146
|
-
}>;
|
|
147
|
-
reservations: Array<{
|
|
148
|
-
id: number;
|
|
149
|
-
agent_name: string;
|
|
150
|
-
path_pattern: string;
|
|
151
|
-
}>;
|
|
152
|
-
eventCount: number;
|
|
153
|
-
latestSequence: number;
|
|
154
|
-
stats: {
|
|
155
|
-
events: number;
|
|
156
|
-
agents: number;
|
|
157
|
-
messages: number;
|
|
158
|
-
reservations: number;
|
|
159
|
-
};
|
|
160
|
-
json?: string;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ============================================================================
|
|
164
|
-
// Helper Functions
|
|
165
|
-
// ============================================================================
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Format timestamp as human-readable ISO string
|
|
169
|
-
*/
|
|
170
|
-
function formatTimestamp(timestamp: number): string {
|
|
171
|
-
return new Date(timestamp).toISOString();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Format duration as human-readable string
|
|
176
|
-
*/
|
|
177
|
-
function formatDuration(ms: number): string {
|
|
178
|
-
if (ms < 0) return "expired";
|
|
179
|
-
if (ms < 60000) return `${Math.round(ms / 1000)}s`;
|
|
180
|
-
if (ms < 3600000) return `${Math.round(ms / 60000)}m`;
|
|
181
|
-
if (ms < 86400000) return `${Math.round(ms / 3600000)}h`;
|
|
182
|
-
return `${Math.round(ms / 86400000)}d`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Generate event summary for timeline
|
|
187
|
-
*/
|
|
188
|
-
function summarizeEvent(
|
|
189
|
-
event: AgentEvent & { id: number; sequence: number },
|
|
190
|
-
): string {
|
|
191
|
-
switch (event.type) {
|
|
192
|
-
case "agent_registered":
|
|
193
|
-
return `Agent ${event.agent_name} registered (${event.program}/${event.model})`;
|
|
194
|
-
case "agent_active":
|
|
195
|
-
return `Agent ${event.agent_name} active`;
|
|
196
|
-
case "message_sent":
|
|
197
|
-
return `${event.from_agent} → ${event.to_agents.join(", ")}: "${event.subject}"`;
|
|
198
|
-
case "message_read":
|
|
199
|
-
return `${event.agent_name} read message #${event.message_id}`;
|
|
200
|
-
case "message_acked":
|
|
201
|
-
return `${event.agent_name} acked message #${event.message_id}`;
|
|
202
|
-
case "file_reserved":
|
|
203
|
-
return `${event.agent_name} reserved ${event.paths.length} file(s)`;
|
|
204
|
-
case "file_released":
|
|
205
|
-
return `${event.agent_name} released files`;
|
|
206
|
-
case "task_started":
|
|
207
|
-
return `${event.agent_name} started task: ${event.bead_id}`;
|
|
208
|
-
case "task_progress":
|
|
209
|
-
return `${event.agent_name} progress on ${event.bead_id}: ${event.progress_percent}%`;
|
|
210
|
-
case "task_completed":
|
|
211
|
-
return `${event.agent_name} completed ${event.bead_id}`;
|
|
212
|
-
case "task_blocked":
|
|
213
|
-
return `${event.agent_name} blocked on ${event.bead_id}: ${event.reason}`;
|
|
214
|
-
default:
|
|
215
|
-
return `Unknown event type`;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Get agent name from event (handles different event types)
|
|
221
|
-
*/
|
|
222
|
-
function getAgentFromEvent(event: AgentEvent): string {
|
|
223
|
-
if ("agent_name" in event && event.agent_name) return event.agent_name;
|
|
224
|
-
if ("from_agent" in event && event.from_agent) return event.from_agent;
|
|
225
|
-
return "unknown";
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// ============================================================================
|
|
229
|
-
// Debug Functions
|
|
230
|
-
// ============================================================================
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Get recent events with filtering
|
|
234
|
-
*
|
|
235
|
-
* For large event logs (>100k events), consider using batchSize option
|
|
236
|
-
* to paginate through results instead of loading all events.
|
|
237
|
-
*/
|
|
238
|
-
export async function debugEvents(
|
|
239
|
-
options: DebugEventsOptions & { batchSize?: number },
|
|
240
|
-
): Promise<DebugEventsResult> {
|
|
241
|
-
const {
|
|
242
|
-
projectPath,
|
|
243
|
-
types,
|
|
244
|
-
agentName,
|
|
245
|
-
limit = 50,
|
|
246
|
-
since,
|
|
247
|
-
until,
|
|
248
|
-
batchSize,
|
|
249
|
-
} = options;
|
|
250
|
-
|
|
251
|
-
// If batchSize is specified, use pagination to avoid OOM
|
|
252
|
-
if (batchSize && batchSize > 0) {
|
|
253
|
-
return await debugEventsPaginated({ ...options, batchSize });
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Get all events first (we'll filter in memory for agent name)
|
|
257
|
-
const allEvents = await readEvents(
|
|
258
|
-
{
|
|
259
|
-
projectKey: projectPath,
|
|
260
|
-
types,
|
|
261
|
-
since,
|
|
262
|
-
until,
|
|
263
|
-
},
|
|
264
|
-
projectPath,
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
// Filter by agent name if specified
|
|
268
|
-
let filteredEvents = allEvents;
|
|
269
|
-
if (agentName) {
|
|
270
|
-
filteredEvents = allEvents.filter((e) => {
|
|
271
|
-
if ("agent_name" in e && e.agent_name === agentName) return true;
|
|
272
|
-
if ("from_agent" in e && e.from_agent === agentName) return true;
|
|
273
|
-
if ("to_agents" in e && e.to_agents?.includes(agentName)) return true;
|
|
274
|
-
return false;
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Sort by sequence descending (most recent first)
|
|
279
|
-
filteredEvents.sort((a, b) => b.sequence - a.sequence);
|
|
280
|
-
|
|
281
|
-
// Apply limit
|
|
282
|
-
const limitedEvents = filteredEvents.slice(0, limit);
|
|
283
|
-
|
|
284
|
-
// Format for output - extract known fields, spread rest
|
|
285
|
-
const events: DebugEventResult[] = limitedEvents.map((e) => {
|
|
286
|
-
const { id, sequence, type, timestamp, project_key, ...rest } = e;
|
|
287
|
-
return {
|
|
288
|
-
id,
|
|
289
|
-
sequence,
|
|
290
|
-
type,
|
|
291
|
-
timestamp,
|
|
292
|
-
timestamp_human: formatTimestamp(timestamp),
|
|
293
|
-
...rest,
|
|
294
|
-
};
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
return {
|
|
298
|
-
events,
|
|
299
|
-
total: filteredEvents.length,
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Get events using pagination to avoid OOM on large logs
|
|
305
|
-
*/
|
|
306
|
-
async function debugEventsPaginated(
|
|
307
|
-
options: DebugEventsOptions & { batchSize: number },
|
|
308
|
-
): Promise<DebugEventsResult> {
|
|
309
|
-
const {
|
|
310
|
-
projectPath,
|
|
311
|
-
types,
|
|
312
|
-
agentName,
|
|
313
|
-
limit = 50,
|
|
314
|
-
since,
|
|
315
|
-
until,
|
|
316
|
-
batchSize,
|
|
317
|
-
} = options;
|
|
318
|
-
|
|
319
|
-
const allEvents: Array<AgentEvent & { id: number; sequence: number }> = [];
|
|
320
|
-
let offset = 0;
|
|
321
|
-
let hasMore = true;
|
|
322
|
-
|
|
323
|
-
// Fetch in batches until we have enough events or run out
|
|
324
|
-
while (hasMore && allEvents.length < limit) {
|
|
325
|
-
const batch = await readEvents(
|
|
326
|
-
{
|
|
327
|
-
projectKey: projectPath,
|
|
328
|
-
types,
|
|
329
|
-
since,
|
|
330
|
-
until,
|
|
331
|
-
limit: batchSize,
|
|
332
|
-
offset,
|
|
333
|
-
},
|
|
334
|
-
projectPath,
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
if (batch.length === 0) {
|
|
338
|
-
hasMore = false;
|
|
339
|
-
break;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Filter by agent name if specified
|
|
343
|
-
const filtered = agentName
|
|
344
|
-
? batch.filter((e) => {
|
|
345
|
-
if ("agent_name" in e && e.agent_name === agentName) return true;
|
|
346
|
-
if ("from_agent" in e && e.from_agent === agentName) return true;
|
|
347
|
-
if ("to_agents" in e && e.to_agents?.includes(agentName)) return true;
|
|
348
|
-
return false;
|
|
349
|
-
})
|
|
350
|
-
: batch;
|
|
351
|
-
|
|
352
|
-
allEvents.push(...filtered);
|
|
353
|
-
offset += batchSize;
|
|
354
|
-
|
|
355
|
-
console.log(
|
|
356
|
-
`[SwarmMail] Fetched ${allEvents.length} events (batch size: ${batchSize})`,
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Sort by sequence descending (most recent first)
|
|
361
|
-
allEvents.sort((a, b) => b.sequence - a.sequence);
|
|
362
|
-
|
|
363
|
-
// Apply limit
|
|
364
|
-
const limitedEvents = allEvents.slice(0, limit);
|
|
365
|
-
|
|
366
|
-
// Format for output
|
|
367
|
-
const events: DebugEventResult[] = limitedEvents.map((e) => {
|
|
368
|
-
const { id, sequence, type, timestamp, project_key, ...rest } = e;
|
|
369
|
-
return {
|
|
370
|
-
id,
|
|
371
|
-
sequence,
|
|
372
|
-
type,
|
|
373
|
-
timestamp,
|
|
374
|
-
timestamp_human: formatTimestamp(timestamp),
|
|
375
|
-
...rest,
|
|
376
|
-
};
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
return {
|
|
380
|
-
events,
|
|
381
|
-
total: allEvents.length,
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Get detailed agent information
|
|
387
|
-
*/
|
|
388
|
-
export async function debugAgent(
|
|
389
|
-
options: DebugAgentOptions,
|
|
390
|
-
): Promise<DebugAgentResult> {
|
|
391
|
-
const { projectPath, agentName, includeEvents = false } = options;
|
|
392
|
-
|
|
393
|
-
// Get agent from projections
|
|
394
|
-
const agent = await getAgent(projectPath, agentName, projectPath);
|
|
395
|
-
|
|
396
|
-
if (!agent) {
|
|
397
|
-
return {
|
|
398
|
-
agent: null,
|
|
399
|
-
stats: { messagesSent: 0, messagesReceived: 0 },
|
|
400
|
-
reservations: [],
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Get message counts
|
|
405
|
-
const db = await getDatabase(projectPath);
|
|
406
|
-
const sentResult = await db.query<{ count: string }>(
|
|
407
|
-
`SELECT COUNT(*) as count FROM messages WHERE project_key = $1 AND from_agent = $2`,
|
|
408
|
-
[projectPath, agentName],
|
|
409
|
-
);
|
|
410
|
-
const receivedResult = await db.query<{ count: string }>(
|
|
411
|
-
`SELECT COUNT(*) as count FROM message_recipients mr
|
|
412
|
-
JOIN messages m ON mr.message_id = m.id
|
|
413
|
-
WHERE m.project_key = $1 AND mr.agent_name = $2`,
|
|
414
|
-
[projectPath, agentName],
|
|
415
|
-
);
|
|
416
|
-
|
|
417
|
-
// Get active reservations
|
|
418
|
-
const reservations = await getActiveReservations(
|
|
419
|
-
projectPath,
|
|
420
|
-
projectPath,
|
|
421
|
-
agentName,
|
|
422
|
-
);
|
|
423
|
-
|
|
424
|
-
const result: DebugAgentResult = {
|
|
425
|
-
agent: {
|
|
426
|
-
name: agent.name,
|
|
427
|
-
program: agent.program,
|
|
428
|
-
model: agent.model,
|
|
429
|
-
task_description: agent.task_description,
|
|
430
|
-
registered_at: agent.registered_at,
|
|
431
|
-
last_active_at: agent.last_active_at,
|
|
432
|
-
},
|
|
433
|
-
stats: {
|
|
434
|
-
messagesSent: parseInt(sentResult.rows[0]?.count || "0"),
|
|
435
|
-
messagesReceived: parseInt(receivedResult.rows[0]?.count || "0"),
|
|
436
|
-
},
|
|
437
|
-
reservations: reservations.map((r) => ({
|
|
438
|
-
id: r.id,
|
|
439
|
-
path: r.path_pattern,
|
|
440
|
-
reason: r.reason,
|
|
441
|
-
expires_at: r.expires_at,
|
|
442
|
-
})),
|
|
443
|
-
};
|
|
444
|
-
|
|
445
|
-
// Include recent events if requested
|
|
446
|
-
if (includeEvents) {
|
|
447
|
-
const eventsResult = await debugEvents({
|
|
448
|
-
projectPath,
|
|
449
|
-
agentName,
|
|
450
|
-
limit: 20,
|
|
451
|
-
});
|
|
452
|
-
result.recentEvents = eventsResult.events;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
return result;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Get detailed message information with audit trail
|
|
460
|
-
*/
|
|
461
|
-
export async function debugMessage(
|
|
462
|
-
options: DebugMessageOptions,
|
|
463
|
-
): Promise<DebugMessageResult> {
|
|
464
|
-
const { projectPath, messageId, includeEvents = false } = options;
|
|
465
|
-
|
|
466
|
-
// Get message from projections
|
|
467
|
-
const message = await getMessage(projectPath, messageId, projectPath);
|
|
468
|
-
|
|
469
|
-
if (!message) {
|
|
470
|
-
return {
|
|
471
|
-
message: null,
|
|
472
|
-
recipients: [],
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// Get recipients
|
|
477
|
-
const db = await getDatabase(projectPath);
|
|
478
|
-
const recipientsResult = await db.query<{
|
|
479
|
-
agent_name: string;
|
|
480
|
-
read_at: string | null;
|
|
481
|
-
acked_at: string | null;
|
|
482
|
-
}>(
|
|
483
|
-
`SELECT agent_name, read_at, acked_at FROM message_recipients WHERE message_id = $1`,
|
|
484
|
-
[messageId],
|
|
485
|
-
);
|
|
486
|
-
|
|
487
|
-
const result: DebugMessageResult = {
|
|
488
|
-
message: {
|
|
489
|
-
id: message.id,
|
|
490
|
-
from_agent: message.from_agent,
|
|
491
|
-
subject: message.subject,
|
|
492
|
-
body: message.body ?? "",
|
|
493
|
-
thread_id: message.thread_id,
|
|
494
|
-
importance: message.importance,
|
|
495
|
-
created_at: message.created_at,
|
|
496
|
-
},
|
|
497
|
-
recipients: recipientsResult.rows.map((r) => ({
|
|
498
|
-
agent_name: r.agent_name,
|
|
499
|
-
read_at: r.read_at ? parseInt(r.read_at) : null,
|
|
500
|
-
acked_at: r.acked_at ? parseInt(r.acked_at) : null,
|
|
501
|
-
})),
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
// Include related events if requested
|
|
505
|
-
if (includeEvents) {
|
|
506
|
-
const allEvents = await readEvents(
|
|
507
|
-
{ projectKey: projectPath },
|
|
508
|
-
projectPath,
|
|
509
|
-
);
|
|
510
|
-
const relatedEvents = allEvents.filter((e) => {
|
|
511
|
-
if (e.type === "message_sent" && e.subject === message.subject)
|
|
512
|
-
return true;
|
|
513
|
-
if (
|
|
514
|
-
(e.type === "message_read" || e.type === "message_acked") &&
|
|
515
|
-
e.message_id === messageId
|
|
516
|
-
)
|
|
517
|
-
return true;
|
|
518
|
-
return false;
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
result.events = relatedEvents.map((e) => {
|
|
522
|
-
const { id, sequence, type, timestamp, project_key, ...rest } = e;
|
|
523
|
-
return {
|
|
524
|
-
id,
|
|
525
|
-
sequence,
|
|
526
|
-
type,
|
|
527
|
-
timestamp,
|
|
528
|
-
timestamp_human: formatTimestamp(timestamp),
|
|
529
|
-
...rest,
|
|
530
|
-
};
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
return result;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* Get current reservation state
|
|
539
|
-
*/
|
|
540
|
-
export async function debugReservations(
|
|
541
|
-
options: DebugReservationsOptions,
|
|
542
|
-
): Promise<DebugReservationsResult> {
|
|
543
|
-
const { projectPath, checkConflicts = false } = options;
|
|
544
|
-
|
|
545
|
-
const reservations = await getActiveReservations(projectPath, projectPath);
|
|
546
|
-
const now = Date.now();
|
|
547
|
-
|
|
548
|
-
// Format reservations
|
|
549
|
-
const formattedReservations = reservations.map((r) => ({
|
|
550
|
-
id: r.id,
|
|
551
|
-
agent_name: r.agent_name,
|
|
552
|
-
path_pattern: r.path_pattern,
|
|
553
|
-
reason: r.reason,
|
|
554
|
-
expires_at: r.expires_at,
|
|
555
|
-
expires_in_human: formatDuration(r.expires_at - now),
|
|
556
|
-
}));
|
|
557
|
-
|
|
558
|
-
// Group by agent
|
|
559
|
-
const byAgent: Record<
|
|
560
|
-
string,
|
|
561
|
-
Array<{ path: string; expires_at: number }>
|
|
562
|
-
> = {};
|
|
563
|
-
for (const r of reservations) {
|
|
564
|
-
if (!byAgent[r.agent_name]) {
|
|
565
|
-
byAgent[r.agent_name] = [];
|
|
566
|
-
}
|
|
567
|
-
byAgent[r.agent_name].push({
|
|
568
|
-
path: r.path_pattern,
|
|
569
|
-
expires_at: r.expires_at,
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const result: DebugReservationsResult = {
|
|
574
|
-
reservations: formattedReservations,
|
|
575
|
-
byAgent,
|
|
576
|
-
};
|
|
577
|
-
|
|
578
|
-
// Check for conflicts if requested
|
|
579
|
-
if (checkConflicts) {
|
|
580
|
-
const conflicts: Array<{
|
|
581
|
-
path1: string;
|
|
582
|
-
agent1: string;
|
|
583
|
-
path2: string;
|
|
584
|
-
agent2: string;
|
|
585
|
-
}> = [];
|
|
586
|
-
|
|
587
|
-
// Simple overlap detection - check if any patterns might conflict
|
|
588
|
-
for (let i = 0; i < reservations.length; i++) {
|
|
589
|
-
for (let j = i + 1; j < reservations.length; j++) {
|
|
590
|
-
const r1 = reservations[i];
|
|
591
|
-
const r2 = reservations[j];
|
|
592
|
-
|
|
593
|
-
// Skip same agent
|
|
594
|
-
if (r1.agent_name === r2.agent_name) continue;
|
|
595
|
-
|
|
596
|
-
// Check for potential overlap (simple heuristic)
|
|
597
|
-
const p1 = r1.path_pattern;
|
|
598
|
-
const p2 = r2.path_pattern;
|
|
599
|
-
|
|
600
|
-
// Glob pattern might overlap with specific file
|
|
601
|
-
if (
|
|
602
|
-
p1.includes("**") &&
|
|
603
|
-
p2.startsWith(p1.replace("/**", "").replace("**", ""))
|
|
604
|
-
) {
|
|
605
|
-
conflicts.push({
|
|
606
|
-
path1: p1,
|
|
607
|
-
agent1: r1.agent_name,
|
|
608
|
-
path2: p2,
|
|
609
|
-
agent2: r2.agent_name,
|
|
610
|
-
});
|
|
611
|
-
} else if (
|
|
612
|
-
p2.includes("**") &&
|
|
613
|
-
p1.startsWith(p2.replace("/**", "").replace("**", ""))
|
|
614
|
-
) {
|
|
615
|
-
conflicts.push({
|
|
616
|
-
path1: p2,
|
|
617
|
-
agent1: r2.agent_name,
|
|
618
|
-
path2: p1,
|
|
619
|
-
agent2: r1.agent_name,
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
result.conflicts = conflicts;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
return result;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* Get event timeline for visualization
|
|
633
|
-
*/
|
|
634
|
-
export async function getEventTimeline(options: {
|
|
635
|
-
projectPath: string;
|
|
636
|
-
since?: number;
|
|
637
|
-
until?: number;
|
|
638
|
-
limit?: number;
|
|
639
|
-
}): Promise<TimelineResult> {
|
|
640
|
-
const { projectPath, since, until, limit = 100 } = options;
|
|
641
|
-
|
|
642
|
-
const events = await readEvents(
|
|
643
|
-
{
|
|
644
|
-
projectKey: projectPath,
|
|
645
|
-
since,
|
|
646
|
-
until,
|
|
647
|
-
limit,
|
|
648
|
-
},
|
|
649
|
-
projectPath,
|
|
650
|
-
);
|
|
651
|
-
|
|
652
|
-
// Sort by sequence ascending for timeline
|
|
653
|
-
events.sort((a, b) => a.sequence - b.sequence);
|
|
654
|
-
|
|
655
|
-
const timeline: TimelineEntry[] = events.map((e) => ({
|
|
656
|
-
time: formatTimestamp(e.timestamp),
|
|
657
|
-
type: e.type,
|
|
658
|
-
summary: summarizeEvent(e),
|
|
659
|
-
agent: getAgentFromEvent(e),
|
|
660
|
-
sequence: e.sequence,
|
|
661
|
-
}));
|
|
662
|
-
|
|
663
|
-
return { timeline };
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/**
|
|
667
|
-
* Get complete state snapshot for debugging
|
|
668
|
-
*/
|
|
669
|
-
export async function inspectState(
|
|
670
|
-
options: InspectStateOptions,
|
|
671
|
-
): Promise<InspectStateResult> {
|
|
672
|
-
const { projectPath, format = "object" } = options;
|
|
673
|
-
|
|
674
|
-
const db = await getDatabase(projectPath);
|
|
675
|
-
|
|
676
|
-
// Get all agents
|
|
677
|
-
const agentsResult = await db.query<{
|
|
678
|
-
name: string;
|
|
679
|
-
program: string;
|
|
680
|
-
model: string;
|
|
681
|
-
task_description: string | null;
|
|
682
|
-
}>(
|
|
683
|
-
`SELECT name, program, model, task_description FROM agents WHERE project_key = $1`,
|
|
684
|
-
[projectPath],
|
|
685
|
-
);
|
|
686
|
-
|
|
687
|
-
// Get all messages
|
|
688
|
-
const messagesResult = await db.query<{
|
|
689
|
-
id: number;
|
|
690
|
-
from_agent: string;
|
|
691
|
-
subject: string;
|
|
692
|
-
thread_id: string | null;
|
|
693
|
-
}>(
|
|
694
|
-
`SELECT id, from_agent, subject, thread_id FROM messages WHERE project_key = $1`,
|
|
695
|
-
[projectPath],
|
|
696
|
-
);
|
|
697
|
-
|
|
698
|
-
// Get active reservations
|
|
699
|
-
const reservationsResult = await db.query<{
|
|
700
|
-
id: number;
|
|
701
|
-
agent_name: string;
|
|
702
|
-
path_pattern: string;
|
|
703
|
-
}>(
|
|
704
|
-
`SELECT id, agent_name, path_pattern FROM reservations
|
|
705
|
-
WHERE project_key = $1 AND released_at IS NULL AND expires_at > $2`,
|
|
706
|
-
[projectPath, Date.now()],
|
|
707
|
-
);
|
|
708
|
-
|
|
709
|
-
// Get stats
|
|
710
|
-
const stats = await getDatabaseStats(projectPath);
|
|
711
|
-
const latestSequence = await getLatestSequence(projectPath, projectPath);
|
|
712
|
-
|
|
713
|
-
const result: InspectStateResult = {
|
|
714
|
-
agents: agentsResult.rows,
|
|
715
|
-
messages: messagesResult.rows,
|
|
716
|
-
reservations: reservationsResult.rows,
|
|
717
|
-
eventCount: stats.events,
|
|
718
|
-
latestSequence,
|
|
719
|
-
stats,
|
|
720
|
-
};
|
|
721
|
-
|
|
722
|
-
if (format === "json") {
|
|
723
|
-
result.json = JSON.stringify(result, null, 2);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
return result;
|
|
727
|
-
}
|