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