agentation-vue-mcp 0.0.2
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/LICENSE +27 -0
- package/README.md +171 -0
- package/dist/cli.js +2952 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.mts +341 -0
- package/dist/index.d.ts +341 -0
- package/dist/index.js +2817 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2761 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +60 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,2952 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
32
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
|
+
|
|
34
|
+
// src/server/mcp.ts
|
|
35
|
+
function setHttpBaseUrl(url) {
|
|
36
|
+
httpBaseUrl = url;
|
|
37
|
+
}
|
|
38
|
+
function setApiKey(key) {
|
|
39
|
+
apiKey = key;
|
|
40
|
+
}
|
|
41
|
+
async function httpGet(path2) {
|
|
42
|
+
const headers = {};
|
|
43
|
+
if (apiKey) {
|
|
44
|
+
headers["x-api-key"] = apiKey;
|
|
45
|
+
}
|
|
46
|
+
const res = await fetch(`${httpBaseUrl}${path2}`, { headers });
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
const body = await res.text();
|
|
49
|
+
throw new Error(`HTTP ${res.status}: ${body}`);
|
|
50
|
+
}
|
|
51
|
+
return res.json();
|
|
52
|
+
}
|
|
53
|
+
async function httpPatch(path2, body) {
|
|
54
|
+
const headers = { "Content-Type": "application/json" };
|
|
55
|
+
if (apiKey) {
|
|
56
|
+
headers["x-api-key"] = apiKey;
|
|
57
|
+
}
|
|
58
|
+
const res = await fetch(`${httpBaseUrl}${path2}`, {
|
|
59
|
+
method: "PATCH",
|
|
60
|
+
headers,
|
|
61
|
+
body: JSON.stringify(body)
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
const text = await res.text();
|
|
65
|
+
throw new Error(`HTTP ${res.status}: ${text}`);
|
|
66
|
+
}
|
|
67
|
+
return res.json();
|
|
68
|
+
}
|
|
69
|
+
async function httpPost(path2, body) {
|
|
70
|
+
const headers = { "Content-Type": "application/json" };
|
|
71
|
+
if (apiKey) {
|
|
72
|
+
headers["x-api-key"] = apiKey;
|
|
73
|
+
}
|
|
74
|
+
const res = await fetch(`${httpBaseUrl}${path2}`, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers,
|
|
77
|
+
body: JSON.stringify(body)
|
|
78
|
+
});
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
const text = await res.text();
|
|
81
|
+
throw new Error(`HTTP ${res.status}: ${text}`);
|
|
82
|
+
}
|
|
83
|
+
return res.json();
|
|
84
|
+
}
|
|
85
|
+
function success(data) {
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function error(message) {
|
|
91
|
+
return {
|
|
92
|
+
content: [{ type: "text", text: message }],
|
|
93
|
+
isError: true
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function watchForAnnotations(sessionId, batchWindowMs, timeoutMs) {
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
let aborted = false;
|
|
99
|
+
const controller = new AbortController();
|
|
100
|
+
let batchTimeout = null;
|
|
101
|
+
const detectedSessions = /* @__PURE__ */ new Set();
|
|
102
|
+
const collectedAnnotations = [];
|
|
103
|
+
const cleanup = () => {
|
|
104
|
+
aborted = true;
|
|
105
|
+
controller.abort();
|
|
106
|
+
if (batchTimeout) clearTimeout(batchTimeout);
|
|
107
|
+
};
|
|
108
|
+
const timeoutId = setTimeout(() => {
|
|
109
|
+
cleanup();
|
|
110
|
+
resolve({ type: "timeout" });
|
|
111
|
+
}, timeoutMs);
|
|
112
|
+
const sseUrl = sessionId ? `${httpBaseUrl}/sessions/${sessionId}/events?agent=true` : `${httpBaseUrl}/events?agent=true`;
|
|
113
|
+
const sseHeaders = { Accept: "text/event-stream" };
|
|
114
|
+
if (apiKey) {
|
|
115
|
+
sseHeaders["x-api-key"] = apiKey;
|
|
116
|
+
}
|
|
117
|
+
fetch(sseUrl, {
|
|
118
|
+
signal: controller.signal,
|
|
119
|
+
headers: sseHeaders
|
|
120
|
+
}).then(async (res) => {
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
clearTimeout(timeoutId);
|
|
123
|
+
cleanup();
|
|
124
|
+
resolve({ type: "error", message: `HTTP server returned ${res.status}: ${res.statusText}` });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (!res.body) {
|
|
128
|
+
clearTimeout(timeoutId);
|
|
129
|
+
cleanup();
|
|
130
|
+
resolve({ type: "error", message: "No response body from SSE endpoint" });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const reader = res.body.getReader();
|
|
134
|
+
const decoder = new TextDecoder();
|
|
135
|
+
let buffer = "";
|
|
136
|
+
while (!aborted) {
|
|
137
|
+
const { done, value } = await reader.read();
|
|
138
|
+
if (done) {
|
|
139
|
+
if (!aborted) {
|
|
140
|
+
clearTimeout(timeoutId);
|
|
141
|
+
cleanup();
|
|
142
|
+
if (collectedAnnotations.length > 0) {
|
|
143
|
+
resolve({
|
|
144
|
+
type: "annotations",
|
|
145
|
+
annotations: collectedAnnotations,
|
|
146
|
+
sessions: Array.from(detectedSessions)
|
|
147
|
+
});
|
|
148
|
+
} else {
|
|
149
|
+
resolve({ type: "error", message: "SSE connection closed unexpectedly. The agentation server may have restarted." });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
buffer += decoder.decode(value, { stream: true });
|
|
155
|
+
const lines = buffer.split("\n");
|
|
156
|
+
buffer = lines.pop() || "";
|
|
157
|
+
for (const line of lines) {
|
|
158
|
+
if (line.startsWith("data: ")) {
|
|
159
|
+
try {
|
|
160
|
+
const event = JSON.parse(line.slice(6));
|
|
161
|
+
if (event.type === "annotation.created") {
|
|
162
|
+
if (event.sequence === 0) continue;
|
|
163
|
+
if (sessionId && event.sessionId !== sessionId) continue;
|
|
164
|
+
detectedSessions.add(event.sessionId);
|
|
165
|
+
collectedAnnotations.push(event.payload);
|
|
166
|
+
if (!batchTimeout) {
|
|
167
|
+
batchTimeout = setTimeout(() => {
|
|
168
|
+
clearTimeout(timeoutId);
|
|
169
|
+
cleanup();
|
|
170
|
+
resolve({
|
|
171
|
+
type: "annotations",
|
|
172
|
+
annotations: collectedAnnotations,
|
|
173
|
+
sessions: Array.from(detectedSessions)
|
|
174
|
+
});
|
|
175
|
+
}, batchWindowMs);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}).catch((err) => {
|
|
184
|
+
if (!aborted) {
|
|
185
|
+
clearTimeout(timeoutId);
|
|
186
|
+
const message = err instanceof Error ? err.message : "Unknown connection error";
|
|
187
|
+
if (message.includes("ECONNREFUSED") || message.includes("fetch failed")) {
|
|
188
|
+
resolve({ type: "error", message: `Cannot connect to HTTP server at ${httpBaseUrl}. Is the agentation server running?` });
|
|
189
|
+
} else if (message.includes("abort")) {
|
|
190
|
+
resolve({ type: "timeout" });
|
|
191
|
+
} else {
|
|
192
|
+
resolve({ type: "error", message: `Connection error: ${message}` });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
async function handleTool(name, args) {
|
|
199
|
+
switch (name) {
|
|
200
|
+
case "agentation_list_sessions": {
|
|
201
|
+
const sessions = await httpGet("/sessions");
|
|
202
|
+
return success({
|
|
203
|
+
sessions: sessions.map((s) => ({
|
|
204
|
+
id: s.id,
|
|
205
|
+
url: s.url,
|
|
206
|
+
status: s.status,
|
|
207
|
+
createdAt: s.createdAt
|
|
208
|
+
}))
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
case "agentation_get_session": {
|
|
212
|
+
const { sessionId } = GetSessionSchema.parse(args);
|
|
213
|
+
try {
|
|
214
|
+
const session = await httpGet(`/sessions/${sessionId}`);
|
|
215
|
+
return success(session);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
if (err.message.includes("404")) {
|
|
218
|
+
return error(`Session not found: ${sessionId}`);
|
|
219
|
+
}
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
case "agentation_get_pending": {
|
|
224
|
+
const { sessionId } = GetPendingSchema.parse(args);
|
|
225
|
+
const response = await httpGet(`/sessions/${sessionId}/pending`);
|
|
226
|
+
return success({
|
|
227
|
+
count: response.count,
|
|
228
|
+
annotations: response.annotations.map((a) => ({
|
|
229
|
+
id: a.id,
|
|
230
|
+
comment: a.comment,
|
|
231
|
+
element: a.element,
|
|
232
|
+
elementPath: a.elementPath,
|
|
233
|
+
url: a.url,
|
|
234
|
+
intent: a.intent,
|
|
235
|
+
severity: a.severity,
|
|
236
|
+
timestamp: a.timestamp,
|
|
237
|
+
nearbyText: a.nearbyText,
|
|
238
|
+
reactComponents: a.reactComponents
|
|
239
|
+
}))
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
case "agentation_get_all_pending": {
|
|
243
|
+
const response = await httpGet("/pending");
|
|
244
|
+
return success({
|
|
245
|
+
count: response.count,
|
|
246
|
+
annotations: response.annotations.map((a) => ({
|
|
247
|
+
id: a.id,
|
|
248
|
+
comment: a.comment,
|
|
249
|
+
element: a.element,
|
|
250
|
+
elementPath: a.elementPath,
|
|
251
|
+
url: a.url,
|
|
252
|
+
intent: a.intent,
|
|
253
|
+
severity: a.severity,
|
|
254
|
+
timestamp: a.timestamp,
|
|
255
|
+
nearbyText: a.nearbyText,
|
|
256
|
+
reactComponents: a.reactComponents
|
|
257
|
+
}))
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
case "agentation_acknowledge": {
|
|
261
|
+
const { annotationId } = AcknowledgeSchema.parse(args);
|
|
262
|
+
try {
|
|
263
|
+
await httpPatch(`/annotations/${annotationId}`, { status: "acknowledged" });
|
|
264
|
+
return success({ acknowledged: true, annotationId });
|
|
265
|
+
} catch (err) {
|
|
266
|
+
if (err.message.includes("404")) {
|
|
267
|
+
return error(`Annotation not found: ${annotationId}`);
|
|
268
|
+
}
|
|
269
|
+
throw err;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
case "agentation_resolve": {
|
|
273
|
+
const { annotationId, summary } = ResolveSchema.parse(args);
|
|
274
|
+
try {
|
|
275
|
+
await httpPatch(`/annotations/${annotationId}`, {
|
|
276
|
+
status: "resolved",
|
|
277
|
+
resolvedBy: "agent"
|
|
278
|
+
});
|
|
279
|
+
if (summary) {
|
|
280
|
+
await httpPost(`/annotations/${annotationId}/thread`, {
|
|
281
|
+
role: "agent",
|
|
282
|
+
content: `Resolved: ${summary}`
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return success({ resolved: true, annotationId, summary });
|
|
286
|
+
} catch (err) {
|
|
287
|
+
if (err.message.includes("404")) {
|
|
288
|
+
return error(`Annotation not found: ${annotationId}`);
|
|
289
|
+
}
|
|
290
|
+
throw err;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
case "agentation_dismiss": {
|
|
294
|
+
const { annotationId, reason } = DismissSchema.parse(args);
|
|
295
|
+
try {
|
|
296
|
+
await httpPatch(`/annotations/${annotationId}`, {
|
|
297
|
+
status: "dismissed",
|
|
298
|
+
resolvedBy: "agent"
|
|
299
|
+
});
|
|
300
|
+
await httpPost(`/annotations/${annotationId}/thread`, {
|
|
301
|
+
role: "agent",
|
|
302
|
+
content: `Dismissed: ${reason}`
|
|
303
|
+
});
|
|
304
|
+
return success({ dismissed: true, annotationId, reason });
|
|
305
|
+
} catch (err) {
|
|
306
|
+
if (err.message.includes("404")) {
|
|
307
|
+
return error(`Annotation not found: ${annotationId}`);
|
|
308
|
+
}
|
|
309
|
+
throw err;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
case "agentation_reply": {
|
|
313
|
+
const { annotationId, message } = ReplySchema.parse(args);
|
|
314
|
+
try {
|
|
315
|
+
await httpPost(`/annotations/${annotationId}/thread`, {
|
|
316
|
+
role: "agent",
|
|
317
|
+
content: message
|
|
318
|
+
});
|
|
319
|
+
return success({ replied: true, annotationId, message });
|
|
320
|
+
} catch (err) {
|
|
321
|
+
if (err.message.includes("404")) {
|
|
322
|
+
return error(`Annotation not found: ${annotationId}`);
|
|
323
|
+
}
|
|
324
|
+
throw err;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
case "agentation_watch_annotations": {
|
|
328
|
+
const parsed = WatchAnnotationsSchema.parse(args);
|
|
329
|
+
const sessionId = parsed.sessionId;
|
|
330
|
+
const batchWindowSeconds = Math.min(60, Math.max(1, parsed.batchWindowSeconds ?? 10));
|
|
331
|
+
const timeoutSeconds = Math.min(300, Math.max(1, parsed.timeoutSeconds ?? 120));
|
|
332
|
+
try {
|
|
333
|
+
const pendingPath = sessionId ? `/sessions/${sessionId}/pending` : "/pending";
|
|
334
|
+
const pending = await httpGet(pendingPath);
|
|
335
|
+
if (pending.count > 0) {
|
|
336
|
+
const sessions = [...new Set(pending.annotations.map((a) => a.sessionId))];
|
|
337
|
+
return success({
|
|
338
|
+
timeout: false,
|
|
339
|
+
count: pending.count,
|
|
340
|
+
sessions,
|
|
341
|
+
annotations: pending.annotations.map((a) => ({
|
|
342
|
+
id: a.id,
|
|
343
|
+
comment: a.comment,
|
|
344
|
+
element: a.element,
|
|
345
|
+
elementPath: a.elementPath,
|
|
346
|
+
url: a.url,
|
|
347
|
+
intent: a.intent,
|
|
348
|
+
severity: a.severity,
|
|
349
|
+
timestamp: a.timestamp,
|
|
350
|
+
nearbyText: a.nearbyText,
|
|
351
|
+
reactComponents: a.reactComponents
|
|
352
|
+
}))
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
} catch (err) {
|
|
356
|
+
console.error("[MCP] Pending drain failed, falling through to SSE watch:", err);
|
|
357
|
+
}
|
|
358
|
+
const result = await watchForAnnotations(
|
|
359
|
+
sessionId,
|
|
360
|
+
batchWindowSeconds * 1e3,
|
|
361
|
+
timeoutSeconds * 1e3
|
|
362
|
+
);
|
|
363
|
+
switch (result.type) {
|
|
364
|
+
case "annotations":
|
|
365
|
+
return success({
|
|
366
|
+
timeout: false,
|
|
367
|
+
count: result.annotations.length,
|
|
368
|
+
sessions: result.sessions,
|
|
369
|
+
annotations: result.annotations.map((a) => ({
|
|
370
|
+
id: a.id,
|
|
371
|
+
comment: a.comment,
|
|
372
|
+
element: a.element,
|
|
373
|
+
elementPath: a.elementPath,
|
|
374
|
+
url: a.url,
|
|
375
|
+
intent: a.intent,
|
|
376
|
+
severity: a.severity,
|
|
377
|
+
timestamp: a.timestamp,
|
|
378
|
+
nearbyText: a.nearbyText,
|
|
379
|
+
reactComponents: a.reactComponents
|
|
380
|
+
}))
|
|
381
|
+
});
|
|
382
|
+
case "timeout":
|
|
383
|
+
return success({
|
|
384
|
+
timeout: true,
|
|
385
|
+
message: `No new annotations within ${timeoutSeconds} seconds`
|
|
386
|
+
});
|
|
387
|
+
case "error":
|
|
388
|
+
return error(result.message);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
default:
|
|
392
|
+
return error(`Unknown tool: ${name}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async function startMcpServer(baseUrl) {
|
|
396
|
+
if (baseUrl) {
|
|
397
|
+
setHttpBaseUrl(baseUrl);
|
|
398
|
+
}
|
|
399
|
+
const server = new import_server.Server(
|
|
400
|
+
{
|
|
401
|
+
name: "agentation",
|
|
402
|
+
version: "0.0.1"
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
capabilities: {
|
|
406
|
+
tools: {}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
);
|
|
410
|
+
server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
|
|
411
|
+
return { tools: TOOLS };
|
|
412
|
+
});
|
|
413
|
+
server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
|
|
414
|
+
const { name, arguments: args } = request.params;
|
|
415
|
+
try {
|
|
416
|
+
return await handleTool(name, args);
|
|
417
|
+
} catch (err) {
|
|
418
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
419
|
+
return error(message);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
423
|
+
await server.connect(transport);
|
|
424
|
+
const isRemote = httpBaseUrl.startsWith("https://") || !httpBaseUrl.includes("localhost") && !httpBaseUrl.includes("127.0.0.1");
|
|
425
|
+
if (isRemote && apiKey) {
|
|
426
|
+
console.error(`[MCP] Agentation MCP server started on stdio (Remote: ${httpBaseUrl}, API key: configured)`);
|
|
427
|
+
} else if (isRemote) {
|
|
428
|
+
console.error(`[MCP] Agentation MCP server started on stdio (Remote: ${httpBaseUrl}, API key: not configured)`);
|
|
429
|
+
} else {
|
|
430
|
+
console.error(`[MCP] Agentation MCP server started on stdio (HTTP: ${httpBaseUrl})`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
var import_server, import_stdio, import_types, import_zod, httpBaseUrl, apiKey, GetPendingSchema, AcknowledgeSchema, ResolveSchema, DismissSchema, ReplySchema, GetSessionSchema, WatchAnnotationsSchema, TOOLS;
|
|
434
|
+
var init_mcp = __esm({
|
|
435
|
+
"src/server/mcp.ts"() {
|
|
436
|
+
"use strict";
|
|
437
|
+
import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
438
|
+
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
439
|
+
import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
440
|
+
import_zod = require("zod");
|
|
441
|
+
httpBaseUrl = "http://localhost:4747";
|
|
442
|
+
GetPendingSchema = import_zod.z.object({
|
|
443
|
+
sessionId: import_zod.z.string().describe("The session ID to get pending annotations for")
|
|
444
|
+
});
|
|
445
|
+
AcknowledgeSchema = import_zod.z.object({
|
|
446
|
+
annotationId: import_zod.z.string().describe("The annotation ID to acknowledge")
|
|
447
|
+
});
|
|
448
|
+
ResolveSchema = import_zod.z.object({
|
|
449
|
+
annotationId: import_zod.z.string().describe("The annotation ID to resolve"),
|
|
450
|
+
summary: import_zod.z.string().optional().describe("Optional summary of how it was resolved")
|
|
451
|
+
});
|
|
452
|
+
DismissSchema = import_zod.z.object({
|
|
453
|
+
annotationId: import_zod.z.string().describe("The annotation ID to dismiss"),
|
|
454
|
+
reason: import_zod.z.string().describe("Reason for dismissing this annotation")
|
|
455
|
+
});
|
|
456
|
+
ReplySchema = import_zod.z.object({
|
|
457
|
+
annotationId: import_zod.z.string().describe("The annotation ID to reply to"),
|
|
458
|
+
message: import_zod.z.string().describe("The reply message")
|
|
459
|
+
});
|
|
460
|
+
GetSessionSchema = import_zod.z.object({
|
|
461
|
+
sessionId: import_zod.z.string().describe("The session ID to get")
|
|
462
|
+
});
|
|
463
|
+
WatchAnnotationsSchema = import_zod.z.object({
|
|
464
|
+
sessionId: import_zod.z.string().optional().describe("Optional session ID to filter. If not provided, watches ALL sessions."),
|
|
465
|
+
batchWindowSeconds: import_zod.z.number().optional().default(10).describe("Seconds to wait after first annotation before returning batch (default: 10, max: 60)"),
|
|
466
|
+
timeoutSeconds: import_zod.z.number().optional().default(120).describe("Max seconds to wait for first annotation (default: 120, max: 300)")
|
|
467
|
+
});
|
|
468
|
+
TOOLS = [
|
|
469
|
+
{
|
|
470
|
+
name: "agentation_list_sessions",
|
|
471
|
+
description: "List all active annotation sessions",
|
|
472
|
+
inputSchema: {
|
|
473
|
+
type: "object",
|
|
474
|
+
properties: {},
|
|
475
|
+
required: []
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: "agentation_get_session",
|
|
480
|
+
description: "Get a session with all its annotations",
|
|
481
|
+
inputSchema: {
|
|
482
|
+
type: "object",
|
|
483
|
+
properties: {
|
|
484
|
+
sessionId: {
|
|
485
|
+
type: "string",
|
|
486
|
+
description: "The session ID to get"
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
required: ["sessionId"]
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
name: "agentation_get_pending",
|
|
494
|
+
description: "Get all pending (unacknowledged) annotations for a session. Use this to see what feedback the human has given that needs attention.",
|
|
495
|
+
inputSchema: {
|
|
496
|
+
type: "object",
|
|
497
|
+
properties: {
|
|
498
|
+
sessionId: {
|
|
499
|
+
type: "string",
|
|
500
|
+
description: "The session ID to get pending annotations for"
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
required: ["sessionId"]
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
name: "agentation_get_all_pending",
|
|
508
|
+
description: "Get all pending annotations across ALL sessions. Use this to see all unaddressed feedback from the human across all pages they've visited.",
|
|
509
|
+
inputSchema: {
|
|
510
|
+
type: "object",
|
|
511
|
+
properties: {},
|
|
512
|
+
required: []
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
name: "agentation_acknowledge",
|
|
517
|
+
description: "Mark an annotation as acknowledged. Use this to let the human know you've seen their feedback and will address it.",
|
|
518
|
+
inputSchema: {
|
|
519
|
+
type: "object",
|
|
520
|
+
properties: {
|
|
521
|
+
annotationId: {
|
|
522
|
+
type: "string",
|
|
523
|
+
description: "The annotation ID to acknowledge"
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
required: ["annotationId"]
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: "agentation_resolve",
|
|
531
|
+
description: "Mark an annotation as resolved. Use this after you've addressed the feedback. Optionally include a summary of what you did.",
|
|
532
|
+
inputSchema: {
|
|
533
|
+
type: "object",
|
|
534
|
+
properties: {
|
|
535
|
+
annotationId: {
|
|
536
|
+
type: "string",
|
|
537
|
+
description: "The annotation ID to resolve"
|
|
538
|
+
},
|
|
539
|
+
summary: {
|
|
540
|
+
type: "string",
|
|
541
|
+
description: "Optional summary of how it was resolved"
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
required: ["annotationId"]
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: "agentation_dismiss",
|
|
549
|
+
description: "Dismiss an annotation. Use this when you've decided not to address the feedback, with a reason why.",
|
|
550
|
+
inputSchema: {
|
|
551
|
+
type: "object",
|
|
552
|
+
properties: {
|
|
553
|
+
annotationId: {
|
|
554
|
+
type: "string",
|
|
555
|
+
description: "The annotation ID to dismiss"
|
|
556
|
+
},
|
|
557
|
+
reason: {
|
|
558
|
+
type: "string",
|
|
559
|
+
description: "Reason for dismissing this annotation"
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
required: ["annotationId", "reason"]
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
name: "agentation_reply",
|
|
567
|
+
description: "Add a reply to an annotation's thread. Use this to ask clarifying questions or provide updates to the human.",
|
|
568
|
+
inputSchema: {
|
|
569
|
+
type: "object",
|
|
570
|
+
properties: {
|
|
571
|
+
annotationId: {
|
|
572
|
+
type: "string",
|
|
573
|
+
description: "The annotation ID to reply to"
|
|
574
|
+
},
|
|
575
|
+
message: {
|
|
576
|
+
type: "string",
|
|
577
|
+
description: "The reply message"
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
required: ["annotationId", "message"]
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: "agentation_watch_annotations",
|
|
585
|
+
description: "Block until new annotations appear, then collect a batch and return them. Triggers automatically when annotations are created \u2014 the user just annotates in the browser and the agent picks them up. After detecting the first new annotation, waits for a batch window to collect more before returning. Use in a loop for hands-free processing. After addressing each annotation, call agentation_resolve with the annotation ID and a summary of what you did. Only resolve annotations the user accepted \u2014 if the user rejects your change, leave the annotation open.",
|
|
586
|
+
inputSchema: {
|
|
587
|
+
type: "object",
|
|
588
|
+
properties: {
|
|
589
|
+
sessionId: {
|
|
590
|
+
type: "string",
|
|
591
|
+
description: "Optional session ID to filter. If not provided, watches ALL sessions."
|
|
592
|
+
},
|
|
593
|
+
batchWindowSeconds: {
|
|
594
|
+
type: "number",
|
|
595
|
+
description: "Seconds to wait after first annotation before returning batch (default: 10, max: 60)"
|
|
596
|
+
},
|
|
597
|
+
timeoutSeconds: {
|
|
598
|
+
type: "number",
|
|
599
|
+
description: "Max seconds to wait for first annotation (default: 120, max: 300)"
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
required: []
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
];
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
// src/server/events.ts
|
|
610
|
+
var globalSequence, EventBus, eventBus, UserEventBus, userEventBus;
|
|
611
|
+
var init_events = __esm({
|
|
612
|
+
"src/server/events.ts"() {
|
|
613
|
+
"use strict";
|
|
614
|
+
globalSequence = 0;
|
|
615
|
+
EventBus = class {
|
|
616
|
+
handlers = /* @__PURE__ */ new Set();
|
|
617
|
+
sessionHandlers = /* @__PURE__ */ new Map();
|
|
618
|
+
/**
|
|
619
|
+
* Subscribe to all events.
|
|
620
|
+
*/
|
|
621
|
+
subscribe(handler) {
|
|
622
|
+
this.handlers.add(handler);
|
|
623
|
+
return () => this.handlers.delete(handler);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Subscribe to events for a specific session.
|
|
627
|
+
*/
|
|
628
|
+
subscribeToSession(sessionId, handler) {
|
|
629
|
+
if (!this.sessionHandlers.has(sessionId)) {
|
|
630
|
+
this.sessionHandlers.set(sessionId, /* @__PURE__ */ new Set());
|
|
631
|
+
}
|
|
632
|
+
this.sessionHandlers.get(sessionId).add(handler);
|
|
633
|
+
return () => {
|
|
634
|
+
const handlers = this.sessionHandlers.get(sessionId);
|
|
635
|
+
if (handlers) {
|
|
636
|
+
handlers.delete(handler);
|
|
637
|
+
if (handlers.size === 0) {
|
|
638
|
+
this.sessionHandlers.delete(sessionId);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Emit an event to all subscribers.
|
|
645
|
+
*/
|
|
646
|
+
emit(type, sessionId, payload) {
|
|
647
|
+
const event = {
|
|
648
|
+
type,
|
|
649
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
650
|
+
sessionId,
|
|
651
|
+
sequence: ++globalSequence,
|
|
652
|
+
payload
|
|
653
|
+
};
|
|
654
|
+
for (const handler of this.handlers) {
|
|
655
|
+
try {
|
|
656
|
+
handler(event);
|
|
657
|
+
} catch (err) {
|
|
658
|
+
console.error("[EventBus] Handler error:", err);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
const sessionHandlers = this.sessionHandlers.get(sessionId);
|
|
662
|
+
if (sessionHandlers) {
|
|
663
|
+
for (const handler of sessionHandlers) {
|
|
664
|
+
try {
|
|
665
|
+
handler(event);
|
|
666
|
+
} catch (err) {
|
|
667
|
+
console.error("[EventBus] Session handler error:", err);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return event;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Get current sequence number (for reconnect logic).
|
|
675
|
+
*/
|
|
676
|
+
getSequence() {
|
|
677
|
+
return globalSequence;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Set sequence from persisted state (for server restart).
|
|
681
|
+
*/
|
|
682
|
+
setSequence(seq) {
|
|
683
|
+
globalSequence = seq;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
eventBus = new EventBus();
|
|
687
|
+
UserEventBus = class {
|
|
688
|
+
userHandlers = /* @__PURE__ */ new Map();
|
|
689
|
+
userSessionHandlers = /* @__PURE__ */ new Map();
|
|
690
|
+
/**
|
|
691
|
+
* Subscribe to all events for a specific user.
|
|
692
|
+
*/
|
|
693
|
+
subscribeForUser(userId, handler) {
|
|
694
|
+
if (!this.userHandlers.has(userId)) {
|
|
695
|
+
this.userHandlers.set(userId, /* @__PURE__ */ new Set());
|
|
696
|
+
}
|
|
697
|
+
this.userHandlers.get(userId).add(handler);
|
|
698
|
+
return () => {
|
|
699
|
+
const handlers = this.userHandlers.get(userId);
|
|
700
|
+
if (handlers) {
|
|
701
|
+
handlers.delete(handler);
|
|
702
|
+
if (handlers.size === 0) {
|
|
703
|
+
this.userHandlers.delete(userId);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Subscribe to events for a specific session of a specific user.
|
|
710
|
+
*/
|
|
711
|
+
subscribeToSessionForUser(userId, sessionId, handler) {
|
|
712
|
+
if (!this.userSessionHandlers.has(userId)) {
|
|
713
|
+
this.userSessionHandlers.set(userId, /* @__PURE__ */ new Map());
|
|
714
|
+
}
|
|
715
|
+
const userSessions = this.userSessionHandlers.get(userId);
|
|
716
|
+
if (!userSessions.has(sessionId)) {
|
|
717
|
+
userSessions.set(sessionId, /* @__PURE__ */ new Set());
|
|
718
|
+
}
|
|
719
|
+
userSessions.get(sessionId).add(handler);
|
|
720
|
+
return () => {
|
|
721
|
+
const userSessions2 = this.userSessionHandlers.get(userId);
|
|
722
|
+
if (userSessions2) {
|
|
723
|
+
const handlers = userSessions2.get(sessionId);
|
|
724
|
+
if (handlers) {
|
|
725
|
+
handlers.delete(handler);
|
|
726
|
+
if (handlers.size === 0) {
|
|
727
|
+
userSessions2.delete(sessionId);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (userSessions2.size === 0) {
|
|
731
|
+
this.userSessionHandlers.delete(userId);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Emit an event scoped to a specific user.
|
|
738
|
+
* Only handlers for that user will receive the event.
|
|
739
|
+
*/
|
|
740
|
+
emitForUser(userId, type, sessionId, payload) {
|
|
741
|
+
const event = {
|
|
742
|
+
type,
|
|
743
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
744
|
+
sessionId,
|
|
745
|
+
sequence: ++globalSequence,
|
|
746
|
+
payload
|
|
747
|
+
};
|
|
748
|
+
const userHandlers = this.userHandlers.get(userId);
|
|
749
|
+
if (userHandlers) {
|
|
750
|
+
for (const handler of userHandlers) {
|
|
751
|
+
try {
|
|
752
|
+
handler(event);
|
|
753
|
+
} catch (err) {
|
|
754
|
+
console.error("[UserEventBus] Handler error:", err);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
const userSessions = this.userSessionHandlers.get(userId);
|
|
759
|
+
if (userSessions) {
|
|
760
|
+
const sessionHandlers = userSessions.get(sessionId);
|
|
761
|
+
if (sessionHandlers) {
|
|
762
|
+
for (const handler of sessionHandlers) {
|
|
763
|
+
try {
|
|
764
|
+
handler(event);
|
|
765
|
+
} catch (err) {
|
|
766
|
+
console.error("[UserEventBus] Session handler error:", err);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return event;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Check if a user has any active listeners.
|
|
775
|
+
*/
|
|
776
|
+
hasListenersForUser(userId) {
|
|
777
|
+
const hasGlobal = this.userHandlers.has(userId) && this.userHandlers.get(userId).size > 0;
|
|
778
|
+
const hasSessions = this.userSessionHandlers.has(userId) && this.userSessionHandlers.get(userId).size > 0;
|
|
779
|
+
return hasGlobal || hasSessions;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Get count of listeners for a user.
|
|
783
|
+
*/
|
|
784
|
+
getListenerCountForUser(userId) {
|
|
785
|
+
let count = 0;
|
|
786
|
+
const handlers = this.userHandlers.get(userId);
|
|
787
|
+
if (handlers) count += handlers.size;
|
|
788
|
+
const sessions = this.userSessionHandlers.get(userId);
|
|
789
|
+
if (sessions) {
|
|
790
|
+
for (const sessionHandlers of sessions.values()) {
|
|
791
|
+
count += sessionHandlers.size;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return count;
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
userEventBus = new UserEventBus();
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// src/server/sqlite.ts
|
|
802
|
+
var sqlite_exports = {};
|
|
803
|
+
__export(sqlite_exports, {
|
|
804
|
+
createSQLiteStore: () => createSQLiteStore,
|
|
805
|
+
createTenantStore: () => createTenantStore
|
|
806
|
+
});
|
|
807
|
+
function getDbPath() {
|
|
808
|
+
const dataDir = (0, import_path.join)((0, import_os.homedir)(), ".agentation");
|
|
809
|
+
if (!(0, import_fs.existsSync)(dataDir)) {
|
|
810
|
+
(0, import_fs.mkdirSync)(dataDir, { recursive: true });
|
|
811
|
+
}
|
|
812
|
+
return (0, import_path.join)(dataDir, "store.db");
|
|
813
|
+
}
|
|
814
|
+
function initDatabase(db) {
|
|
815
|
+
db.exec(`
|
|
816
|
+
-- Multi-tenant tables
|
|
817
|
+
CREATE TABLE IF NOT EXISTS organizations (
|
|
818
|
+
id TEXT PRIMARY KEY,
|
|
819
|
+
name TEXT NOT NULL,
|
|
820
|
+
created_at TEXT NOT NULL,
|
|
821
|
+
updated_at TEXT
|
|
822
|
+
);
|
|
823
|
+
|
|
824
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
825
|
+
id TEXT PRIMARY KEY,
|
|
826
|
+
email TEXT NOT NULL UNIQUE,
|
|
827
|
+
org_id TEXT NOT NULL,
|
|
828
|
+
role TEXT NOT NULL DEFAULT 'member',
|
|
829
|
+
created_at TEXT NOT NULL,
|
|
830
|
+
updated_at TEXT,
|
|
831
|
+
FOREIGN KEY (org_id) REFERENCES organizations(id)
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
835
|
+
id TEXT PRIMARY KEY,
|
|
836
|
+
key_prefix TEXT NOT NULL,
|
|
837
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
838
|
+
user_id TEXT NOT NULL,
|
|
839
|
+
name TEXT NOT NULL,
|
|
840
|
+
created_at TEXT NOT NULL,
|
|
841
|
+
expires_at TEXT,
|
|
842
|
+
last_used_at TEXT,
|
|
843
|
+
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
847
|
+
id TEXT PRIMARY KEY,
|
|
848
|
+
url TEXT NOT NULL,
|
|
849
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
850
|
+
created_at TEXT NOT NULL,
|
|
851
|
+
updated_at TEXT,
|
|
852
|
+
project_id TEXT,
|
|
853
|
+
metadata TEXT,
|
|
854
|
+
user_id TEXT,
|
|
855
|
+
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
856
|
+
);
|
|
857
|
+
|
|
858
|
+
CREATE TABLE IF NOT EXISTS annotations (
|
|
859
|
+
id TEXT PRIMARY KEY,
|
|
860
|
+
session_id TEXT NOT NULL,
|
|
861
|
+
x REAL NOT NULL,
|
|
862
|
+
y REAL NOT NULL,
|
|
863
|
+
comment TEXT NOT NULL,
|
|
864
|
+
element TEXT NOT NULL,
|
|
865
|
+
element_path TEXT NOT NULL,
|
|
866
|
+
timestamp INTEGER NOT NULL,
|
|
867
|
+
selected_text TEXT,
|
|
868
|
+
bounding_box TEXT,
|
|
869
|
+
nearby_text TEXT,
|
|
870
|
+
css_classes TEXT,
|
|
871
|
+
nearby_elements TEXT,
|
|
872
|
+
computed_styles TEXT,
|
|
873
|
+
full_path TEXT,
|
|
874
|
+
accessibility TEXT,
|
|
875
|
+
is_multi_select INTEGER DEFAULT 0,
|
|
876
|
+
is_fixed INTEGER DEFAULT 0,
|
|
877
|
+
react_components TEXT,
|
|
878
|
+
url TEXT,
|
|
879
|
+
intent TEXT,
|
|
880
|
+
severity TEXT,
|
|
881
|
+
status TEXT DEFAULT 'pending',
|
|
882
|
+
thread TEXT,
|
|
883
|
+
created_at TEXT NOT NULL,
|
|
884
|
+
updated_at TEXT,
|
|
885
|
+
resolved_at TEXT,
|
|
886
|
+
resolved_by TEXT,
|
|
887
|
+
author_id TEXT,
|
|
888
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
889
|
+
);
|
|
890
|
+
|
|
891
|
+
CREATE TABLE IF NOT EXISTS annotations_v2 (
|
|
892
|
+
id TEXT PRIMARY KEY,
|
|
893
|
+
session_id TEXT NOT NULL,
|
|
894
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
895
|
+
timestamp TEXT NOT NULL,
|
|
896
|
+
url TEXT NOT NULL,
|
|
897
|
+
element_selector TEXT NOT NULL,
|
|
898
|
+
element_text TEXT,
|
|
899
|
+
comment TEXT NOT NULL,
|
|
900
|
+
source TEXT NOT NULL,
|
|
901
|
+
metadata TEXT,
|
|
902
|
+
created_at TEXT NOT NULL,
|
|
903
|
+
updated_at TEXT,
|
|
904
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
905
|
+
);
|
|
906
|
+
|
|
907
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
908
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
909
|
+
type TEXT NOT NULL,
|
|
910
|
+
timestamp TEXT NOT NULL,
|
|
911
|
+
session_id TEXT NOT NULL,
|
|
912
|
+
sequence INTEGER NOT NULL UNIQUE,
|
|
913
|
+
payload TEXT NOT NULL,
|
|
914
|
+
user_id TEXT,
|
|
915
|
+
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
916
|
+
);
|
|
917
|
+
|
|
918
|
+
-- Indexes
|
|
919
|
+
CREATE INDEX IF NOT EXISTS idx_users_org ON users(org_id);
|
|
920
|
+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
|
921
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_user ON api_keys(user_id);
|
|
922
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash);
|
|
923
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
|
|
924
|
+
CREATE INDEX IF NOT EXISTS idx_annotations_session ON annotations(session_id);
|
|
925
|
+
CREATE INDEX IF NOT EXISTS idx_annotations_v2_session ON annotations_v2(session_id);
|
|
926
|
+
CREATE INDEX IF NOT EXISTS idx_events_session_seq ON events(session_id, sequence);
|
|
927
|
+
CREATE INDEX IF NOT EXISTS idx_events_user ON events(user_id);
|
|
928
|
+
`);
|
|
929
|
+
}
|
|
930
|
+
function generateId() {
|
|
931
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
932
|
+
}
|
|
933
|
+
function rowToSession(row) {
|
|
934
|
+
return {
|
|
935
|
+
id: row.id,
|
|
936
|
+
url: row.url,
|
|
937
|
+
status: row.status,
|
|
938
|
+
createdAt: row.created_at,
|
|
939
|
+
updatedAt: row.updated_at,
|
|
940
|
+
projectId: row.project_id,
|
|
941
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
942
|
+
userId: row.user_id
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
function rowToOrganization(row) {
|
|
946
|
+
return {
|
|
947
|
+
id: row.id,
|
|
948
|
+
name: row.name,
|
|
949
|
+
createdAt: row.created_at,
|
|
950
|
+
updatedAt: row.updated_at
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
function rowToUser(row) {
|
|
954
|
+
return {
|
|
955
|
+
id: row.id,
|
|
956
|
+
email: row.email,
|
|
957
|
+
orgId: row.org_id,
|
|
958
|
+
role: row.role,
|
|
959
|
+
createdAt: row.created_at,
|
|
960
|
+
updatedAt: row.updated_at
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function rowToApiKey(row) {
|
|
964
|
+
return {
|
|
965
|
+
id: row.id,
|
|
966
|
+
keyPrefix: row.key_prefix,
|
|
967
|
+
keyHash: row.key_hash,
|
|
968
|
+
userId: row.user_id,
|
|
969
|
+
name: row.name,
|
|
970
|
+
createdAt: row.created_at,
|
|
971
|
+
expiresAt: row.expires_at,
|
|
972
|
+
lastUsedAt: row.last_used_at
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
function rowToAnnotation(row) {
|
|
976
|
+
return {
|
|
977
|
+
id: row.id,
|
|
978
|
+
sessionId: row.session_id,
|
|
979
|
+
x: row.x,
|
|
980
|
+
y: row.y,
|
|
981
|
+
comment: row.comment,
|
|
982
|
+
element: row.element,
|
|
983
|
+
elementPath: row.element_path,
|
|
984
|
+
timestamp: row.timestamp,
|
|
985
|
+
selectedText: row.selected_text,
|
|
986
|
+
boundingBox: row.bounding_box ? JSON.parse(row.bounding_box) : void 0,
|
|
987
|
+
nearbyText: row.nearby_text,
|
|
988
|
+
cssClasses: row.css_classes,
|
|
989
|
+
nearbyElements: row.nearby_elements,
|
|
990
|
+
computedStyles: row.computed_styles,
|
|
991
|
+
fullPath: row.full_path,
|
|
992
|
+
accessibility: row.accessibility,
|
|
993
|
+
isMultiSelect: Boolean(row.is_multi_select),
|
|
994
|
+
isFixed: Boolean(row.is_fixed),
|
|
995
|
+
reactComponents: row.react_components,
|
|
996
|
+
url: row.url,
|
|
997
|
+
intent: row.intent,
|
|
998
|
+
severity: row.severity,
|
|
999
|
+
status: row.status,
|
|
1000
|
+
thread: row.thread ? JSON.parse(row.thread) : void 0,
|
|
1001
|
+
createdAt: row.created_at,
|
|
1002
|
+
updatedAt: row.updated_at,
|
|
1003
|
+
resolvedAt: row.resolved_at,
|
|
1004
|
+
resolvedBy: row.resolved_by,
|
|
1005
|
+
authorId: row.author_id
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
function rowToAnnotationV2(row) {
|
|
1009
|
+
return {
|
|
1010
|
+
id: row.id,
|
|
1011
|
+
schemaVersion: row.schema_version,
|
|
1012
|
+
timestamp: row.timestamp,
|
|
1013
|
+
url: row.url,
|
|
1014
|
+
elementSelector: row.element_selector,
|
|
1015
|
+
elementText: row.element_text,
|
|
1016
|
+
comment: row.comment,
|
|
1017
|
+
source: JSON.parse(row.source),
|
|
1018
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
1019
|
+
sessionId: row.session_id,
|
|
1020
|
+
createdAt: row.created_at,
|
|
1021
|
+
updatedAt: row.updated_at
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
function createSQLiteStore(dbPath) {
|
|
1025
|
+
const db = new import_better_sqlite3.default(dbPath ?? getDbPath());
|
|
1026
|
+
db.pragma("journal_mode = WAL");
|
|
1027
|
+
initDatabase(db);
|
|
1028
|
+
const lastEvent = db.prepare("SELECT MAX(sequence) as seq FROM events").get();
|
|
1029
|
+
if (lastEvent?.seq) {
|
|
1030
|
+
eventBus.setSequence(lastEvent.seq);
|
|
1031
|
+
}
|
|
1032
|
+
const stmts = {
|
|
1033
|
+
// Sessions
|
|
1034
|
+
insertSession: db.prepare(`
|
|
1035
|
+
INSERT INTO sessions (id, url, status, created_at, project_id, metadata)
|
|
1036
|
+
VALUES (@id, @url, @status, @createdAt, @projectId, @metadata)
|
|
1037
|
+
`),
|
|
1038
|
+
getSession: db.prepare("SELECT * FROM sessions WHERE id = ?"),
|
|
1039
|
+
updateSessionStatus: db.prepare(`
|
|
1040
|
+
UPDATE sessions SET status = @status, updated_at = @updatedAt WHERE id = @id
|
|
1041
|
+
`),
|
|
1042
|
+
listSessions: db.prepare("SELECT * FROM sessions ORDER BY created_at DESC"),
|
|
1043
|
+
// Annotations
|
|
1044
|
+
insertAnnotation: db.prepare(`
|
|
1045
|
+
INSERT INTO annotations (
|
|
1046
|
+
id, session_id, x, y, comment, element, element_path, timestamp,
|
|
1047
|
+
selected_text, bounding_box, nearby_text, css_classes, nearby_elements,
|
|
1048
|
+
computed_styles, full_path, accessibility, is_multi_select, is_fixed,
|
|
1049
|
+
react_components, url, intent, severity, status, thread, created_at,
|
|
1050
|
+
updated_at, resolved_at, resolved_by, author_id
|
|
1051
|
+
) VALUES (
|
|
1052
|
+
@id, @sessionId, @x, @y, @comment, @element, @elementPath, @timestamp,
|
|
1053
|
+
@selectedText, @boundingBox, @nearbyText, @cssClasses, @nearbyElements,
|
|
1054
|
+
@computedStyles, @fullPath, @accessibility, @isMultiSelect, @isFixed,
|
|
1055
|
+
@reactComponents, @url, @intent, @severity, @status, @thread, @createdAt,
|
|
1056
|
+
@updatedAt, @resolvedAt, @resolvedBy, @authorId
|
|
1057
|
+
)
|
|
1058
|
+
`),
|
|
1059
|
+
getAnnotation: db.prepare("SELECT * FROM annotations WHERE id = ?"),
|
|
1060
|
+
getAnnotationsBySession: db.prepare("SELECT * FROM annotations WHERE session_id = ? ORDER BY timestamp"),
|
|
1061
|
+
getPendingAnnotations: db.prepare("SELECT * FROM annotations WHERE session_id = ? AND status = 'pending' ORDER BY timestamp"),
|
|
1062
|
+
deleteAnnotation: db.prepare("DELETE FROM annotations WHERE id = ?"),
|
|
1063
|
+
updateAnnotation: db.prepare(`
|
|
1064
|
+
UPDATE annotations SET
|
|
1065
|
+
comment = COALESCE(@comment, comment),
|
|
1066
|
+
status = COALESCE(@status, status),
|
|
1067
|
+
updated_at = @updatedAt,
|
|
1068
|
+
resolved_at = COALESCE(@resolvedAt, resolved_at),
|
|
1069
|
+
resolved_by = COALESCE(@resolvedBy, resolved_by),
|
|
1070
|
+
thread = COALESCE(@thread, thread),
|
|
1071
|
+
intent = COALESCE(@intent, intent),
|
|
1072
|
+
severity = COALESCE(@severity, severity)
|
|
1073
|
+
WHERE id = @id
|
|
1074
|
+
`),
|
|
1075
|
+
// Events
|
|
1076
|
+
insertEvent: db.prepare(`
|
|
1077
|
+
INSERT INTO events (type, timestamp, session_id, sequence, payload)
|
|
1078
|
+
VALUES (@type, @timestamp, @sessionId, @sequence, @payload)
|
|
1079
|
+
`),
|
|
1080
|
+
getEventsSince: db.prepare(`
|
|
1081
|
+
SELECT * FROM events WHERE session_id = ? AND sequence > ? ORDER BY sequence
|
|
1082
|
+
`),
|
|
1083
|
+
pruneOldEvents: db.prepare(`
|
|
1084
|
+
DELETE FROM events WHERE timestamp < ?
|
|
1085
|
+
`),
|
|
1086
|
+
// Annotations V2
|
|
1087
|
+
insertAnnotationV2: db.prepare(`
|
|
1088
|
+
INSERT INTO annotations_v2 (
|
|
1089
|
+
id, session_id, schema_version, timestamp, url, element_selector,
|
|
1090
|
+
element_text, comment, source, metadata, created_at, updated_at
|
|
1091
|
+
) VALUES (
|
|
1092
|
+
@id, @sessionId, @schemaVersion, @timestamp, @url, @elementSelector,
|
|
1093
|
+
@elementText, @comment, @source, @metadata, @createdAt, @updatedAt
|
|
1094
|
+
)
|
|
1095
|
+
`),
|
|
1096
|
+
getAnnotationV2: db.prepare("SELECT * FROM annotations_v2 WHERE id = ?"),
|
|
1097
|
+
getAnnotationsV2BySession: db.prepare(
|
|
1098
|
+
"SELECT * FROM annotations_v2 WHERE session_id = ? ORDER BY created_at"
|
|
1099
|
+
),
|
|
1100
|
+
updateAnnotationV2: db.prepare(`
|
|
1101
|
+
UPDATE annotations_v2 SET
|
|
1102
|
+
element_text = COALESCE(@elementText, element_text),
|
|
1103
|
+
comment = COALESCE(@comment, comment),
|
|
1104
|
+
source = COALESCE(@source, source),
|
|
1105
|
+
metadata = COALESCE(@metadata, metadata),
|
|
1106
|
+
updated_at = @updatedAt
|
|
1107
|
+
WHERE id = @id
|
|
1108
|
+
`),
|
|
1109
|
+
deleteAnnotationV2: db.prepare("DELETE FROM annotations_v2 WHERE id = ?")
|
|
1110
|
+
};
|
|
1111
|
+
const retentionDays = parseInt(process.env.AGENTATION_EVENT_RETENTION_DAYS || "7", 10);
|
|
1112
|
+
const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1e3).toISOString();
|
|
1113
|
+
stmts.pruneOldEvents.run(cutoff);
|
|
1114
|
+
function persistEvent(event) {
|
|
1115
|
+
stmts.insertEvent.run({
|
|
1116
|
+
type: event.type,
|
|
1117
|
+
timestamp: event.timestamp,
|
|
1118
|
+
sessionId: event.sessionId,
|
|
1119
|
+
sequence: event.sequence,
|
|
1120
|
+
payload: JSON.stringify(event.payload)
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
return {
|
|
1124
|
+
// Sessions
|
|
1125
|
+
createSession(url, projectId) {
|
|
1126
|
+
const session = {
|
|
1127
|
+
id: generateId(),
|
|
1128
|
+
url,
|
|
1129
|
+
status: "active",
|
|
1130
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1131
|
+
projectId
|
|
1132
|
+
};
|
|
1133
|
+
stmts.insertSession.run({
|
|
1134
|
+
id: session.id,
|
|
1135
|
+
url: session.url,
|
|
1136
|
+
status: session.status,
|
|
1137
|
+
createdAt: session.createdAt,
|
|
1138
|
+
projectId: session.projectId ?? null,
|
|
1139
|
+
metadata: null
|
|
1140
|
+
});
|
|
1141
|
+
const event = eventBus.emit("session.created", session.id, session);
|
|
1142
|
+
persistEvent(event);
|
|
1143
|
+
return session;
|
|
1144
|
+
},
|
|
1145
|
+
getSession(id) {
|
|
1146
|
+
const row = stmts.getSession.get(id);
|
|
1147
|
+
return row ? rowToSession(row) : void 0;
|
|
1148
|
+
},
|
|
1149
|
+
getSessionWithAnnotations(id) {
|
|
1150
|
+
const sessionRow = stmts.getSession.get(id);
|
|
1151
|
+
if (!sessionRow) return void 0;
|
|
1152
|
+
const annotationRows = stmts.getAnnotationsBySession.all(id);
|
|
1153
|
+
return {
|
|
1154
|
+
...rowToSession(sessionRow),
|
|
1155
|
+
annotations: annotationRows.map(rowToAnnotation)
|
|
1156
|
+
};
|
|
1157
|
+
},
|
|
1158
|
+
updateSessionStatus(id, status) {
|
|
1159
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1160
|
+
const result = stmts.updateSessionStatus.run({ id, status, updatedAt });
|
|
1161
|
+
if (result.changes === 0) return void 0;
|
|
1162
|
+
const session = this.getSession(id);
|
|
1163
|
+
if (session) {
|
|
1164
|
+
const eventType = status === "closed" ? "session.closed" : "session.updated";
|
|
1165
|
+
const event = eventBus.emit(eventType, id, session);
|
|
1166
|
+
persistEvent(event);
|
|
1167
|
+
}
|
|
1168
|
+
return session;
|
|
1169
|
+
},
|
|
1170
|
+
listSessions() {
|
|
1171
|
+
const rows = stmts.listSessions.all();
|
|
1172
|
+
return rows.map(rowToSession);
|
|
1173
|
+
},
|
|
1174
|
+
// Annotations
|
|
1175
|
+
addAnnotation(sessionId, data) {
|
|
1176
|
+
const session = this.getSession(sessionId);
|
|
1177
|
+
if (!session) return void 0;
|
|
1178
|
+
const annotation = {
|
|
1179
|
+
...data,
|
|
1180
|
+
id: generateId(),
|
|
1181
|
+
sessionId,
|
|
1182
|
+
status: "pending",
|
|
1183
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1184
|
+
};
|
|
1185
|
+
stmts.insertAnnotation.run({
|
|
1186
|
+
id: annotation.id,
|
|
1187
|
+
sessionId: annotation.sessionId,
|
|
1188
|
+
x: annotation.x,
|
|
1189
|
+
y: annotation.y,
|
|
1190
|
+
comment: annotation.comment,
|
|
1191
|
+
element: annotation.element,
|
|
1192
|
+
elementPath: annotation.elementPath,
|
|
1193
|
+
timestamp: annotation.timestamp,
|
|
1194
|
+
selectedText: annotation.selectedText ?? null,
|
|
1195
|
+
boundingBox: annotation.boundingBox ? JSON.stringify(annotation.boundingBox) : null,
|
|
1196
|
+
nearbyText: annotation.nearbyText ?? null,
|
|
1197
|
+
cssClasses: annotation.cssClasses ?? null,
|
|
1198
|
+
nearbyElements: annotation.nearbyElements ?? null,
|
|
1199
|
+
computedStyles: annotation.computedStyles ?? null,
|
|
1200
|
+
fullPath: annotation.fullPath ?? null,
|
|
1201
|
+
accessibility: annotation.accessibility ?? null,
|
|
1202
|
+
isMultiSelect: annotation.isMultiSelect ? 1 : 0,
|
|
1203
|
+
isFixed: annotation.isFixed ? 1 : 0,
|
|
1204
|
+
reactComponents: annotation.reactComponents ?? null,
|
|
1205
|
+
url: annotation.url ?? null,
|
|
1206
|
+
intent: annotation.intent ?? null,
|
|
1207
|
+
severity: annotation.severity ?? null,
|
|
1208
|
+
status: annotation.status ?? "pending",
|
|
1209
|
+
thread: annotation.thread ? JSON.stringify(annotation.thread) : null,
|
|
1210
|
+
createdAt: annotation.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1211
|
+
updatedAt: null,
|
|
1212
|
+
resolvedAt: null,
|
|
1213
|
+
resolvedBy: null,
|
|
1214
|
+
authorId: annotation.authorId ?? null
|
|
1215
|
+
});
|
|
1216
|
+
const event = eventBus.emit("annotation.created", sessionId, annotation);
|
|
1217
|
+
persistEvent(event);
|
|
1218
|
+
return annotation;
|
|
1219
|
+
},
|
|
1220
|
+
getAnnotation(id) {
|
|
1221
|
+
const row = stmts.getAnnotation.get(id);
|
|
1222
|
+
return row ? rowToAnnotation(row) : void 0;
|
|
1223
|
+
},
|
|
1224
|
+
updateAnnotation(id, data) {
|
|
1225
|
+
const existing = this.getAnnotation(id);
|
|
1226
|
+
if (!existing) return void 0;
|
|
1227
|
+
stmts.updateAnnotation.run({
|
|
1228
|
+
id,
|
|
1229
|
+
comment: data.comment ?? null,
|
|
1230
|
+
status: data.status ?? null,
|
|
1231
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1232
|
+
resolvedAt: data.resolvedAt ?? null,
|
|
1233
|
+
resolvedBy: data.resolvedBy ?? null,
|
|
1234
|
+
thread: data.thread ? JSON.stringify(data.thread) : null,
|
|
1235
|
+
intent: data.intent ?? null,
|
|
1236
|
+
severity: data.severity ?? null
|
|
1237
|
+
});
|
|
1238
|
+
const updated = this.getAnnotation(id);
|
|
1239
|
+
if (updated && existing.sessionId) {
|
|
1240
|
+
const event = eventBus.emit("annotation.updated", existing.sessionId, updated);
|
|
1241
|
+
persistEvent(event);
|
|
1242
|
+
}
|
|
1243
|
+
return updated;
|
|
1244
|
+
},
|
|
1245
|
+
updateAnnotationStatus(id, status, resolvedBy) {
|
|
1246
|
+
const isResolved = status === "resolved" || status === "dismissed";
|
|
1247
|
+
return this.updateAnnotation(id, {
|
|
1248
|
+
status,
|
|
1249
|
+
resolvedAt: isResolved ? (/* @__PURE__ */ new Date()).toISOString() : void 0,
|
|
1250
|
+
resolvedBy: isResolved ? resolvedBy || "agent" : void 0
|
|
1251
|
+
});
|
|
1252
|
+
},
|
|
1253
|
+
addThreadMessage(annotationId, role, content) {
|
|
1254
|
+
const existing = this.getAnnotation(annotationId);
|
|
1255
|
+
if (!existing) return void 0;
|
|
1256
|
+
const message = {
|
|
1257
|
+
id: generateId(),
|
|
1258
|
+
role,
|
|
1259
|
+
content,
|
|
1260
|
+
timestamp: Date.now()
|
|
1261
|
+
};
|
|
1262
|
+
const thread = [...existing.thread || [], message];
|
|
1263
|
+
const updated = this.updateAnnotation(annotationId, { thread });
|
|
1264
|
+
if (updated && existing.sessionId) {
|
|
1265
|
+
const event = eventBus.emit("thread.message", existing.sessionId, message);
|
|
1266
|
+
persistEvent(event);
|
|
1267
|
+
}
|
|
1268
|
+
return updated;
|
|
1269
|
+
},
|
|
1270
|
+
getPendingAnnotations(sessionId) {
|
|
1271
|
+
const rows = stmts.getPendingAnnotations.all(sessionId);
|
|
1272
|
+
return rows.map(rowToAnnotation);
|
|
1273
|
+
},
|
|
1274
|
+
getSessionAnnotations(sessionId) {
|
|
1275
|
+
const rows = stmts.getAnnotationsBySession.all(sessionId);
|
|
1276
|
+
return rows.map(rowToAnnotation);
|
|
1277
|
+
},
|
|
1278
|
+
deleteAnnotation(id) {
|
|
1279
|
+
const existing = this.getAnnotation(id);
|
|
1280
|
+
if (!existing) return void 0;
|
|
1281
|
+
stmts.deleteAnnotation.run(id);
|
|
1282
|
+
if (existing.sessionId) {
|
|
1283
|
+
const event = eventBus.emit("annotation.deleted", existing.sessionId, existing);
|
|
1284
|
+
persistEvent(event);
|
|
1285
|
+
}
|
|
1286
|
+
return existing;
|
|
1287
|
+
},
|
|
1288
|
+
// -- Annotations V2 (Vue schema) ------------------------------------------
|
|
1289
|
+
getSessionWithAnnotationsV2(id) {
|
|
1290
|
+
const sessionRow = stmts.getSession.get(id);
|
|
1291
|
+
if (!sessionRow) return void 0;
|
|
1292
|
+
const annotationRows = stmts.getAnnotationsV2BySession.all(id);
|
|
1293
|
+
return {
|
|
1294
|
+
...rowToSession(sessionRow),
|
|
1295
|
+
annotations: annotationRows.map(rowToAnnotationV2)
|
|
1296
|
+
};
|
|
1297
|
+
},
|
|
1298
|
+
addAnnotationV2(sessionId, data) {
|
|
1299
|
+
const session = this.getSession(sessionId);
|
|
1300
|
+
if (!session) return void 0;
|
|
1301
|
+
const existing = this.getAnnotationV2(data.id);
|
|
1302
|
+
if (existing) return existing;
|
|
1303
|
+
const annotation = {
|
|
1304
|
+
...data,
|
|
1305
|
+
sessionId,
|
|
1306
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1307
|
+
};
|
|
1308
|
+
stmts.insertAnnotationV2.run({
|
|
1309
|
+
id: annotation.id,
|
|
1310
|
+
sessionId: annotation.sessionId,
|
|
1311
|
+
schemaVersion: annotation.schemaVersion,
|
|
1312
|
+
timestamp: annotation.timestamp,
|
|
1313
|
+
url: annotation.url,
|
|
1314
|
+
elementSelector: annotation.elementSelector,
|
|
1315
|
+
elementText: annotation.elementText ?? null,
|
|
1316
|
+
comment: annotation.comment,
|
|
1317
|
+
source: JSON.stringify(annotation.source),
|
|
1318
|
+
metadata: annotation.metadata ? JSON.stringify(annotation.metadata) : null,
|
|
1319
|
+
createdAt: annotation.createdAt,
|
|
1320
|
+
updatedAt: null
|
|
1321
|
+
});
|
|
1322
|
+
return annotation;
|
|
1323
|
+
},
|
|
1324
|
+
getAnnotationV2(id) {
|
|
1325
|
+
const row = stmts.getAnnotationV2.get(id);
|
|
1326
|
+
return row ? rowToAnnotationV2(row) : void 0;
|
|
1327
|
+
},
|
|
1328
|
+
updateAnnotationV2(id, data) {
|
|
1329
|
+
const existing = this.getAnnotationV2(id);
|
|
1330
|
+
if (!existing) return void 0;
|
|
1331
|
+
stmts.updateAnnotationV2.run({
|
|
1332
|
+
id,
|
|
1333
|
+
elementText: data.elementText ?? null,
|
|
1334
|
+
comment: data.comment ?? null,
|
|
1335
|
+
source: data.source ? JSON.stringify(data.source) : null,
|
|
1336
|
+
metadata: data.metadata ? JSON.stringify(data.metadata) : null,
|
|
1337
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1338
|
+
});
|
|
1339
|
+
return this.getAnnotationV2(id);
|
|
1340
|
+
},
|
|
1341
|
+
getSessionAnnotationsV2(sessionId) {
|
|
1342
|
+
const rows = stmts.getAnnotationsV2BySession.all(sessionId);
|
|
1343
|
+
return rows.map(rowToAnnotationV2);
|
|
1344
|
+
},
|
|
1345
|
+
deleteAnnotationV2(id) {
|
|
1346
|
+
const existing = this.getAnnotationV2(id);
|
|
1347
|
+
if (!existing) return void 0;
|
|
1348
|
+
stmts.deleteAnnotationV2.run(id);
|
|
1349
|
+
return existing;
|
|
1350
|
+
},
|
|
1351
|
+
// Events
|
|
1352
|
+
getEventsSince(sessionId, sequence) {
|
|
1353
|
+
const rows = stmts.getEventsSince.all(sessionId, sequence);
|
|
1354
|
+
return rows.map((row) => ({
|
|
1355
|
+
type: row.type,
|
|
1356
|
+
timestamp: row.timestamp,
|
|
1357
|
+
sessionId: row.session_id,
|
|
1358
|
+
sequence: row.sequence,
|
|
1359
|
+
payload: JSON.parse(row.payload)
|
|
1360
|
+
}));
|
|
1361
|
+
},
|
|
1362
|
+
// Lifecycle
|
|
1363
|
+
close() {
|
|
1364
|
+
db.close();
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
function createTenantStore(dbPath) {
|
|
1369
|
+
const db = new import_better_sqlite3.default(dbPath ?? getDbPath());
|
|
1370
|
+
db.pragma("journal_mode = WAL");
|
|
1371
|
+
initDatabase(db);
|
|
1372
|
+
const lastEvent = db.prepare("SELECT MAX(sequence) as seq FROM events").get();
|
|
1373
|
+
if (lastEvent?.seq) {
|
|
1374
|
+
eventBus.setSequence(lastEvent.seq);
|
|
1375
|
+
}
|
|
1376
|
+
const tenantStmts = {
|
|
1377
|
+
// Organizations
|
|
1378
|
+
insertOrg: db.prepare(`
|
|
1379
|
+
INSERT INTO organizations (id, name, created_at)
|
|
1380
|
+
VALUES (@id, @name, @createdAt)
|
|
1381
|
+
`),
|
|
1382
|
+
getOrg: db.prepare("SELECT * FROM organizations WHERE id = ?"),
|
|
1383
|
+
// Users
|
|
1384
|
+
insertUser: db.prepare(`
|
|
1385
|
+
INSERT INTO users (id, email, org_id, role, created_at)
|
|
1386
|
+
VALUES (@id, @email, @orgId, @role, @createdAt)
|
|
1387
|
+
`),
|
|
1388
|
+
getUser: db.prepare("SELECT * FROM users WHERE id = ?"),
|
|
1389
|
+
getUserByEmail: db.prepare("SELECT * FROM users WHERE email = ?"),
|
|
1390
|
+
getUsersByOrg: db.prepare("SELECT * FROM users WHERE org_id = ?"),
|
|
1391
|
+
// API Keys
|
|
1392
|
+
insertApiKey: db.prepare(`
|
|
1393
|
+
INSERT INTO api_keys (id, key_prefix, key_hash, user_id, name, created_at, expires_at)
|
|
1394
|
+
VALUES (@id, @keyPrefix, @keyHash, @userId, @name, @createdAt, @expiresAt)
|
|
1395
|
+
`),
|
|
1396
|
+
getApiKeyByHash: db.prepare("SELECT * FROM api_keys WHERE key_hash = ?"),
|
|
1397
|
+
listApiKeys: db.prepare("SELECT * FROM api_keys WHERE user_id = ? ORDER BY created_at DESC"),
|
|
1398
|
+
deleteApiKey: db.prepare("DELETE FROM api_keys WHERE id = ?"),
|
|
1399
|
+
updateApiKeyLastUsed: db.prepare("UPDATE api_keys SET last_used_at = ? WHERE id = ?"),
|
|
1400
|
+
// User-scoped sessions
|
|
1401
|
+
insertSessionForUser: db.prepare(`
|
|
1402
|
+
INSERT INTO sessions (id, url, status, created_at, project_id, metadata, user_id)
|
|
1403
|
+
VALUES (@id, @url, @status, @createdAt, @projectId, @metadata, @userId)
|
|
1404
|
+
`),
|
|
1405
|
+
listSessionsForUser: db.prepare("SELECT * FROM sessions WHERE user_id = ? ORDER BY created_at DESC"),
|
|
1406
|
+
getSessionForUser: db.prepare("SELECT * FROM sessions WHERE id = ? AND user_id = ?"),
|
|
1407
|
+
getAnnotationsBySession: db.prepare("SELECT * FROM annotations WHERE session_id = ? ORDER BY timestamp"),
|
|
1408
|
+
getPendingAnnotationsForSession: db.prepare("SELECT * FROM annotations WHERE session_id = ? AND status = 'pending' ORDER BY timestamp"),
|
|
1409
|
+
// Get all pending for a user (across all their sessions)
|
|
1410
|
+
getAllPendingForUser: db.prepare(`
|
|
1411
|
+
SELECT a.* FROM annotations a
|
|
1412
|
+
JOIN sessions s ON a.session_id = s.id
|
|
1413
|
+
WHERE s.user_id = ? AND a.status = 'pending'
|
|
1414
|
+
ORDER BY a.timestamp
|
|
1415
|
+
`),
|
|
1416
|
+
// Events
|
|
1417
|
+
insertEvent: db.prepare(`
|
|
1418
|
+
INSERT INTO events (type, timestamp, session_id, sequence, payload, user_id)
|
|
1419
|
+
VALUES (@type, @timestamp, @sessionId, @sequence, @payload, @userId)
|
|
1420
|
+
`),
|
|
1421
|
+
// Prune old events
|
|
1422
|
+
pruneOldEvents: db.prepare(`
|
|
1423
|
+
DELETE FROM events WHERE timestamp < ?
|
|
1424
|
+
`)
|
|
1425
|
+
};
|
|
1426
|
+
const retentionDays = parseInt(process.env.AGENTATION_EVENT_RETENTION_DAYS || "7", 10);
|
|
1427
|
+
const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1e3).toISOString();
|
|
1428
|
+
tenantStmts.pruneOldEvents.run(cutoff);
|
|
1429
|
+
function persistEventForUser(event, userId) {
|
|
1430
|
+
tenantStmts.insertEvent.run({
|
|
1431
|
+
type: event.type,
|
|
1432
|
+
timestamp: event.timestamp,
|
|
1433
|
+
sessionId: event.sessionId,
|
|
1434
|
+
sequence: event.sequence,
|
|
1435
|
+
payload: JSON.stringify(event.payload),
|
|
1436
|
+
userId
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
return {
|
|
1440
|
+
// Organizations
|
|
1441
|
+
createOrganization(name) {
|
|
1442
|
+
const org = {
|
|
1443
|
+
id: `org_${generateId()}`,
|
|
1444
|
+
name,
|
|
1445
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1446
|
+
};
|
|
1447
|
+
tenantStmts.insertOrg.run({
|
|
1448
|
+
id: org.id,
|
|
1449
|
+
name: org.name,
|
|
1450
|
+
createdAt: org.createdAt
|
|
1451
|
+
});
|
|
1452
|
+
return org;
|
|
1453
|
+
},
|
|
1454
|
+
getOrganization(id) {
|
|
1455
|
+
const row = tenantStmts.getOrg.get(id);
|
|
1456
|
+
return row ? rowToOrganization(row) : void 0;
|
|
1457
|
+
},
|
|
1458
|
+
// Users
|
|
1459
|
+
createUser(email, orgId, role = "member") {
|
|
1460
|
+
const user = {
|
|
1461
|
+
id: `user_${generateId()}`,
|
|
1462
|
+
email,
|
|
1463
|
+
orgId,
|
|
1464
|
+
role,
|
|
1465
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1466
|
+
};
|
|
1467
|
+
tenantStmts.insertUser.run({
|
|
1468
|
+
id: user.id,
|
|
1469
|
+
email: user.email,
|
|
1470
|
+
orgId: user.orgId,
|
|
1471
|
+
role: user.role,
|
|
1472
|
+
createdAt: user.createdAt
|
|
1473
|
+
});
|
|
1474
|
+
return user;
|
|
1475
|
+
},
|
|
1476
|
+
getUser(id) {
|
|
1477
|
+
const row = tenantStmts.getUser.get(id);
|
|
1478
|
+
return row ? rowToUser(row) : void 0;
|
|
1479
|
+
},
|
|
1480
|
+
getUserByEmail(email) {
|
|
1481
|
+
const row = tenantStmts.getUserByEmail.get(email);
|
|
1482
|
+
return row ? rowToUser(row) : void 0;
|
|
1483
|
+
},
|
|
1484
|
+
getUsersByOrg(orgId) {
|
|
1485
|
+
const rows = tenantStmts.getUsersByOrg.all(orgId);
|
|
1486
|
+
return rows.map(rowToUser);
|
|
1487
|
+
},
|
|
1488
|
+
// API Keys
|
|
1489
|
+
createApiKey(userId, name, expiresAt) {
|
|
1490
|
+
const id = `key_${generateId()}`;
|
|
1491
|
+
const rawKey = `sk_live_${(0, import_crypto.randomBytes)(32).toString("base64url")}`;
|
|
1492
|
+
const keyPrefix = rawKey.substring(0, 12);
|
|
1493
|
+
const keyHash = (0, import_crypto.createHash)("sha256").update(rawKey).digest("hex");
|
|
1494
|
+
const apiKey2 = {
|
|
1495
|
+
id,
|
|
1496
|
+
keyPrefix,
|
|
1497
|
+
keyHash,
|
|
1498
|
+
userId,
|
|
1499
|
+
name,
|
|
1500
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1501
|
+
expiresAt
|
|
1502
|
+
};
|
|
1503
|
+
tenantStmts.insertApiKey.run({
|
|
1504
|
+
id: apiKey2.id,
|
|
1505
|
+
keyPrefix: apiKey2.keyPrefix,
|
|
1506
|
+
keyHash: apiKey2.keyHash,
|
|
1507
|
+
userId: apiKey2.userId,
|
|
1508
|
+
name: apiKey2.name,
|
|
1509
|
+
createdAt: apiKey2.createdAt,
|
|
1510
|
+
expiresAt: apiKey2.expiresAt ?? null
|
|
1511
|
+
});
|
|
1512
|
+
return { apiKey: apiKey2, rawKey };
|
|
1513
|
+
},
|
|
1514
|
+
getApiKeyByHash(hash) {
|
|
1515
|
+
const row = tenantStmts.getApiKeyByHash.get(hash);
|
|
1516
|
+
return row ? rowToApiKey(row) : void 0;
|
|
1517
|
+
},
|
|
1518
|
+
listApiKeys(userId) {
|
|
1519
|
+
const rows = tenantStmts.listApiKeys.all(userId);
|
|
1520
|
+
return rows.map(rowToApiKey);
|
|
1521
|
+
},
|
|
1522
|
+
deleteApiKey(id) {
|
|
1523
|
+
const result = tenantStmts.deleteApiKey.run(id);
|
|
1524
|
+
return result.changes > 0;
|
|
1525
|
+
},
|
|
1526
|
+
updateApiKeyLastUsed(id) {
|
|
1527
|
+
tenantStmts.updateApiKeyLastUsed.run((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
1528
|
+
},
|
|
1529
|
+
// User-scoped sessions
|
|
1530
|
+
createSessionForUser(userId, url, projectId) {
|
|
1531
|
+
const session = {
|
|
1532
|
+
id: generateId(),
|
|
1533
|
+
url,
|
|
1534
|
+
status: "active",
|
|
1535
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1536
|
+
projectId
|
|
1537
|
+
};
|
|
1538
|
+
tenantStmts.insertSessionForUser.run({
|
|
1539
|
+
id: session.id,
|
|
1540
|
+
url: session.url,
|
|
1541
|
+
status: session.status,
|
|
1542
|
+
createdAt: session.createdAt,
|
|
1543
|
+
projectId: session.projectId ?? null,
|
|
1544
|
+
metadata: null,
|
|
1545
|
+
userId
|
|
1546
|
+
});
|
|
1547
|
+
const event = eventBus.emit("session.created", session.id, session);
|
|
1548
|
+
persistEventForUser(event, userId);
|
|
1549
|
+
return session;
|
|
1550
|
+
},
|
|
1551
|
+
listSessionsForUser(userId) {
|
|
1552
|
+
const rows = tenantStmts.listSessionsForUser.all(userId);
|
|
1553
|
+
return rows.map(rowToSession);
|
|
1554
|
+
},
|
|
1555
|
+
getSessionForUser(userId, sessionId) {
|
|
1556
|
+
const row = tenantStmts.getSessionForUser.get(sessionId, userId);
|
|
1557
|
+
return row ? rowToSession(row) : void 0;
|
|
1558
|
+
},
|
|
1559
|
+
getSessionWithAnnotationsForUser(userId, sessionId) {
|
|
1560
|
+
const sessionRow = tenantStmts.getSessionForUser.get(sessionId, userId);
|
|
1561
|
+
if (!sessionRow) return void 0;
|
|
1562
|
+
const annotationRows = tenantStmts.getAnnotationsBySession.all(sessionId);
|
|
1563
|
+
return {
|
|
1564
|
+
...rowToSession(sessionRow),
|
|
1565
|
+
annotations: annotationRows.map(rowToAnnotation)
|
|
1566
|
+
};
|
|
1567
|
+
},
|
|
1568
|
+
// User-scoped annotations
|
|
1569
|
+
getPendingAnnotationsForUser(userId, sessionId) {
|
|
1570
|
+
const session = this.getSessionForUser(userId, sessionId);
|
|
1571
|
+
if (!session) return [];
|
|
1572
|
+
const rows = tenantStmts.getPendingAnnotationsForSession.all(sessionId);
|
|
1573
|
+
return rows.map(rowToAnnotation);
|
|
1574
|
+
},
|
|
1575
|
+
getAllPendingForUser(userId) {
|
|
1576
|
+
const rows = tenantStmts.getAllPendingForUser.all(userId);
|
|
1577
|
+
return rows.map(rowToAnnotation);
|
|
1578
|
+
},
|
|
1579
|
+
// Lifecycle
|
|
1580
|
+
close() {
|
|
1581
|
+
db.close();
|
|
1582
|
+
}
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
var import_better_sqlite3, import_crypto, import_fs, import_path, import_os;
|
|
1586
|
+
var init_sqlite = __esm({
|
|
1587
|
+
"src/server/sqlite.ts"() {
|
|
1588
|
+
"use strict";
|
|
1589
|
+
import_better_sqlite3 = __toESM(require("better-sqlite3"));
|
|
1590
|
+
import_crypto = require("crypto");
|
|
1591
|
+
import_fs = require("fs");
|
|
1592
|
+
import_path = require("path");
|
|
1593
|
+
import_os = require("os");
|
|
1594
|
+
init_events();
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
// src/server/store.ts
|
|
1599
|
+
function getStore() {
|
|
1600
|
+
if (!_store) {
|
|
1601
|
+
_store = initializeStore();
|
|
1602
|
+
}
|
|
1603
|
+
return _store;
|
|
1604
|
+
}
|
|
1605
|
+
function initializeStore() {
|
|
1606
|
+
if (process.env.AGENTATION_STORE === "memory") {
|
|
1607
|
+
process.stderr.write("[Store] Using in-memory store (AGENTATION_STORE=memory)\n");
|
|
1608
|
+
return createMemoryStore();
|
|
1609
|
+
}
|
|
1610
|
+
try {
|
|
1611
|
+
const { createSQLiteStore: createSQLiteStore2 } = (init_sqlite(), __toCommonJS(sqlite_exports));
|
|
1612
|
+
const store2 = createSQLiteStore2();
|
|
1613
|
+
process.stderr.write("[Store] Using SQLite store (~/.agentation/store.db)\n");
|
|
1614
|
+
return store2;
|
|
1615
|
+
} catch (err) {
|
|
1616
|
+
console.warn("[Store] SQLite unavailable, falling back to in-memory:", err.message);
|
|
1617
|
+
return createMemoryStore();
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
function createMemoryStore() {
|
|
1621
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1622
|
+
const annotations = /* @__PURE__ */ new Map();
|
|
1623
|
+
const annotationsV2 = /* @__PURE__ */ new Map();
|
|
1624
|
+
const events = [];
|
|
1625
|
+
function generateId2() {
|
|
1626
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1627
|
+
}
|
|
1628
|
+
return {
|
|
1629
|
+
createSession(url, projectId) {
|
|
1630
|
+
const session = {
|
|
1631
|
+
id: generateId2(),
|
|
1632
|
+
url,
|
|
1633
|
+
status: "active",
|
|
1634
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1635
|
+
projectId
|
|
1636
|
+
};
|
|
1637
|
+
sessions.set(session.id, session);
|
|
1638
|
+
const event = eventBus.emit("session.created", session.id, session);
|
|
1639
|
+
events.push(event);
|
|
1640
|
+
return session;
|
|
1641
|
+
},
|
|
1642
|
+
getSession(id) {
|
|
1643
|
+
return sessions.get(id);
|
|
1644
|
+
},
|
|
1645
|
+
getSessionWithAnnotations(id) {
|
|
1646
|
+
const session = sessions.get(id);
|
|
1647
|
+
if (!session) return void 0;
|
|
1648
|
+
const sessionAnnotations = Array.from(annotations.values()).filter(
|
|
1649
|
+
(a) => a.sessionId === id
|
|
1650
|
+
);
|
|
1651
|
+
return {
|
|
1652
|
+
...session,
|
|
1653
|
+
annotations: sessionAnnotations
|
|
1654
|
+
};
|
|
1655
|
+
},
|
|
1656
|
+
getSessionWithAnnotationsV2(id) {
|
|
1657
|
+
const session = sessions.get(id);
|
|
1658
|
+
if (!session) return void 0;
|
|
1659
|
+
return {
|
|
1660
|
+
...session,
|
|
1661
|
+
annotations: Array.from(annotationsV2.values()).filter(
|
|
1662
|
+
(a) => a.sessionId === id
|
|
1663
|
+
)
|
|
1664
|
+
};
|
|
1665
|
+
},
|
|
1666
|
+
updateSessionStatus(id, status) {
|
|
1667
|
+
const session = sessions.get(id);
|
|
1668
|
+
if (!session) return void 0;
|
|
1669
|
+
session.status = status;
|
|
1670
|
+
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1671
|
+
const eventType = status === "closed" ? "session.closed" : "session.updated";
|
|
1672
|
+
const event = eventBus.emit(eventType, id, session);
|
|
1673
|
+
events.push(event);
|
|
1674
|
+
return session;
|
|
1675
|
+
},
|
|
1676
|
+
listSessions() {
|
|
1677
|
+
return Array.from(sessions.values());
|
|
1678
|
+
},
|
|
1679
|
+
addAnnotation(sessionId, data) {
|
|
1680
|
+
const session = sessions.get(sessionId);
|
|
1681
|
+
if (!session) return void 0;
|
|
1682
|
+
const annotation = {
|
|
1683
|
+
...data,
|
|
1684
|
+
id: generateId2(),
|
|
1685
|
+
sessionId,
|
|
1686
|
+
status: "pending",
|
|
1687
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1688
|
+
};
|
|
1689
|
+
annotations.set(annotation.id, annotation);
|
|
1690
|
+
const event = eventBus.emit("annotation.created", sessionId, annotation);
|
|
1691
|
+
events.push(event);
|
|
1692
|
+
return annotation;
|
|
1693
|
+
},
|
|
1694
|
+
getAnnotation(id) {
|
|
1695
|
+
return annotations.get(id);
|
|
1696
|
+
},
|
|
1697
|
+
updateAnnotation(id, data) {
|
|
1698
|
+
const annotation = annotations.get(id);
|
|
1699
|
+
if (!annotation) return void 0;
|
|
1700
|
+
Object.assign(annotation, data, { updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1701
|
+
if (annotation.sessionId) {
|
|
1702
|
+
const event = eventBus.emit("annotation.updated", annotation.sessionId, annotation);
|
|
1703
|
+
events.push(event);
|
|
1704
|
+
}
|
|
1705
|
+
return annotation;
|
|
1706
|
+
},
|
|
1707
|
+
updateAnnotationStatus(id, status, resolvedBy) {
|
|
1708
|
+
const annotation = annotations.get(id);
|
|
1709
|
+
if (!annotation) return void 0;
|
|
1710
|
+
annotation.status = status;
|
|
1711
|
+
annotation.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1712
|
+
if (status === "resolved" || status === "dismissed") {
|
|
1713
|
+
annotation.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1714
|
+
annotation.resolvedBy = resolvedBy || "agent";
|
|
1715
|
+
}
|
|
1716
|
+
if (annotation.sessionId) {
|
|
1717
|
+
const event = eventBus.emit("annotation.updated", annotation.sessionId, annotation);
|
|
1718
|
+
events.push(event);
|
|
1719
|
+
}
|
|
1720
|
+
return annotation;
|
|
1721
|
+
},
|
|
1722
|
+
addThreadMessage(annotationId, role, content) {
|
|
1723
|
+
const annotation = annotations.get(annotationId);
|
|
1724
|
+
if (!annotation) return void 0;
|
|
1725
|
+
const message = {
|
|
1726
|
+
id: generateId2(),
|
|
1727
|
+
role,
|
|
1728
|
+
content,
|
|
1729
|
+
timestamp: Date.now()
|
|
1730
|
+
};
|
|
1731
|
+
if (!annotation.thread) {
|
|
1732
|
+
annotation.thread = [];
|
|
1733
|
+
}
|
|
1734
|
+
annotation.thread.push(message);
|
|
1735
|
+
annotation.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1736
|
+
if (annotation.sessionId) {
|
|
1737
|
+
const event = eventBus.emit("thread.message", annotation.sessionId, message);
|
|
1738
|
+
events.push(event);
|
|
1739
|
+
}
|
|
1740
|
+
return annotation;
|
|
1741
|
+
},
|
|
1742
|
+
getPendingAnnotations(sessionId) {
|
|
1743
|
+
return Array.from(annotations.values()).filter(
|
|
1744
|
+
(a) => a.sessionId === sessionId && a.status === "pending"
|
|
1745
|
+
);
|
|
1746
|
+
},
|
|
1747
|
+
getSessionAnnotations(sessionId) {
|
|
1748
|
+
return Array.from(annotations.values()).filter(
|
|
1749
|
+
(a) => a.sessionId === sessionId
|
|
1750
|
+
);
|
|
1751
|
+
},
|
|
1752
|
+
deleteAnnotation(id) {
|
|
1753
|
+
const annotation = annotations.get(id);
|
|
1754
|
+
if (!annotation) return void 0;
|
|
1755
|
+
annotations.delete(id);
|
|
1756
|
+
if (annotation.sessionId) {
|
|
1757
|
+
const event = eventBus.emit("annotation.deleted", annotation.sessionId, annotation);
|
|
1758
|
+
events.push(event);
|
|
1759
|
+
}
|
|
1760
|
+
return annotation;
|
|
1761
|
+
},
|
|
1762
|
+
// -- Annotations V2 (Vue schema) ------------------------------------------
|
|
1763
|
+
addAnnotationV2(sessionId, data) {
|
|
1764
|
+
const session = sessions.get(sessionId);
|
|
1765
|
+
if (!session) return void 0;
|
|
1766
|
+
const existing = annotationsV2.get(data.id);
|
|
1767
|
+
if (existing) return existing;
|
|
1768
|
+
const annotation = {
|
|
1769
|
+
...data,
|
|
1770
|
+
sessionId,
|
|
1771
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1772
|
+
};
|
|
1773
|
+
annotationsV2.set(annotation.id, annotation);
|
|
1774
|
+
return annotation;
|
|
1775
|
+
},
|
|
1776
|
+
getAnnotationV2(id) {
|
|
1777
|
+
return annotationsV2.get(id);
|
|
1778
|
+
},
|
|
1779
|
+
updateAnnotationV2(id, data) {
|
|
1780
|
+
const annotation = annotationsV2.get(id);
|
|
1781
|
+
if (!annotation) return void 0;
|
|
1782
|
+
Object.assign(annotation, data, { updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1783
|
+
return annotation;
|
|
1784
|
+
},
|
|
1785
|
+
getSessionAnnotationsV2(sessionId) {
|
|
1786
|
+
return Array.from(annotationsV2.values()).filter(
|
|
1787
|
+
(a) => a.sessionId === sessionId
|
|
1788
|
+
);
|
|
1789
|
+
},
|
|
1790
|
+
deleteAnnotationV2(id) {
|
|
1791
|
+
const annotation = annotationsV2.get(id);
|
|
1792
|
+
if (!annotation) return void 0;
|
|
1793
|
+
annotationsV2.delete(id);
|
|
1794
|
+
return annotation;
|
|
1795
|
+
},
|
|
1796
|
+
getEventsSince(sessionId, sequence) {
|
|
1797
|
+
return events.filter(
|
|
1798
|
+
(e) => e.sessionId === sessionId && e.sequence > sequence
|
|
1799
|
+
);
|
|
1800
|
+
},
|
|
1801
|
+
close() {
|
|
1802
|
+
sessions.clear();
|
|
1803
|
+
annotations.clear();
|
|
1804
|
+
annotationsV2.clear();
|
|
1805
|
+
events.length = 0;
|
|
1806
|
+
}
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
function createSession(url, projectId) {
|
|
1810
|
+
return getStore().createSession(url, projectId);
|
|
1811
|
+
}
|
|
1812
|
+
function getSession(id) {
|
|
1813
|
+
return getStore().getSession(id);
|
|
1814
|
+
}
|
|
1815
|
+
function getSessionWithAnnotations(id) {
|
|
1816
|
+
return getStore().getSessionWithAnnotations(id);
|
|
1817
|
+
}
|
|
1818
|
+
function updateSessionStatus(id, status) {
|
|
1819
|
+
return getStore().updateSessionStatus(id, status);
|
|
1820
|
+
}
|
|
1821
|
+
function listSessions() {
|
|
1822
|
+
return getStore().listSessions();
|
|
1823
|
+
}
|
|
1824
|
+
function addAnnotation(sessionId, data) {
|
|
1825
|
+
return getStore().addAnnotation(sessionId, data);
|
|
1826
|
+
}
|
|
1827
|
+
function getAnnotation(id) {
|
|
1828
|
+
return getStore().getAnnotation(id);
|
|
1829
|
+
}
|
|
1830
|
+
function updateAnnotation(id, data) {
|
|
1831
|
+
return getStore().updateAnnotation(id, data);
|
|
1832
|
+
}
|
|
1833
|
+
function updateAnnotationStatus(id, status, resolvedBy) {
|
|
1834
|
+
return getStore().updateAnnotationStatus(id, status, resolvedBy);
|
|
1835
|
+
}
|
|
1836
|
+
function addThreadMessage(annotationId, role, content) {
|
|
1837
|
+
return getStore().addThreadMessage(annotationId, role, content);
|
|
1838
|
+
}
|
|
1839
|
+
function getPendingAnnotations(sessionId) {
|
|
1840
|
+
return getStore().getPendingAnnotations(sessionId);
|
|
1841
|
+
}
|
|
1842
|
+
function getSessionAnnotations(sessionId) {
|
|
1843
|
+
return getStore().getSessionAnnotations(sessionId);
|
|
1844
|
+
}
|
|
1845
|
+
function deleteAnnotation(id) {
|
|
1846
|
+
return getStore().deleteAnnotation(id);
|
|
1847
|
+
}
|
|
1848
|
+
function getSessionWithAnnotationsV2(id) {
|
|
1849
|
+
return getStore().getSessionWithAnnotationsV2(id);
|
|
1850
|
+
}
|
|
1851
|
+
function addAnnotationV2(sessionId, data) {
|
|
1852
|
+
return getStore().addAnnotationV2(sessionId, data);
|
|
1853
|
+
}
|
|
1854
|
+
function getAnnotationV2(id) {
|
|
1855
|
+
return getStore().getAnnotationV2(id);
|
|
1856
|
+
}
|
|
1857
|
+
function updateAnnotationV2(id, data) {
|
|
1858
|
+
return getStore().updateAnnotationV2(id, data);
|
|
1859
|
+
}
|
|
1860
|
+
function getSessionAnnotationsV2(sessionId) {
|
|
1861
|
+
return getStore().getSessionAnnotationsV2(sessionId);
|
|
1862
|
+
}
|
|
1863
|
+
function deleteAnnotationV2(id) {
|
|
1864
|
+
return getStore().deleteAnnotationV2(id);
|
|
1865
|
+
}
|
|
1866
|
+
function getEventsSince(sessionId, sequence) {
|
|
1867
|
+
return getStore().getEventsSince(sessionId, sequence);
|
|
1868
|
+
}
|
|
1869
|
+
function clearAll() {
|
|
1870
|
+
getStore().close();
|
|
1871
|
+
_store = null;
|
|
1872
|
+
}
|
|
1873
|
+
var _store, store;
|
|
1874
|
+
var init_store = __esm({
|
|
1875
|
+
"src/server/store.ts"() {
|
|
1876
|
+
"use strict";
|
|
1877
|
+
init_events();
|
|
1878
|
+
_store = null;
|
|
1879
|
+
store = {
|
|
1880
|
+
get instance() {
|
|
1881
|
+
return getStore();
|
|
1882
|
+
}
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
});
|
|
1886
|
+
|
|
1887
|
+
// src/server/http.ts
|
|
1888
|
+
function log(message) {
|
|
1889
|
+
process.stderr.write(message + "\n");
|
|
1890
|
+
}
|
|
1891
|
+
function setCloudApiKey(key) {
|
|
1892
|
+
cloudApiKey = key;
|
|
1893
|
+
}
|
|
1894
|
+
function isCloudMode() {
|
|
1895
|
+
return !!cloudApiKey;
|
|
1896
|
+
}
|
|
1897
|
+
function createMcpSession() {
|
|
1898
|
+
const transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
1899
|
+
sessionIdGenerator: () => crypto.randomUUID()
|
|
1900
|
+
});
|
|
1901
|
+
const server = new import_server2.Server(
|
|
1902
|
+
{ name: "agentation", version: "0.0.1" },
|
|
1903
|
+
{ capabilities: { tools: {} } }
|
|
1904
|
+
);
|
|
1905
|
+
server.setRequestHandler(import_types2.ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
1906
|
+
server.setRequestHandler(import_types2.CallToolRequestSchema, async (req) => {
|
|
1907
|
+
try {
|
|
1908
|
+
return await handleTool(req.params.name, req.params.arguments);
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1911
|
+
return error(message);
|
|
1912
|
+
}
|
|
1913
|
+
});
|
|
1914
|
+
server.connect(transport);
|
|
1915
|
+
return { server, transport };
|
|
1916
|
+
}
|
|
1917
|
+
function getWebhookUrls() {
|
|
1918
|
+
const urls = [];
|
|
1919
|
+
const singleUrl = process.env.AGENTATION_WEBHOOK_URL;
|
|
1920
|
+
if (singleUrl) {
|
|
1921
|
+
urls.push(singleUrl.trim());
|
|
1922
|
+
}
|
|
1923
|
+
const multipleUrls = process.env.AGENTATION_WEBHOOKS;
|
|
1924
|
+
if (multipleUrls) {
|
|
1925
|
+
const parsed = multipleUrls.split(",").map((url) => url.trim()).filter((url) => url.length > 0);
|
|
1926
|
+
urls.push(...parsed);
|
|
1927
|
+
}
|
|
1928
|
+
return urls;
|
|
1929
|
+
}
|
|
1930
|
+
function sendWebhooks(actionRequest) {
|
|
1931
|
+
const webhookUrls = getWebhookUrls();
|
|
1932
|
+
if (webhookUrls.length === 0) {
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
const payload = JSON.stringify(actionRequest);
|
|
1936
|
+
for (const url of webhookUrls) {
|
|
1937
|
+
fetch(url, {
|
|
1938
|
+
method: "POST",
|
|
1939
|
+
headers: {
|
|
1940
|
+
"Content-Type": "application/json",
|
|
1941
|
+
"User-Agent": "Agentation-Webhook/1.0"
|
|
1942
|
+
},
|
|
1943
|
+
body: payload
|
|
1944
|
+
}).then((res) => {
|
|
1945
|
+
log(
|
|
1946
|
+
`[Webhook] POST ${url} -> ${res.status} ${res.statusText}`
|
|
1947
|
+
);
|
|
1948
|
+
}).catch((err) => {
|
|
1949
|
+
console.error(`[Webhook] POST ${url} failed:`, err.message);
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
log(
|
|
1953
|
+
`[Webhook] Fired ${webhookUrls.length} webhook(s) for session ${actionRequest.sessionId}`
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
async function parseBody(req) {
|
|
1957
|
+
return new Promise((resolve, reject) => {
|
|
1958
|
+
let body = "";
|
|
1959
|
+
req.on("data", (chunk) => body += chunk);
|
|
1960
|
+
req.on("end", () => {
|
|
1961
|
+
try {
|
|
1962
|
+
resolve(body ? JSON.parse(body) : {});
|
|
1963
|
+
} catch {
|
|
1964
|
+
reject(new Error("Invalid JSON"));
|
|
1965
|
+
}
|
|
1966
|
+
});
|
|
1967
|
+
req.on("error", reject);
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
function sendJson(res, status, data) {
|
|
1971
|
+
res.writeHead(status, {
|
|
1972
|
+
"Content-Type": "application/json",
|
|
1973
|
+
"Access-Control-Allow-Origin": "*",
|
|
1974
|
+
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
1975
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
1976
|
+
});
|
|
1977
|
+
res.end(JSON.stringify(data));
|
|
1978
|
+
}
|
|
1979
|
+
function sendError(res, status, message) {
|
|
1980
|
+
sendJson(res, status, { error: message });
|
|
1981
|
+
}
|
|
1982
|
+
function handleCors(res) {
|
|
1983
|
+
res.writeHead(204, {
|
|
1984
|
+
"Access-Control-Allow-Origin": "*",
|
|
1985
|
+
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
1986
|
+
"Access-Control-Allow-Headers": "Content-Type, Accept, Mcp-Session-Id",
|
|
1987
|
+
"Access-Control-Expose-Headers": "Mcp-Session-Id",
|
|
1988
|
+
"Access-Control-Max-Age": "86400"
|
|
1989
|
+
});
|
|
1990
|
+
res.end();
|
|
1991
|
+
}
|
|
1992
|
+
async function proxyToCloud(req, res, pathname) {
|
|
1993
|
+
const method = req.method || "GET";
|
|
1994
|
+
const cloudUrl = `${CLOUD_API_URL}${pathname}`;
|
|
1995
|
+
const headers = {
|
|
1996
|
+
"x-api-key": cloudApiKey
|
|
1997
|
+
};
|
|
1998
|
+
if (req.headers["content-type"]) {
|
|
1999
|
+
headers["Content-Type"] = req.headers["content-type"];
|
|
2000
|
+
}
|
|
2001
|
+
let body;
|
|
2002
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
2003
|
+
body = await new Promise((resolve, reject) => {
|
|
2004
|
+
let data = "";
|
|
2005
|
+
req.on("data", (chunk) => data += chunk);
|
|
2006
|
+
req.on("end", () => resolve(data));
|
|
2007
|
+
req.on("error", reject);
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
try {
|
|
2011
|
+
const cloudRes = await fetch(cloudUrl, {
|
|
2012
|
+
method,
|
|
2013
|
+
headers,
|
|
2014
|
+
body
|
|
2015
|
+
});
|
|
2016
|
+
if (cloudRes.headers.get("content-type")?.includes("text/event-stream")) {
|
|
2017
|
+
res.writeHead(cloudRes.status, {
|
|
2018
|
+
"Content-Type": "text/event-stream",
|
|
2019
|
+
"Cache-Control": "no-cache",
|
|
2020
|
+
Connection: "keep-alive",
|
|
2021
|
+
"Access-Control-Allow-Origin": "*"
|
|
2022
|
+
});
|
|
2023
|
+
const reader = cloudRes.body?.getReader();
|
|
2024
|
+
if (reader) {
|
|
2025
|
+
const pump = async () => {
|
|
2026
|
+
while (true) {
|
|
2027
|
+
const { done, value } = await reader.read();
|
|
2028
|
+
if (done) break;
|
|
2029
|
+
res.write(value);
|
|
2030
|
+
}
|
|
2031
|
+
res.end();
|
|
2032
|
+
};
|
|
2033
|
+
pump().catch(() => res.end());
|
|
2034
|
+
req.on("close", () => {
|
|
2035
|
+
reader.cancel();
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
const data = await cloudRes.text();
|
|
2041
|
+
res.writeHead(cloudRes.status, {
|
|
2042
|
+
"Content-Type": cloudRes.headers.get("content-type") || "application/json",
|
|
2043
|
+
"Access-Control-Allow-Origin": "*",
|
|
2044
|
+
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
2045
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
2046
|
+
});
|
|
2047
|
+
res.end(data);
|
|
2048
|
+
} catch (err) {
|
|
2049
|
+
console.error("[Cloud Proxy] Error:", err);
|
|
2050
|
+
sendError(res, 502, `Cloud proxy error: ${err.message}`);
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
function sendSSEEvent(res, event) {
|
|
2054
|
+
res.write(`event: ${event.type}
|
|
2055
|
+
`);
|
|
2056
|
+
res.write(`id: ${event.sequence}
|
|
2057
|
+
`);
|
|
2058
|
+
res.write(`data: ${JSON.stringify(event)}
|
|
2059
|
+
|
|
2060
|
+
`);
|
|
2061
|
+
}
|
|
2062
|
+
async function handleMcp(req, res) {
|
|
2063
|
+
const method = req.method || "GET";
|
|
2064
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
2065
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
2066
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
2067
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Mcp-Session-Id");
|
|
2068
|
+
res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");
|
|
2069
|
+
if (method === "POST") {
|
|
2070
|
+
let transport;
|
|
2071
|
+
if (sessionId) {
|
|
2072
|
+
if (!mcpTransports.has(sessionId)) {
|
|
2073
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
2074
|
+
res.end(JSON.stringify({
|
|
2075
|
+
jsonrpc: "2.0",
|
|
2076
|
+
error: { code: -32e3, message: "Session not found. Please re-initialize." },
|
|
2077
|
+
id: null
|
|
2078
|
+
}));
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
transport = mcpTransports.get(sessionId);
|
|
2082
|
+
} else {
|
|
2083
|
+
const { transport: newTransport } = createMcpSession();
|
|
2084
|
+
transport = newTransport;
|
|
2085
|
+
}
|
|
2086
|
+
try {
|
|
2087
|
+
const body = await new Promise((resolve, reject) => {
|
|
2088
|
+
let data = "";
|
|
2089
|
+
req.on("data", (chunk) => data += chunk);
|
|
2090
|
+
req.on("end", () => resolve(data));
|
|
2091
|
+
req.on("error", reject);
|
|
2092
|
+
});
|
|
2093
|
+
const parsedBody = body ? JSON.parse(body) : void 0;
|
|
2094
|
+
await transport.handleRequest(req, res, parsedBody);
|
|
2095
|
+
const newSessionId = transport.sessionId;
|
|
2096
|
+
if (newSessionId && !mcpTransports.has(newSessionId)) {
|
|
2097
|
+
mcpTransports.set(newSessionId, transport);
|
|
2098
|
+
log(`[MCP HTTP] New session created: ${newSessionId}`);
|
|
2099
|
+
}
|
|
2100
|
+
} catch (err) {
|
|
2101
|
+
console.error("[MCP HTTP] Error handling request:", err);
|
|
2102
|
+
if (!res.headersSent) {
|
|
2103
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
2104
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
if (method === "GET") {
|
|
2110
|
+
if (!sessionId || !mcpTransports.has(sessionId)) {
|
|
2111
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2112
|
+
res.end(JSON.stringify({ error: "Missing or invalid Mcp-Session-Id" }));
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
const transport = mcpTransports.get(sessionId);
|
|
2116
|
+
try {
|
|
2117
|
+
await transport.handleRequest(req, res);
|
|
2118
|
+
} catch (err) {
|
|
2119
|
+
console.error("[MCP HTTP] Error handling SSE:", err);
|
|
2120
|
+
if (!res.headersSent) {
|
|
2121
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
2122
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
if (method === "DELETE") {
|
|
2128
|
+
if (sessionId && mcpTransports.has(sessionId)) {
|
|
2129
|
+
const transport = mcpTransports.get(sessionId);
|
|
2130
|
+
await transport.close();
|
|
2131
|
+
mcpTransports.delete(sessionId);
|
|
2132
|
+
res.writeHead(204);
|
|
2133
|
+
res.end();
|
|
2134
|
+
} else {
|
|
2135
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
2136
|
+
res.end(JSON.stringify({ error: "Session not found" }));
|
|
2137
|
+
}
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
2141
|
+
res.end(JSON.stringify({ error: "Method not allowed" }));
|
|
2142
|
+
}
|
|
2143
|
+
function matchRoute(method, pathname) {
|
|
2144
|
+
for (const route of routes) {
|
|
2145
|
+
if (route.method !== method) continue;
|
|
2146
|
+
const match = pathname.match(route.pattern);
|
|
2147
|
+
if (match) {
|
|
2148
|
+
const params = {};
|
|
2149
|
+
route.paramNames.forEach((name, i) => {
|
|
2150
|
+
params[name] = match[i + 1];
|
|
2151
|
+
});
|
|
2152
|
+
return { handler: route.handler, params };
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
return null;
|
|
2156
|
+
}
|
|
2157
|
+
function startHttpServer(port, apiKey2) {
|
|
2158
|
+
if (apiKey2) {
|
|
2159
|
+
setCloudApiKey(apiKey2);
|
|
2160
|
+
}
|
|
2161
|
+
const server = (0, import_http.createServer)(async (req, res) => {
|
|
2162
|
+
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
2163
|
+
const pathname = url.pathname;
|
|
2164
|
+
const method = req.method || "GET";
|
|
2165
|
+
if (method !== "OPTIONS" && pathname !== "/health") {
|
|
2166
|
+
log(`[HTTP] ${method} ${pathname}`);
|
|
2167
|
+
}
|
|
2168
|
+
if (method === "OPTIONS") {
|
|
2169
|
+
return handleCors(res);
|
|
2170
|
+
}
|
|
2171
|
+
if (pathname === "/health" && method === "GET") {
|
|
2172
|
+
return sendJson(res, 200, { status: "ok", mode: isCloudMode() ? "cloud" : "local" });
|
|
2173
|
+
}
|
|
2174
|
+
if (pathname === "/status" && method === "GET") {
|
|
2175
|
+
const webhookUrls = getWebhookUrls();
|
|
2176
|
+
return sendJson(res, 200, {
|
|
2177
|
+
mode: isCloudMode() ? "cloud" : "local",
|
|
2178
|
+
webhooksConfigured: webhookUrls.length > 0,
|
|
2179
|
+
webhookCount: webhookUrls.length,
|
|
2180
|
+
activeListeners: sseConnections.size,
|
|
2181
|
+
agentListeners: agentConnections.size
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
if (pathname === "/mcp") {
|
|
2185
|
+
return handleMcp(req, res);
|
|
2186
|
+
}
|
|
2187
|
+
if (isCloudMode()) {
|
|
2188
|
+
return proxyToCloud(req, res, pathname + url.search);
|
|
2189
|
+
}
|
|
2190
|
+
const match = matchRoute(method, pathname);
|
|
2191
|
+
if (!match) {
|
|
2192
|
+
return sendError(res, 404, "Not found");
|
|
2193
|
+
}
|
|
2194
|
+
try {
|
|
2195
|
+
await match.handler(req, res, match.params);
|
|
2196
|
+
} catch (err) {
|
|
2197
|
+
console.error("Request error:", err);
|
|
2198
|
+
sendError(res, 500, "Internal server error");
|
|
2199
|
+
}
|
|
2200
|
+
});
|
|
2201
|
+
server.on("error", (err) => {
|
|
2202
|
+
if (err.code === "EADDRINUSE") {
|
|
2203
|
+
log(`[HTTP] Port ${port} already in use \u2014 skipping HTTP server (MCP stdio still active)`);
|
|
2204
|
+
} else {
|
|
2205
|
+
log(`[HTTP] Server error: ${err.message}`);
|
|
2206
|
+
}
|
|
2207
|
+
});
|
|
2208
|
+
server.listen(port, () => {
|
|
2209
|
+
if (isCloudMode()) {
|
|
2210
|
+
log(`[HTTP] Agentation server listening on http://localhost:${port} (cloud mode)`);
|
|
2211
|
+
} else {
|
|
2212
|
+
log(`[HTTP] Agentation server listening on http://localhost:${port}`);
|
|
2213
|
+
}
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
var import_http, import_streamableHttp, import_server2, import_types2, cloudApiKey, CLOUD_API_URL, sseConnections, agentConnections, mcpTransports, createSessionHandler, listSessionsHandler, getSessionHandler, addAnnotationHandler, updateAnnotationHandler, getAnnotationHandler, deleteAnnotationHandler, getSessionV2Handler, addAnnotationV2Handler, updateAnnotationV2Handler, getAnnotationV2Handler, deleteAnnotationV2Handler, getPendingHandler, getAllPendingHandler, requestActionHandler, addThreadHandler, sseHandler, globalSseHandler, routes;
|
|
2217
|
+
var init_http = __esm({
|
|
2218
|
+
"src/server/http.ts"() {
|
|
2219
|
+
"use strict";
|
|
2220
|
+
import_http = require("http");
|
|
2221
|
+
import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
2222
|
+
import_server2 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
2223
|
+
import_types2 = require("@modelcontextprotocol/sdk/types.js");
|
|
2224
|
+
init_mcp();
|
|
2225
|
+
init_store();
|
|
2226
|
+
init_events();
|
|
2227
|
+
CLOUD_API_URL = "https://agentation-mcp-cloud.vercel.app/api";
|
|
2228
|
+
sseConnections = /* @__PURE__ */ new Set();
|
|
2229
|
+
agentConnections = /* @__PURE__ */ new Set();
|
|
2230
|
+
mcpTransports = /* @__PURE__ */ new Map();
|
|
2231
|
+
createSessionHandler = async (req, res) => {
|
|
2232
|
+
try {
|
|
2233
|
+
const body = await parseBody(req);
|
|
2234
|
+
if (!body.url) {
|
|
2235
|
+
return sendError(res, 400, "url is required");
|
|
2236
|
+
}
|
|
2237
|
+
const session = createSession(body.url, body.projectId);
|
|
2238
|
+
sendJson(res, 201, session);
|
|
2239
|
+
} catch (err) {
|
|
2240
|
+
sendError(res, 400, err.message);
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2243
|
+
listSessionsHandler = async (_req, res) => {
|
|
2244
|
+
const sessions = listSessions();
|
|
2245
|
+
sendJson(res, 200, sessions);
|
|
2246
|
+
};
|
|
2247
|
+
getSessionHandler = async (_req, res, params) => {
|
|
2248
|
+
const session = getSessionWithAnnotations(params.id);
|
|
2249
|
+
if (!session) {
|
|
2250
|
+
return sendError(res, 404, "Session not found");
|
|
2251
|
+
}
|
|
2252
|
+
sendJson(res, 200, session);
|
|
2253
|
+
};
|
|
2254
|
+
addAnnotationHandler = async (req, res, params) => {
|
|
2255
|
+
try {
|
|
2256
|
+
const body = await parseBody(req);
|
|
2257
|
+
if (!body.comment || !body.element || !body.elementPath) {
|
|
2258
|
+
return sendError(res, 400, "comment, element, and elementPath are required");
|
|
2259
|
+
}
|
|
2260
|
+
const annotation = addAnnotation(params.id, body);
|
|
2261
|
+
if (!annotation) {
|
|
2262
|
+
return sendError(res, 404, "Session not found");
|
|
2263
|
+
}
|
|
2264
|
+
sendJson(res, 201, annotation);
|
|
2265
|
+
} catch (err) {
|
|
2266
|
+
sendError(res, 400, err.message);
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
updateAnnotationHandler = async (req, res, params) => {
|
|
2270
|
+
try {
|
|
2271
|
+
const body = await parseBody(req);
|
|
2272
|
+
const existing = getAnnotation(params.id);
|
|
2273
|
+
if (!existing) {
|
|
2274
|
+
return sendError(res, 404, "Annotation not found");
|
|
2275
|
+
}
|
|
2276
|
+
const annotation = updateAnnotation(params.id, body);
|
|
2277
|
+
sendJson(res, 200, annotation);
|
|
2278
|
+
} catch (err) {
|
|
2279
|
+
sendError(res, 400, err.message);
|
|
2280
|
+
}
|
|
2281
|
+
};
|
|
2282
|
+
getAnnotationHandler = async (_req, res, params) => {
|
|
2283
|
+
const annotation = getAnnotation(params.id);
|
|
2284
|
+
if (!annotation) {
|
|
2285
|
+
return sendError(res, 404, "Annotation not found");
|
|
2286
|
+
}
|
|
2287
|
+
sendJson(res, 200, annotation);
|
|
2288
|
+
};
|
|
2289
|
+
deleteAnnotationHandler = async (_req, res, params) => {
|
|
2290
|
+
const annotation = deleteAnnotation(params.id);
|
|
2291
|
+
if (!annotation) {
|
|
2292
|
+
return sendError(res, 404, "Annotation not found");
|
|
2293
|
+
}
|
|
2294
|
+
sendJson(res, 200, { deleted: true, annotationId: params.id });
|
|
2295
|
+
};
|
|
2296
|
+
getSessionV2Handler = async (_req, res, params) => {
|
|
2297
|
+
const session = getSessionWithAnnotationsV2(params.id);
|
|
2298
|
+
if (!session) {
|
|
2299
|
+
return sendError(res, 404, "Session not found");
|
|
2300
|
+
}
|
|
2301
|
+
sendJson(res, 200, session);
|
|
2302
|
+
};
|
|
2303
|
+
addAnnotationV2Handler = async (req, res, params) => {
|
|
2304
|
+
try {
|
|
2305
|
+
const body = await parseBody(req);
|
|
2306
|
+
if (!body.id || !body.comment || !body.elementSelector || !body.source) {
|
|
2307
|
+
return sendError(res, 400, "id, comment, elementSelector, and source are required");
|
|
2308
|
+
}
|
|
2309
|
+
const existing = getAnnotationV2(body.id);
|
|
2310
|
+
if (existing) {
|
|
2311
|
+
if (existing.sessionId !== params.id) {
|
|
2312
|
+
return sendError(res, 409, "Annotation ID already exists in a different session");
|
|
2313
|
+
}
|
|
2314
|
+
return sendJson(res, 200, existing);
|
|
2315
|
+
}
|
|
2316
|
+
const annotation = addAnnotationV2(params.id, body);
|
|
2317
|
+
if (!annotation) {
|
|
2318
|
+
return sendError(res, 404, "Session not found");
|
|
2319
|
+
}
|
|
2320
|
+
sendJson(res, 201, annotation);
|
|
2321
|
+
} catch (err) {
|
|
2322
|
+
sendError(res, 400, err.message);
|
|
2323
|
+
}
|
|
2324
|
+
};
|
|
2325
|
+
updateAnnotationV2Handler = async (req, res, params) => {
|
|
2326
|
+
try {
|
|
2327
|
+
const body = await parseBody(req);
|
|
2328
|
+
const existing = getAnnotationV2(params.id);
|
|
2329
|
+
if (!existing) {
|
|
2330
|
+
return sendError(res, 404, "Annotation not found");
|
|
2331
|
+
}
|
|
2332
|
+
const annotation = updateAnnotationV2(params.id, body);
|
|
2333
|
+
sendJson(res, 200, annotation);
|
|
2334
|
+
} catch (err) {
|
|
2335
|
+
sendError(res, 400, err.message);
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
getAnnotationV2Handler = async (_req, res, params) => {
|
|
2339
|
+
const annotation = getAnnotationV2(params.id);
|
|
2340
|
+
if (!annotation) {
|
|
2341
|
+
return sendError(res, 404, "Annotation not found");
|
|
2342
|
+
}
|
|
2343
|
+
sendJson(res, 200, annotation);
|
|
2344
|
+
};
|
|
2345
|
+
deleteAnnotationV2Handler = async (_req, res, params) => {
|
|
2346
|
+
const annotation = deleteAnnotationV2(params.id);
|
|
2347
|
+
if (!annotation) {
|
|
2348
|
+
return sendError(res, 404, "Annotation not found");
|
|
2349
|
+
}
|
|
2350
|
+
sendJson(res, 200, { deleted: true, annotationId: params.id });
|
|
2351
|
+
};
|
|
2352
|
+
getPendingHandler = async (_req, res, params) => {
|
|
2353
|
+
const pending = getPendingAnnotations(params.id);
|
|
2354
|
+
sendJson(res, 200, { count: pending.length, annotations: pending });
|
|
2355
|
+
};
|
|
2356
|
+
getAllPendingHandler = async (_req, res) => {
|
|
2357
|
+
const sessions = listSessions();
|
|
2358
|
+
const allPending = sessions.flatMap((session) => getPendingAnnotations(session.id));
|
|
2359
|
+
sendJson(res, 200, { count: allPending.length, annotations: allPending });
|
|
2360
|
+
};
|
|
2361
|
+
requestActionHandler = async (req, res, params) => {
|
|
2362
|
+
try {
|
|
2363
|
+
const sessionId = params.id;
|
|
2364
|
+
const body = await parseBody(req);
|
|
2365
|
+
const session = getSessionWithAnnotations(sessionId);
|
|
2366
|
+
if (!session) {
|
|
2367
|
+
return sendError(res, 404, "Session not found");
|
|
2368
|
+
}
|
|
2369
|
+
if (!body.output) {
|
|
2370
|
+
return sendError(res, 400, "output is required");
|
|
2371
|
+
}
|
|
2372
|
+
const actionRequest = {
|
|
2373
|
+
sessionId,
|
|
2374
|
+
annotations: session.annotations,
|
|
2375
|
+
output: body.output,
|
|
2376
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2377
|
+
};
|
|
2378
|
+
eventBus.emit("action.requested", sessionId, actionRequest);
|
|
2379
|
+
const webhookUrls = getWebhookUrls();
|
|
2380
|
+
sendWebhooks(actionRequest);
|
|
2381
|
+
const agentListeners = agentConnections.size;
|
|
2382
|
+
const webhooks = webhookUrls.length;
|
|
2383
|
+
sendJson(res, 200, {
|
|
2384
|
+
success: true,
|
|
2385
|
+
annotationCount: session.annotations.length,
|
|
2386
|
+
delivered: {
|
|
2387
|
+
sseListeners: agentListeners,
|
|
2388
|
+
webhooks,
|
|
2389
|
+
total: agentListeners + webhooks
|
|
2390
|
+
}
|
|
2391
|
+
});
|
|
2392
|
+
} catch (err) {
|
|
2393
|
+
sendError(res, 400, err.message);
|
|
2394
|
+
}
|
|
2395
|
+
};
|
|
2396
|
+
addThreadHandler = async (req, res, params) => {
|
|
2397
|
+
try {
|
|
2398
|
+
const body = await parseBody(req);
|
|
2399
|
+
if (!body.role || !body.content) {
|
|
2400
|
+
return sendError(res, 400, "role and content are required");
|
|
2401
|
+
}
|
|
2402
|
+
const annotation = addThreadMessage(params.id, body.role, body.content);
|
|
2403
|
+
if (!annotation) {
|
|
2404
|
+
return sendError(res, 404, "Annotation not found");
|
|
2405
|
+
}
|
|
2406
|
+
sendJson(res, 201, annotation);
|
|
2407
|
+
} catch (err) {
|
|
2408
|
+
sendError(res, 400, err.message);
|
|
2409
|
+
}
|
|
2410
|
+
};
|
|
2411
|
+
sseHandler = async (req, res, params) => {
|
|
2412
|
+
const sessionId = params.id;
|
|
2413
|
+
const url = new URL(req.url || "/", "http://localhost");
|
|
2414
|
+
const isAgent = url.searchParams.get("agent") === "true";
|
|
2415
|
+
const session = getSessionWithAnnotations(sessionId);
|
|
2416
|
+
if (!session) {
|
|
2417
|
+
return sendError(res, 404, "Session not found");
|
|
2418
|
+
}
|
|
2419
|
+
res.writeHead(200, {
|
|
2420
|
+
"Content-Type": "text/event-stream",
|
|
2421
|
+
"Cache-Control": "no-cache",
|
|
2422
|
+
Connection: "keep-alive",
|
|
2423
|
+
"Access-Control-Allow-Origin": "*"
|
|
2424
|
+
});
|
|
2425
|
+
sseConnections.add(res);
|
|
2426
|
+
if (isAgent) {
|
|
2427
|
+
agentConnections.add(res);
|
|
2428
|
+
}
|
|
2429
|
+
res.write(": connected\n\n");
|
|
2430
|
+
const lastEventId = req.headers["last-event-id"];
|
|
2431
|
+
if (lastEventId) {
|
|
2432
|
+
const lastSequence = parseInt(lastEventId, 10);
|
|
2433
|
+
if (!isNaN(lastSequence)) {
|
|
2434
|
+
const missedEvents = getEventsSince(sessionId, lastSequence);
|
|
2435
|
+
for (const event of missedEvents) {
|
|
2436
|
+
sendSSEEvent(res, event);
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
const unsubscribe = eventBus.subscribeToSession(sessionId, (event) => {
|
|
2441
|
+
sendSSEEvent(res, event);
|
|
2442
|
+
});
|
|
2443
|
+
const keepAlive = setInterval(() => {
|
|
2444
|
+
res.write(": ping\n\n");
|
|
2445
|
+
}, 3e4);
|
|
2446
|
+
req.on("close", () => {
|
|
2447
|
+
clearInterval(keepAlive);
|
|
2448
|
+
unsubscribe();
|
|
2449
|
+
sseConnections.delete(res);
|
|
2450
|
+
agentConnections.delete(res);
|
|
2451
|
+
});
|
|
2452
|
+
};
|
|
2453
|
+
globalSseHandler = async (req, res) => {
|
|
2454
|
+
const url = new URL(req.url || "/", "http://localhost");
|
|
2455
|
+
const domain = url.searchParams.get("domain");
|
|
2456
|
+
const isAgent = url.searchParams.get("agent") === "true";
|
|
2457
|
+
res.writeHead(200, {
|
|
2458
|
+
"Content-Type": "text/event-stream",
|
|
2459
|
+
"Cache-Control": "no-cache",
|
|
2460
|
+
Connection: "keep-alive",
|
|
2461
|
+
"Access-Control-Allow-Origin": "*"
|
|
2462
|
+
});
|
|
2463
|
+
sseConnections.add(res);
|
|
2464
|
+
if (isAgent) {
|
|
2465
|
+
agentConnections.add(res);
|
|
2466
|
+
}
|
|
2467
|
+
res.write(`: connected${domain ? ` to domain ${domain}` : ""}
|
|
2468
|
+
|
|
2469
|
+
`);
|
|
2470
|
+
if (isAgent) {
|
|
2471
|
+
let syncCount = 0;
|
|
2472
|
+
const sessions = listSessions();
|
|
2473
|
+
for (const session of sessions) {
|
|
2474
|
+
try {
|
|
2475
|
+
if (domain) {
|
|
2476
|
+
const sessionHost = new URL(session.url).host;
|
|
2477
|
+
if (sessionHost !== domain) continue;
|
|
2478
|
+
}
|
|
2479
|
+
const pending = getPendingAnnotations(session.id);
|
|
2480
|
+
for (const annotation of pending) {
|
|
2481
|
+
sendSSEEvent(res, {
|
|
2482
|
+
type: "annotation.created",
|
|
2483
|
+
sessionId: session.id,
|
|
2484
|
+
timestamp: annotation.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
2485
|
+
sequence: 0,
|
|
2486
|
+
payload: annotation
|
|
2487
|
+
});
|
|
2488
|
+
syncCount++;
|
|
2489
|
+
}
|
|
2490
|
+
} catch {
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
res.write(`event: sync.complete
|
|
2494
|
+
data: ${JSON.stringify({ domain: domain ?? "all", count: syncCount, timestamp: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
2495
|
+
|
|
2496
|
+
`);
|
|
2497
|
+
}
|
|
2498
|
+
const unsubscribe = eventBus.subscribe((event) => {
|
|
2499
|
+
if (!domain) {
|
|
2500
|
+
sendSSEEvent(res, event);
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
const session = getSession(event.sessionId);
|
|
2504
|
+
if (session) {
|
|
2505
|
+
try {
|
|
2506
|
+
const sessionHost = new URL(session.url).host;
|
|
2507
|
+
if (sessionHost === domain) {
|
|
2508
|
+
sendSSEEvent(res, event);
|
|
2509
|
+
}
|
|
2510
|
+
} catch {
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
});
|
|
2514
|
+
const keepAlive = setInterval(() => {
|
|
2515
|
+
res.write(": ping\n\n");
|
|
2516
|
+
}, 3e4);
|
|
2517
|
+
req.on("close", () => {
|
|
2518
|
+
clearInterval(keepAlive);
|
|
2519
|
+
unsubscribe();
|
|
2520
|
+
sseConnections.delete(res);
|
|
2521
|
+
agentConnections.delete(res);
|
|
2522
|
+
});
|
|
2523
|
+
};
|
|
2524
|
+
routes = [
|
|
2525
|
+
// V2 routes (Vue schema) — must be before legacy routes for clean matching
|
|
2526
|
+
{
|
|
2527
|
+
method: "GET",
|
|
2528
|
+
pattern: /^\/v2\/sessions$/,
|
|
2529
|
+
handler: listSessionsHandler,
|
|
2530
|
+
paramNames: []
|
|
2531
|
+
},
|
|
2532
|
+
{
|
|
2533
|
+
method: "POST",
|
|
2534
|
+
pattern: /^\/v2\/sessions$/,
|
|
2535
|
+
handler: createSessionHandler,
|
|
2536
|
+
paramNames: []
|
|
2537
|
+
},
|
|
2538
|
+
{
|
|
2539
|
+
method: "GET",
|
|
2540
|
+
pattern: /^\/v2\/sessions\/([^/]+)$/,
|
|
2541
|
+
handler: getSessionV2Handler,
|
|
2542
|
+
paramNames: ["id"]
|
|
2543
|
+
},
|
|
2544
|
+
{
|
|
2545
|
+
method: "POST",
|
|
2546
|
+
pattern: /^\/v2\/sessions\/([^/]+)\/annotations$/,
|
|
2547
|
+
handler: addAnnotationV2Handler,
|
|
2548
|
+
paramNames: ["id"]
|
|
2549
|
+
},
|
|
2550
|
+
{
|
|
2551
|
+
method: "PATCH",
|
|
2552
|
+
pattern: /^\/v2\/annotations\/([^/]+)$/,
|
|
2553
|
+
handler: updateAnnotationV2Handler,
|
|
2554
|
+
paramNames: ["id"]
|
|
2555
|
+
},
|
|
2556
|
+
{
|
|
2557
|
+
method: "GET",
|
|
2558
|
+
pattern: /^\/v2\/annotations\/([^/]+)$/,
|
|
2559
|
+
handler: getAnnotationV2Handler,
|
|
2560
|
+
paramNames: ["id"]
|
|
2561
|
+
},
|
|
2562
|
+
{
|
|
2563
|
+
method: "DELETE",
|
|
2564
|
+
pattern: /^\/v2\/annotations\/([^/]+)$/,
|
|
2565
|
+
handler: deleteAnnotationV2Handler,
|
|
2566
|
+
paramNames: ["id"]
|
|
2567
|
+
},
|
|
2568
|
+
// Legacy routes (React schema)
|
|
2569
|
+
{
|
|
2570
|
+
method: "GET",
|
|
2571
|
+
pattern: /^\/events$/,
|
|
2572
|
+
handler: globalSseHandler,
|
|
2573
|
+
paramNames: []
|
|
2574
|
+
},
|
|
2575
|
+
{
|
|
2576
|
+
method: "GET",
|
|
2577
|
+
pattern: /^\/pending$/,
|
|
2578
|
+
handler: getAllPendingHandler,
|
|
2579
|
+
paramNames: []
|
|
2580
|
+
},
|
|
2581
|
+
{
|
|
2582
|
+
method: "GET",
|
|
2583
|
+
pattern: /^\/sessions$/,
|
|
2584
|
+
handler: listSessionsHandler,
|
|
2585
|
+
paramNames: []
|
|
2586
|
+
},
|
|
2587
|
+
{
|
|
2588
|
+
method: "POST",
|
|
2589
|
+
pattern: /^\/sessions$/,
|
|
2590
|
+
handler: createSessionHandler,
|
|
2591
|
+
paramNames: []
|
|
2592
|
+
},
|
|
2593
|
+
{
|
|
2594
|
+
method: "GET",
|
|
2595
|
+
pattern: /^\/sessions\/([^/]+)$/,
|
|
2596
|
+
handler: getSessionHandler,
|
|
2597
|
+
paramNames: ["id"]
|
|
2598
|
+
},
|
|
2599
|
+
{
|
|
2600
|
+
method: "GET",
|
|
2601
|
+
pattern: /^\/sessions\/([^/]+)\/events$/,
|
|
2602
|
+
handler: sseHandler,
|
|
2603
|
+
paramNames: ["id"]
|
|
2604
|
+
},
|
|
2605
|
+
{
|
|
2606
|
+
method: "GET",
|
|
2607
|
+
pattern: /^\/sessions\/([^/]+)\/pending$/,
|
|
2608
|
+
handler: getPendingHandler,
|
|
2609
|
+
paramNames: ["id"]
|
|
2610
|
+
},
|
|
2611
|
+
{
|
|
2612
|
+
method: "POST",
|
|
2613
|
+
pattern: /^\/sessions\/([^/]+)\/action$/,
|
|
2614
|
+
handler: requestActionHandler,
|
|
2615
|
+
paramNames: ["id"]
|
|
2616
|
+
},
|
|
2617
|
+
{
|
|
2618
|
+
method: "POST",
|
|
2619
|
+
pattern: /^\/sessions\/([^/]+)\/annotations$/,
|
|
2620
|
+
handler: addAnnotationHandler,
|
|
2621
|
+
paramNames: ["id"]
|
|
2622
|
+
},
|
|
2623
|
+
{
|
|
2624
|
+
method: "PATCH",
|
|
2625
|
+
pattern: /^\/annotations\/([^/]+)$/,
|
|
2626
|
+
handler: updateAnnotationHandler,
|
|
2627
|
+
paramNames: ["id"]
|
|
2628
|
+
},
|
|
2629
|
+
{
|
|
2630
|
+
method: "GET",
|
|
2631
|
+
pattern: /^\/annotations\/([^/]+)$/,
|
|
2632
|
+
handler: getAnnotationHandler,
|
|
2633
|
+
paramNames: ["id"]
|
|
2634
|
+
},
|
|
2635
|
+
{
|
|
2636
|
+
method: "DELETE",
|
|
2637
|
+
pattern: /^\/annotations\/([^/]+)$/,
|
|
2638
|
+
handler: deleteAnnotationHandler,
|
|
2639
|
+
paramNames: ["id"]
|
|
2640
|
+
},
|
|
2641
|
+
{
|
|
2642
|
+
method: "POST",
|
|
2643
|
+
pattern: /^\/annotations\/([^/]+)\/thread$/,
|
|
2644
|
+
handler: addThreadHandler,
|
|
2645
|
+
paramNames: ["id"]
|
|
2646
|
+
}
|
|
2647
|
+
];
|
|
2648
|
+
}
|
|
2649
|
+
});
|
|
2650
|
+
|
|
2651
|
+
// src/server/index.ts
|
|
2652
|
+
var server_exports = {};
|
|
2653
|
+
__export(server_exports, {
|
|
2654
|
+
addAnnotation: () => addAnnotation,
|
|
2655
|
+
addAnnotationV2: () => addAnnotationV2,
|
|
2656
|
+
addThreadMessage: () => addThreadMessage,
|
|
2657
|
+
clearAll: () => clearAll,
|
|
2658
|
+
createSession: () => createSession,
|
|
2659
|
+
deleteAnnotation: () => deleteAnnotation,
|
|
2660
|
+
deleteAnnotationV2: () => deleteAnnotationV2,
|
|
2661
|
+
getAnnotation: () => getAnnotation,
|
|
2662
|
+
getAnnotationV2: () => getAnnotationV2,
|
|
2663
|
+
getEventsSince: () => getEventsSince,
|
|
2664
|
+
getPendingAnnotations: () => getPendingAnnotations,
|
|
2665
|
+
getSession: () => getSession,
|
|
2666
|
+
getSessionAnnotations: () => getSessionAnnotations,
|
|
2667
|
+
getSessionAnnotationsV2: () => getSessionAnnotationsV2,
|
|
2668
|
+
getSessionWithAnnotations: () => getSessionWithAnnotations,
|
|
2669
|
+
getSessionWithAnnotationsV2: () => getSessionWithAnnotationsV2,
|
|
2670
|
+
getStore: () => getStore,
|
|
2671
|
+
listSessions: () => listSessions,
|
|
2672
|
+
setApiKey: () => setApiKey,
|
|
2673
|
+
setCloudApiKey: () => setCloudApiKey,
|
|
2674
|
+
startHttpServer: () => startHttpServer,
|
|
2675
|
+
startMcpServer: () => startMcpServer,
|
|
2676
|
+
store: () => store,
|
|
2677
|
+
updateAnnotation: () => updateAnnotation,
|
|
2678
|
+
updateAnnotationStatus: () => updateAnnotationStatus,
|
|
2679
|
+
updateAnnotationV2: () => updateAnnotationV2,
|
|
2680
|
+
updateSessionStatus: () => updateSessionStatus
|
|
2681
|
+
});
|
|
2682
|
+
var init_server = __esm({
|
|
2683
|
+
"src/server/index.ts"() {
|
|
2684
|
+
"use strict";
|
|
2685
|
+
init_http();
|
|
2686
|
+
init_mcp();
|
|
2687
|
+
init_http();
|
|
2688
|
+
init_mcp();
|
|
2689
|
+
init_store();
|
|
2690
|
+
}
|
|
2691
|
+
});
|
|
2692
|
+
|
|
2693
|
+
// src/cli.ts
|
|
2694
|
+
var readline = __toESM(require("readline"));
|
|
2695
|
+
var fs = __toESM(require("fs"));
|
|
2696
|
+
var path = __toESM(require("path"));
|
|
2697
|
+
var import_child_process = require("child_process");
|
|
2698
|
+
var command = process.argv[2];
|
|
2699
|
+
async function runInit() {
|
|
2700
|
+
const rl = readline.createInterface({
|
|
2701
|
+
input: process.stdin,
|
|
2702
|
+
output: process.stdout
|
|
2703
|
+
});
|
|
2704
|
+
const question = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
2705
|
+
console.log(`
|
|
2706
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
2707
|
+
\u2551 Agentation MCP Setup Wizard \u2551
|
|
2708
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
2709
|
+
`);
|
|
2710
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
2711
|
+
const claudeConfigPath = path.join(homeDir, ".claude.json");
|
|
2712
|
+
const hasClaudeConfig = fs.existsSync(claudeConfigPath);
|
|
2713
|
+
if (hasClaudeConfig) {
|
|
2714
|
+
console.log(`\u2713 Found Claude Code config at ${claudeConfigPath}`);
|
|
2715
|
+
} else {
|
|
2716
|
+
console.log(`\u25CB No Claude Code config found at ${claudeConfigPath}`);
|
|
2717
|
+
}
|
|
2718
|
+
console.log();
|
|
2719
|
+
console.log(`The Agentation MCP server allows Claude Code to receive`);
|
|
2720
|
+
console.log(`real-time annotations and respond to feedback.`);
|
|
2721
|
+
console.log();
|
|
2722
|
+
const setupMcp = await question(`Set up MCP server integration? [Y/n] `);
|
|
2723
|
+
const wantsMcp = setupMcp.toLowerCase() !== "n";
|
|
2724
|
+
if (wantsMcp) {
|
|
2725
|
+
let port = 4747;
|
|
2726
|
+
const portAnswer = await question(`HTTP server port [4747]: `);
|
|
2727
|
+
if (portAnswer && !isNaN(parseInt(portAnswer, 10))) {
|
|
2728
|
+
port = parseInt(portAnswer, 10);
|
|
2729
|
+
}
|
|
2730
|
+
const mcpArgs = port === 4747 ? ["mcp", "add", "agentation", "--", "npx", "agentation-vue-mcp", "server"] : ["mcp", "add", "agentation", "--", "npx", "agentation-vue-mcp", "server", "--port", String(port)];
|
|
2731
|
+
console.log();
|
|
2732
|
+
console.log(`Running: claude ${mcpArgs.join(" ")}`);
|
|
2733
|
+
try {
|
|
2734
|
+
const result = (0, import_child_process.spawn)("claude", mcpArgs, { stdio: "inherit" });
|
|
2735
|
+
await new Promise((resolve, reject) => {
|
|
2736
|
+
result.on("close", (code) => {
|
|
2737
|
+
if (code === 0) resolve();
|
|
2738
|
+
else reject(new Error(`claude mcp add exited with code ${code}`));
|
|
2739
|
+
});
|
|
2740
|
+
result.on("error", reject);
|
|
2741
|
+
});
|
|
2742
|
+
console.log(`\u2713 Registered agentation MCP server with Claude Code`);
|
|
2743
|
+
} catch (err) {
|
|
2744
|
+
console.log(`\u2717 Could not register MCP server automatically: ${err}`);
|
|
2745
|
+
console.log(` You can register manually by running:`);
|
|
2746
|
+
console.log(` claude mcp add agentation -- npx agentation-vue-mcp server`);
|
|
2747
|
+
}
|
|
2748
|
+
console.log();
|
|
2749
|
+
const testNow = await question(`Start server and test connection? [Y/n] `);
|
|
2750
|
+
if (testNow.toLowerCase() !== "n") {
|
|
2751
|
+
console.log();
|
|
2752
|
+
console.log(`Starting server on port ${port}...`);
|
|
2753
|
+
const server = (0, import_child_process.spawn)("agentation-vue-mcp", ["server", "--port", String(port)], {
|
|
2754
|
+
stdio: "inherit",
|
|
2755
|
+
detached: false
|
|
2756
|
+
});
|
|
2757
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
2758
|
+
try {
|
|
2759
|
+
const response = await fetch(`http://localhost:${port}/health`);
|
|
2760
|
+
if (response.ok) {
|
|
2761
|
+
console.log();
|
|
2762
|
+
console.log(`\u2713 Server is running on http://localhost:${port}`);
|
|
2763
|
+
console.log(`\u2713 MCP tools available to Claude Code`);
|
|
2764
|
+
console.log();
|
|
2765
|
+
console.log(`Press Ctrl+C to stop the server.`);
|
|
2766
|
+
await new Promise(() => {
|
|
2767
|
+
});
|
|
2768
|
+
} else {
|
|
2769
|
+
console.log(`\u2717 Server health check failed: ${response.status}`);
|
|
2770
|
+
server.kill();
|
|
2771
|
+
}
|
|
2772
|
+
} catch (err) {
|
|
2773
|
+
console.log(`\u2717 Could not connect to server: ${err}`);
|
|
2774
|
+
server.kill();
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
console.log();
|
|
2779
|
+
console.log(`Setup complete! Run 'agentation-vue-mcp doctor' to verify your setup.`);
|
|
2780
|
+
rl.close();
|
|
2781
|
+
}
|
|
2782
|
+
async function runDoctor() {
|
|
2783
|
+
console.log(`
|
|
2784
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
2785
|
+
\u2551 Agentation MCP Doctor \u2551
|
|
2786
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
2787
|
+
`);
|
|
2788
|
+
let allPassed = true;
|
|
2789
|
+
const results = [];
|
|
2790
|
+
const nodeVersion = process.version;
|
|
2791
|
+
const majorVersion = parseInt(nodeVersion.slice(1).split(".")[0], 10);
|
|
2792
|
+
if (majorVersion >= 18) {
|
|
2793
|
+
results.push({ name: "Node.js", status: "pass", message: `${nodeVersion} (18+ required)` });
|
|
2794
|
+
} else {
|
|
2795
|
+
results.push({ name: "Node.js", status: "fail", message: `${nodeVersion} (18+ required)` });
|
|
2796
|
+
allPassed = false;
|
|
2797
|
+
}
|
|
2798
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
2799
|
+
const claudeConfigPath = path.join(homeDir, ".claude.json");
|
|
2800
|
+
if (fs.existsSync(claudeConfigPath)) {
|
|
2801
|
+
try {
|
|
2802
|
+
const config = JSON.parse(fs.readFileSync(claudeConfigPath, "utf-8"));
|
|
2803
|
+
let found = false;
|
|
2804
|
+
if (config.mcpServers?.agentation) {
|
|
2805
|
+
found = true;
|
|
2806
|
+
}
|
|
2807
|
+
if (!found && config.projects) {
|
|
2808
|
+
for (const proj of Object.values(config.projects)) {
|
|
2809
|
+
if (proj.mcpServers?.agentation) {
|
|
2810
|
+
found = true;
|
|
2811
|
+
break;
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
if (found) {
|
|
2816
|
+
results.push({ name: "Claude Code config", status: "pass", message: "MCP server configured" });
|
|
2817
|
+
} else {
|
|
2818
|
+
results.push({ name: "Claude Code config", status: "warn", message: "Config exists but no agentation MCP entry. Run: claude mcp add agentation -- npx agentation-vue-mcp server" });
|
|
2819
|
+
}
|
|
2820
|
+
} catch {
|
|
2821
|
+
results.push({ name: "Claude Code config", status: "fail", message: "Could not parse config file" });
|
|
2822
|
+
allPassed = false;
|
|
2823
|
+
}
|
|
2824
|
+
} else {
|
|
2825
|
+
results.push({ name: "Claude Code config", status: "warn", message: "No config found at ~/.claude.json. Run: claude mcp add agentation -- npx agentation-vue-mcp server" });
|
|
2826
|
+
}
|
|
2827
|
+
const oldConfigPath = path.join(homeDir, ".claude", "claude_code_config.json");
|
|
2828
|
+
if (fs.existsSync(oldConfigPath)) {
|
|
2829
|
+
results.push({ name: "Stale config", status: "warn", message: `${oldConfigPath} exists but Claude Code doesn't read this file. Safe to delete.` });
|
|
2830
|
+
}
|
|
2831
|
+
try {
|
|
2832
|
+
const response = await fetch("http://localhost:4747/health", { signal: AbortSignal.timeout(2e3) });
|
|
2833
|
+
if (response.ok) {
|
|
2834
|
+
results.push({ name: "Server (port 4747)", status: "pass", message: "Running and healthy" });
|
|
2835
|
+
} else {
|
|
2836
|
+
results.push({ name: "Server (port 4747)", status: "warn", message: `Responded with ${response.status}` });
|
|
2837
|
+
}
|
|
2838
|
+
} catch {
|
|
2839
|
+
results.push({ name: "Server (port 4747)", status: "warn", message: "Not running (start with: agentation-vue-mcp server)" });
|
|
2840
|
+
}
|
|
2841
|
+
for (const r of results) {
|
|
2842
|
+
const icon = r.status === "pass" ? "\u2713" : r.status === "fail" ? "\u2717" : "\u25CB";
|
|
2843
|
+
const color = r.status === "pass" ? "\x1B[32m" : r.status === "fail" ? "\x1B[31m" : "\x1B[33m";
|
|
2844
|
+
console.log(`${color}${icon}\x1B[0m ${r.name}: ${r.message}`);
|
|
2845
|
+
}
|
|
2846
|
+
console.log();
|
|
2847
|
+
if (allPassed) {
|
|
2848
|
+
console.log(`All checks passed!`);
|
|
2849
|
+
} else {
|
|
2850
|
+
console.log(`Some checks failed. Run 'agentation-vue-mcp init' to fix.`);
|
|
2851
|
+
process.exit(1);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
if (command === "init") {
|
|
2855
|
+
runInit().catch((err) => {
|
|
2856
|
+
console.error("Init failed:", err);
|
|
2857
|
+
process.exit(1);
|
|
2858
|
+
});
|
|
2859
|
+
} else if (command === "doctor") {
|
|
2860
|
+
runDoctor().catch((err) => {
|
|
2861
|
+
console.error("Doctor failed:", err);
|
|
2862
|
+
process.exit(1);
|
|
2863
|
+
});
|
|
2864
|
+
} else if (command === "server") {
|
|
2865
|
+
Promise.resolve().then(() => (init_server(), server_exports)).then(({ startHttpServer: startHttpServer2, startMcpServer: startMcpServer2, setApiKey: setApiKey3 }) => {
|
|
2866
|
+
const args = process.argv.slice(3);
|
|
2867
|
+
let port = 4747;
|
|
2868
|
+
let mcpOnly = false;
|
|
2869
|
+
let httpUrl = "http://localhost:4747";
|
|
2870
|
+
let apiKeyArg;
|
|
2871
|
+
for (let i = 0; i < args.length; i++) {
|
|
2872
|
+
if (args[i] === "--port" && args[i + 1]) {
|
|
2873
|
+
const parsed = parseInt(args[i + 1], 10);
|
|
2874
|
+
if (!isNaN(parsed) && parsed > 0 && parsed < 65536) {
|
|
2875
|
+
port = parsed;
|
|
2876
|
+
if (!args.includes("--http-url")) {
|
|
2877
|
+
httpUrl = `http://localhost:${port}`;
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
i++;
|
|
2881
|
+
}
|
|
2882
|
+
if (args[i] === "--mcp-only") {
|
|
2883
|
+
mcpOnly = true;
|
|
2884
|
+
}
|
|
2885
|
+
if (args[i] === "--http-url" && args[i + 1]) {
|
|
2886
|
+
httpUrl = args[i + 1];
|
|
2887
|
+
i++;
|
|
2888
|
+
}
|
|
2889
|
+
if (args[i] === "--api-key" && args[i + 1]) {
|
|
2890
|
+
apiKeyArg = args[i + 1];
|
|
2891
|
+
i++;
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
const apiKey2 = apiKeyArg || process.env.AGENTATION_API_KEY;
|
|
2895
|
+
if (apiKey2) {
|
|
2896
|
+
setApiKey3(apiKey2);
|
|
2897
|
+
}
|
|
2898
|
+
if (!mcpOnly) {
|
|
2899
|
+
startHttpServer2(port, apiKey2);
|
|
2900
|
+
}
|
|
2901
|
+
startMcpServer2(httpUrl).catch((err) => {
|
|
2902
|
+
console.error("MCP server error:", err);
|
|
2903
|
+
process.exit(1);
|
|
2904
|
+
});
|
|
2905
|
+
});
|
|
2906
|
+
} else if (command === "help" || command === "--help" || command === "-h" || !command) {
|
|
2907
|
+
console.log(`
|
|
2908
|
+
agentation-vue-mcp - MCP server for Agentation visual feedback
|
|
2909
|
+
|
|
2910
|
+
Usage:
|
|
2911
|
+
agentation-vue-mcp init Interactive setup wizard
|
|
2912
|
+
agentation-vue-mcp server [options] Start the annotation server
|
|
2913
|
+
agentation-vue-mcp doctor Check your setup and diagnose issues
|
|
2914
|
+
agentation-vue-mcp help Show this help message
|
|
2915
|
+
|
|
2916
|
+
Server Options:
|
|
2917
|
+
--port <port> HTTP server port (default: 4747)
|
|
2918
|
+
--mcp-only Skip HTTP server, only run MCP on stdio
|
|
2919
|
+
--http-url <url> HTTP server URL for MCP to fetch from
|
|
2920
|
+
--api-key <key> API key for cloud storage (or set AGENTATION_API_KEY env var)
|
|
2921
|
+
|
|
2922
|
+
Commands:
|
|
2923
|
+
init Guided setup that configures Claude Code to use the MCP server.
|
|
2924
|
+
Registers the server via 'claude mcp add'.
|
|
2925
|
+
|
|
2926
|
+
server Starts both an HTTP server and MCP server for collecting annotations.
|
|
2927
|
+
The HTTP server receives annotations from the React component.
|
|
2928
|
+
The MCP server exposes tools for Claude Code to read/act on annotations.
|
|
2929
|
+
|
|
2930
|
+
doctor Runs diagnostic checks on your setup:
|
|
2931
|
+
- Node.js version
|
|
2932
|
+
- Claude Code configuration
|
|
2933
|
+
- Server connectivity
|
|
2934
|
+
|
|
2935
|
+
Examples:
|
|
2936
|
+
agentation-vue-mcp init Set up Agentation MCP
|
|
2937
|
+
agentation-vue-mcp server Start server on default port 4747
|
|
2938
|
+
agentation-vue-mcp server --port 8080 Start server on port 8080
|
|
2939
|
+
agentation-vue-mcp doctor Check if everything is configured correctly
|
|
2940
|
+
|
|
2941
|
+
# Use cloud storage with API key (local server proxies to cloud)
|
|
2942
|
+
agentation-vue-mcp server --api-key ag_xxx
|
|
2943
|
+
|
|
2944
|
+
# Or using environment variable
|
|
2945
|
+
AGENTATION_API_KEY=ag_xxx agentation-vue-mcp server
|
|
2946
|
+
`);
|
|
2947
|
+
} else {
|
|
2948
|
+
console.error(`Unknown command: ${command}`);
|
|
2949
|
+
console.error("Run 'agentation-vue-mcp help' for usage information.");
|
|
2950
|
+
process.exit(1);
|
|
2951
|
+
}
|
|
2952
|
+
//# sourceMappingURL=cli.js.map
|