tandem-editor 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -11
- package/dist/channel/index.js.map +1 -1
- package/dist/cli/index.js +10 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/client/assets/index-CcE9UvS8.js +308 -0
- package/dist/client/index.html +1 -1
- package/dist/server/index.js +458 -458
- package/dist/server/index.js.map +1 -1
- package/package.json +3 -1
- package/dist/client/assets/index-BXWLR51Y.js +0 -308
package/dist/server/index.js
CHANGED
|
@@ -9,7 +9,7 @@ var __export = (target, all) => {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
// src/shared/constants.ts
|
|
12
|
-
var DEFAULT_WS_PORT, DEFAULT_MCP_PORT, SUPPORTED_EXTENSIONS, MAX_FILE_SIZE, MAX_WS_PAYLOAD, IDLE_TIMEOUT, SESSION_MAX_AGE, TANDEM_MODE_DEFAULT, SELECTION_DWELL_DEFAULT_MS, CHARS_PER_PAGE, LARGE_FILE_PAGE_THRESHOLD, VERY_LARGE_FILE_PAGE_THRESHOLD, CTRL_ROOM, Y_MAP_ANNOTATIONS, Y_MAP_AWARENESS, Y_MAP_USER_AWARENESS, Y_MAP_MODE, Y_MAP_CHAT, Y_MAP_DOCUMENT_META, Y_MAP_SAVED_AT_VERSION, NOTIFICATION_BUFFER_SIZE, TUTORIAL_ANNOTATION_PREFIX, CHANNEL_EVENT_BUFFER_SIZE, CHANNEL_EVENT_BUFFER_AGE_MS, CHANNEL_SSE_KEEPALIVE_MS;
|
|
12
|
+
var DEFAULT_WS_PORT, DEFAULT_MCP_PORT, SUPPORTED_EXTENSIONS, MAX_FILE_SIZE, MAX_WS_PAYLOAD, IDLE_TIMEOUT, SESSION_MAX_AGE, TANDEM_MODE_DEFAULT, SELECTION_DWELL_DEFAULT_MS, SELECTION_DWELL_MIN_MS, SELECTION_DWELL_MAX_MS, CHARS_PER_PAGE, LARGE_FILE_PAGE_THRESHOLD, VERY_LARGE_FILE_PAGE_THRESHOLD, CTRL_ROOM, Y_MAP_ANNOTATIONS, Y_MAP_AWARENESS, Y_MAP_USER_AWARENESS, Y_MAP_MODE, Y_MAP_DWELL_MS, Y_MAP_CHAT, Y_MAP_DOCUMENT_META, Y_MAP_SAVED_AT_VERSION, NOTIFICATION_BUFFER_SIZE, TUTORIAL_ANNOTATION_PREFIX, CHANNEL_EVENT_BUFFER_SIZE, CHANNEL_EVENT_BUFFER_AGE_MS, CHANNEL_SSE_KEEPALIVE_MS;
|
|
13
13
|
var init_constants = __esm({
|
|
14
14
|
"src/shared/constants.ts"() {
|
|
15
15
|
"use strict";
|
|
@@ -22,6 +22,8 @@ var init_constants = __esm({
|
|
|
22
22
|
SESSION_MAX_AGE = 30 * 24 * 60 * 60 * 1e3;
|
|
23
23
|
TANDEM_MODE_DEFAULT = "tandem";
|
|
24
24
|
SELECTION_DWELL_DEFAULT_MS = 1e3;
|
|
25
|
+
SELECTION_DWELL_MIN_MS = 500;
|
|
26
|
+
SELECTION_DWELL_MAX_MS = 3e3;
|
|
25
27
|
CHARS_PER_PAGE = 3e3;
|
|
26
28
|
LARGE_FILE_PAGE_THRESHOLD = 50;
|
|
27
29
|
VERY_LARGE_FILE_PAGE_THRESHOLD = 100;
|
|
@@ -30,6 +32,7 @@ var init_constants = __esm({
|
|
|
30
32
|
Y_MAP_AWARENESS = "awareness";
|
|
31
33
|
Y_MAP_USER_AWARENESS = "userAwareness";
|
|
32
34
|
Y_MAP_MODE = "mode";
|
|
35
|
+
Y_MAP_DWELL_MS = "selectionDwellMs";
|
|
33
36
|
Y_MAP_CHAT = "chat";
|
|
34
37
|
Y_MAP_DOCUMENT_META = "documentMeta";
|
|
35
38
|
Y_MAP_SAVED_AT_VERSION = "savedAtVersion";
|
|
@@ -41,106 +44,79 @@ var init_constants = __esm({
|
|
|
41
44
|
}
|
|
42
45
|
});
|
|
43
46
|
|
|
44
|
-
// src/server/
|
|
45
|
-
import
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
function getOrCreateDocument(name) {
|
|
58
|
-
let doc = documents.get(name);
|
|
59
|
-
if (!doc) {
|
|
60
|
-
doc = new Y.Doc();
|
|
61
|
-
documents.set(name, doc);
|
|
62
|
-
}
|
|
63
|
-
return doc;
|
|
64
|
-
}
|
|
65
|
-
async function startHocuspocus(port) {
|
|
66
|
-
hocuspocusInstance = new Hocuspocus({
|
|
67
|
-
port,
|
|
68
|
-
address: "127.0.0.1",
|
|
69
|
-
quiet: true,
|
|
70
|
-
// stdout is the MCP wire — suppress the startup banner
|
|
71
|
-
async onConnect({ request, documentName }) {
|
|
72
|
-
const origin = request?.headers?.origin;
|
|
73
|
-
if (!origin) {
|
|
74
|
-
console.error("[Hocuspocus] Rejected connection: missing Origin header");
|
|
75
|
-
throw new Error("Connection rejected: missing origin header");
|
|
76
|
-
}
|
|
77
|
-
const url = new URL(origin);
|
|
78
|
-
if (url.hostname !== "localhost" && url.hostname !== "127.0.0.1") {
|
|
79
|
-
console.error(`[Hocuspocus] Rejected connection from origin: ${origin}`);
|
|
80
|
-
throw new Error("Connection rejected: invalid origin");
|
|
81
|
-
}
|
|
82
|
-
console.error(`[Hocuspocus] Client connected to: ${documentName}`);
|
|
83
|
-
},
|
|
84
|
-
async onDisconnect({ documentName }) {
|
|
85
|
-
console.error(`[Hocuspocus] Client disconnected from: ${documentName}`);
|
|
86
|
-
},
|
|
87
|
-
async onLoadDocument({ document, documentName }) {
|
|
88
|
-
console.error(`[Hocuspocus] Loading document: ${documentName}`);
|
|
89
|
-
const existing = documents.get(documentName);
|
|
90
|
-
if (existing && existing !== document) {
|
|
91
|
-
const update = Y.encodeStateAsUpdate(existing);
|
|
92
|
-
Y.applyUpdate(document, update);
|
|
93
|
-
existing.destroy();
|
|
94
|
-
console.error(`[Hocuspocus] Merged pre-existing content into document: ${documentName}`);
|
|
95
|
-
}
|
|
96
|
-
documents.set(documentName, document);
|
|
97
|
-
if (onDocSwapped) {
|
|
98
|
-
onDocSwapped(documentName, document);
|
|
99
|
-
} else {
|
|
100
|
-
console.error(
|
|
101
|
-
`[Tandem] WARN: onDocSwapped callback not registered during doc load for ${documentName}. Server-side observers will NOT be attached. Call setDocLifecycleCallbacks() before starting Hocuspocus.`
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
return document;
|
|
105
|
-
},
|
|
106
|
-
async afterUnloadDocument({ documentName }) {
|
|
107
|
-
if (shouldKeepDocument?.(documentName)) {
|
|
108
|
-
console.error(`[Hocuspocus] Kept document in map (MCP still tracking): ${documentName}`);
|
|
47
|
+
// src/server/file-watcher.ts
|
|
48
|
+
import fs from "fs";
|
|
49
|
+
function watchFile(filePath, onChanged) {
|
|
50
|
+
if (watched.has(filePath)) return;
|
|
51
|
+
let watcher;
|
|
52
|
+
try {
|
|
53
|
+
watcher = fs.watch(filePath, (eventType) => {
|
|
54
|
+
if (eventType !== "change") return;
|
|
55
|
+
const entry = watched.get(filePath);
|
|
56
|
+
if (!entry) return;
|
|
57
|
+
if (entry.suppressed) {
|
|
58
|
+
entry.suppressed = false;
|
|
109
59
|
return;
|
|
110
60
|
}
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
documents.delete(documentName);
|
|
114
|
-
console.error(`[Hocuspocus] Unloaded document from map: ${documentName}`);
|
|
61
|
+
if (entry.timer !== null) {
|
|
62
|
+
clearTimeout(entry.timer);
|
|
115
63
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
console.error(`[Tandem] Hocuspocus httpServer error: ${err.message}`);
|
|
64
|
+
entry.timer = setTimeout(() => {
|
|
65
|
+
entry.timer = null;
|
|
66
|
+
onChanged(filePath).catch((err) => {
|
|
67
|
+
console.error(`[FileWatcher] onChanged callback failed for ${filePath}:`, err);
|
|
68
|
+
});
|
|
69
|
+
}, 500);
|
|
123
70
|
});
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error(`[FileWatcher] Failed to watch ${filePath}:`, err);
|
|
73
|
+
return;
|
|
124
74
|
}
|
|
125
|
-
|
|
75
|
+
watcher.on("error", (err) => {
|
|
76
|
+
console.error(`[FileWatcher] Watcher error for ${filePath}:`, err);
|
|
77
|
+
unwatchFile(filePath);
|
|
78
|
+
});
|
|
79
|
+
watched.set(filePath, { watcher, timer: null, suppressed: false });
|
|
80
|
+
console.error(`[FileWatcher] Watching ${filePath}`);
|
|
126
81
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
82
|
+
function suppressNextChange(filePath) {
|
|
83
|
+
const entry = watched.get(filePath);
|
|
84
|
+
if (entry) {
|
|
85
|
+
entry.suppressed = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function unwatchFile(filePath) {
|
|
89
|
+
const entry = watched.get(filePath);
|
|
90
|
+
if (!entry) return;
|
|
91
|
+
if (entry.timer !== null) {
|
|
92
|
+
clearTimeout(entry.timer);
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
entry.watcher.close();
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error(`[FileWatcher] watcher.close() failed for ${filePath}:`, err);
|
|
98
|
+
}
|
|
99
|
+
watched.delete(filePath);
|
|
100
|
+
console.error(`[FileWatcher] Unwatched ${filePath}`);
|
|
101
|
+
}
|
|
102
|
+
function unwatchAll() {
|
|
103
|
+
for (const filePath of [...watched.keys()]) {
|
|
104
|
+
unwatchFile(filePath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
var watched;
|
|
108
|
+
var init_file_watcher = __esm({
|
|
109
|
+
"src/server/file-watcher.ts"() {
|
|
130
110
|
"use strict";
|
|
131
|
-
|
|
132
|
-
documents = /* @__PURE__ */ new Map();
|
|
133
|
-
shouldKeepDocument = null;
|
|
134
|
-
onDocSwapped = null;
|
|
135
|
-
onDocUnloaded = null;
|
|
111
|
+
watched = /* @__PURE__ */ new Map();
|
|
136
112
|
}
|
|
137
113
|
});
|
|
138
114
|
|
|
139
115
|
// src/server/platform.ts
|
|
140
116
|
import { execSync } from "child_process";
|
|
117
|
+
import envPaths from "env-paths";
|
|
141
118
|
import net from "net";
|
|
142
119
|
import path from "path";
|
|
143
|
-
import envPaths from "env-paths";
|
|
144
120
|
function freePort(port) {
|
|
145
121
|
try {
|
|
146
122
|
if (process.platform === "win32") {
|
|
@@ -233,15 +209,15 @@ var init_platform = __esm({
|
|
|
233
209
|
});
|
|
234
210
|
|
|
235
211
|
// src/server/session/manager.ts
|
|
236
|
-
import
|
|
212
|
+
import fs2 from "fs/promises";
|
|
237
213
|
import path2 from "path";
|
|
238
|
-
import * as
|
|
214
|
+
import * as Y from "yjs";
|
|
239
215
|
async function atomicWrite(sessionPath, content) {
|
|
240
216
|
const tmpPath = `${sessionPath}.tmp`;
|
|
241
|
-
await
|
|
217
|
+
await fs2.writeFile(tmpPath, content, "utf-8");
|
|
242
218
|
for (let attempt = 0; attempt < RENAME_MAX_RETRIES; attempt++) {
|
|
243
219
|
try {
|
|
244
|
-
await
|
|
220
|
+
await fs2.rename(tmpPath, sessionPath);
|
|
245
221
|
return;
|
|
246
222
|
} catch (err) {
|
|
247
223
|
const code = err.code;
|
|
@@ -249,7 +225,7 @@ async function atomicWrite(sessionPath, content) {
|
|
|
249
225
|
await new Promise((r) => setTimeout(r, RENAME_RETRY_BASE_MS * 2 ** attempt));
|
|
250
226
|
continue;
|
|
251
227
|
}
|
|
252
|
-
await
|
|
228
|
+
await fs2.unlink(tmpPath).catch(() => {
|
|
253
229
|
});
|
|
254
230
|
throw err;
|
|
255
231
|
}
|
|
@@ -263,12 +239,12 @@ async function saveSession(filePath, format, doc) {
|
|
|
263
239
|
let sourceFileMtime = 0;
|
|
264
240
|
if (!filePath.startsWith("upload://")) {
|
|
265
241
|
try {
|
|
266
|
-
const stat = await
|
|
242
|
+
const stat = await fs2.stat(filePath);
|
|
267
243
|
sourceFileMtime = stat.mtimeMs;
|
|
268
244
|
} catch {
|
|
269
245
|
}
|
|
270
246
|
}
|
|
271
|
-
const state =
|
|
247
|
+
const state = Y.encodeStateAsUpdate(doc);
|
|
272
248
|
const ydocState = Buffer.from(state).toString("base64");
|
|
273
249
|
const data = {
|
|
274
250
|
filePath,
|
|
@@ -278,7 +254,7 @@ async function saveSession(filePath, format, doc) {
|
|
|
278
254
|
lastAccessed: Date.now()
|
|
279
255
|
};
|
|
280
256
|
if (!sessionDirReady) {
|
|
281
|
-
await
|
|
257
|
+
await fs2.mkdir(SESSION_DIR, { recursive: true });
|
|
282
258
|
sessionDirReady = true;
|
|
283
259
|
}
|
|
284
260
|
const sessionPath = path2.join(SESSION_DIR, `${key}.json`);
|
|
@@ -288,14 +264,14 @@ async function loadSession(filePath) {
|
|
|
288
264
|
const key = sessionKey(filePath);
|
|
289
265
|
const sessionPath = path2.join(SESSION_DIR, `${key}.json`);
|
|
290
266
|
try {
|
|
291
|
-
const content = await
|
|
267
|
+
const content = await fs2.readFile(sessionPath, "utf-8");
|
|
292
268
|
return JSON.parse(content);
|
|
293
269
|
} catch (err) {
|
|
294
270
|
const code = err.code;
|
|
295
271
|
if (code === "ENOENT") return null;
|
|
296
272
|
if (err instanceof SyntaxError) {
|
|
297
273
|
console.error(`[Tandem] Corrupted session file ${sessionPath}, removing:`, err.message);
|
|
298
|
-
await
|
|
274
|
+
await fs2.unlink(sessionPath).catch((unlinkErr) => {
|
|
299
275
|
console.error(`[Tandem] Failed to remove corrupted session ${sessionPath}:`, unlinkErr);
|
|
300
276
|
});
|
|
301
277
|
return null;
|
|
@@ -306,12 +282,12 @@ async function loadSession(filePath) {
|
|
|
306
282
|
}
|
|
307
283
|
function restoreYDoc(doc, session) {
|
|
308
284
|
const state = Buffer.from(session.ydocState, "base64");
|
|
309
|
-
|
|
285
|
+
Y.applyUpdate(doc, new Uint8Array(state));
|
|
310
286
|
}
|
|
311
287
|
async function sourceFileChanged(session) {
|
|
312
288
|
if (session.filePath.startsWith("upload://")) return false;
|
|
313
289
|
try {
|
|
314
|
-
const stat = await
|
|
290
|
+
const stat = await fs2.stat(session.filePath);
|
|
315
291
|
return stat.mtimeMs !== session.sourceFileMtime;
|
|
316
292
|
} catch {
|
|
317
293
|
return true;
|
|
@@ -321,7 +297,7 @@ async function deleteSession(filePath) {
|
|
|
321
297
|
const key = sessionKey(filePath);
|
|
322
298
|
const sessionPath = path2.join(SESSION_DIR, `${key}.json`);
|
|
323
299
|
try {
|
|
324
|
-
await
|
|
300
|
+
await fs2.unlink(sessionPath);
|
|
325
301
|
} catch (err) {
|
|
326
302
|
const code = err.code;
|
|
327
303
|
if (code !== "ENOENT") {
|
|
@@ -331,7 +307,7 @@ async function deleteSession(filePath) {
|
|
|
331
307
|
}
|
|
332
308
|
async function saveCtrlSession(doc) {
|
|
333
309
|
if (!sessionDirReady) {
|
|
334
|
-
await
|
|
310
|
+
await fs2.mkdir(SESSION_DIR, { recursive: true });
|
|
335
311
|
sessionDirReady = true;
|
|
336
312
|
}
|
|
337
313
|
const chatMap = doc.getMap(Y_MAP_CHAT);
|
|
@@ -349,7 +325,7 @@ async function saveCtrlSession(doc) {
|
|
|
349
325
|
}
|
|
350
326
|
}, MCP_ORIGIN);
|
|
351
327
|
}
|
|
352
|
-
const state =
|
|
328
|
+
const state = Y.encodeStateAsUpdate(doc);
|
|
353
329
|
const ydocState = Buffer.from(state).toString("base64");
|
|
354
330
|
const data = { ydocState, lastAccessed: Date.now() };
|
|
355
331
|
const sessionPath = path2.join(SESSION_DIR, `${CTRL_SESSION_KEY}.json`);
|
|
@@ -358,7 +334,7 @@ async function saveCtrlSession(doc) {
|
|
|
358
334
|
async function loadCtrlSession() {
|
|
359
335
|
const sessionPath = path2.join(SESSION_DIR, `${CTRL_SESSION_KEY}.json`);
|
|
360
336
|
try {
|
|
361
|
-
const content = await
|
|
337
|
+
const content = await fs2.readFile(sessionPath, "utf-8");
|
|
362
338
|
const data = JSON.parse(content);
|
|
363
339
|
return data.ydocState ?? null;
|
|
364
340
|
} catch (err) {
|
|
@@ -366,7 +342,7 @@ async function loadCtrlSession() {
|
|
|
366
342
|
if (code === "ENOENT") return null;
|
|
367
343
|
if (err instanceof SyntaxError) {
|
|
368
344
|
console.error(`[Tandem] Corrupted ctrl session ${sessionPath}, removing:`, err.message);
|
|
369
|
-
await
|
|
345
|
+
await fs2.unlink(sessionPath).catch((unlinkErr) => {
|
|
370
346
|
console.error(
|
|
371
347
|
`[Tandem] Failed to remove corrupted ctrl session ${sessionPath}:`,
|
|
372
348
|
unlinkErr
|
|
@@ -380,18 +356,18 @@ async function loadCtrlSession() {
|
|
|
380
356
|
}
|
|
381
357
|
function restoreCtrlDoc(doc, base64State) {
|
|
382
358
|
const state = Buffer.from(base64State, "base64");
|
|
383
|
-
|
|
359
|
+
Y.applyUpdate(doc, new Uint8Array(state));
|
|
384
360
|
}
|
|
385
361
|
async function listSessionFilePaths() {
|
|
386
362
|
try {
|
|
387
|
-
await
|
|
388
|
-
const files = await
|
|
363
|
+
await fs2.mkdir(SESSION_DIR, { recursive: true });
|
|
364
|
+
const files = await fs2.readdir(SESSION_DIR);
|
|
389
365
|
const results = [];
|
|
390
366
|
for (const file of files) {
|
|
391
367
|
if (!file.endsWith(".json")) continue;
|
|
392
368
|
if (file === `${encodeURIComponent(CTRL_ROOM)}.json`) continue;
|
|
393
369
|
try {
|
|
394
|
-
const raw = await
|
|
370
|
+
const raw = await fs2.readFile(path2.join(SESSION_DIR, file), "utf-8");
|
|
395
371
|
const data = JSON.parse(raw);
|
|
396
372
|
if (!data.filePath || data.filePath.startsWith("upload://")) continue;
|
|
397
373
|
results.push({ filePath: data.filePath, lastAccessed: data.lastAccessed ?? 0 });
|
|
@@ -410,7 +386,7 @@ async function cleanupSessions() {
|
|
|
410
386
|
let cleaned = 0;
|
|
411
387
|
let files;
|
|
412
388
|
try {
|
|
413
|
-
files = await
|
|
389
|
+
files = await fs2.readdir(SESSION_DIR);
|
|
414
390
|
} catch (err) {
|
|
415
391
|
if (err.code === "ENOENT") return 0;
|
|
416
392
|
console.error("[Tandem] Failed to read session directory:", err);
|
|
@@ -420,9 +396,9 @@ async function cleanupSessions() {
|
|
|
420
396
|
for (const file of files) {
|
|
421
397
|
try {
|
|
422
398
|
const filePath = path2.join(SESSION_DIR, file);
|
|
423
|
-
const stat = await
|
|
399
|
+
const stat = await fs2.stat(filePath);
|
|
424
400
|
if (now - stat.mtimeMs > SESSION_MAX_AGE) {
|
|
425
|
-
await
|
|
401
|
+
await fs2.unlink(filePath);
|
|
426
402
|
cleaned++;
|
|
427
403
|
}
|
|
428
404
|
} catch (err) {
|
|
@@ -456,9 +432,9 @@ var AUTO_SAVE_INTERVAL, RENAME_MAX_RETRIES, RENAME_RETRY_BASE_MS, sessionDirRead
|
|
|
456
432
|
var init_manager = __esm({
|
|
457
433
|
"src/server/session/manager.ts"() {
|
|
458
434
|
"use strict";
|
|
459
|
-
init_platform();
|
|
460
435
|
init_constants();
|
|
461
436
|
init_queue();
|
|
437
|
+
init_platform();
|
|
462
438
|
AUTO_SAVE_INTERVAL = 60 * 1e3;
|
|
463
439
|
RENAME_MAX_RETRIES = 3;
|
|
464
440
|
RENAME_RETRY_BASE_MS = 50;
|
|
@@ -469,71 +445,136 @@ var init_manager = __esm({
|
|
|
469
445
|
}
|
|
470
446
|
});
|
|
471
447
|
|
|
472
|
-
// src/server/
|
|
473
|
-
import
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
448
|
+
// src/server/yjs/provider.ts
|
|
449
|
+
import { Hocuspocus } from "@hocuspocus/server";
|
|
450
|
+
import * as Y2 from "yjs";
|
|
451
|
+
function setDocLifecycleCallbacks(swapped, unloaded) {
|
|
452
|
+
onDocSwapped = swapped;
|
|
453
|
+
onDocUnloaded = unloaded;
|
|
454
|
+
}
|
|
455
|
+
function setShouldKeepDocument(fn) {
|
|
456
|
+
shouldKeepDocument = fn;
|
|
457
|
+
}
|
|
458
|
+
function getDocument(name) {
|
|
459
|
+
return documents.get(name);
|
|
460
|
+
}
|
|
461
|
+
function getOrCreateDocument(name) {
|
|
462
|
+
let doc = documents.get(name);
|
|
463
|
+
if (!doc) {
|
|
464
|
+
doc = new Y2.Doc();
|
|
465
|
+
documents.set(name, doc);
|
|
466
|
+
}
|
|
467
|
+
return doc;
|
|
468
|
+
}
|
|
469
|
+
async function startHocuspocus(port) {
|
|
470
|
+
hocuspocusInstance = new Hocuspocus({
|
|
471
|
+
port,
|
|
472
|
+
address: "127.0.0.1",
|
|
473
|
+
quiet: true,
|
|
474
|
+
// stdout is the MCP wire — suppress the startup banner
|
|
475
|
+
async onConnect({ request, documentName }) {
|
|
476
|
+
const origin = request?.headers?.origin;
|
|
477
|
+
if (!origin) {
|
|
478
|
+
console.error("[Hocuspocus] Rejected connection: missing Origin header");
|
|
479
|
+
throw new Error("Connection rejected: missing origin header");
|
|
480
|
+
}
|
|
481
|
+
const url = new URL(origin);
|
|
482
|
+
if (url.hostname !== "localhost" && url.hostname !== "127.0.0.1") {
|
|
483
|
+
console.error(`[Hocuspocus] Rejected connection from origin: ${origin}`);
|
|
484
|
+
throw new Error("Connection rejected: invalid origin");
|
|
485
|
+
}
|
|
486
|
+
console.error(`[Hocuspocus] Client connected to: ${documentName}`);
|
|
487
|
+
},
|
|
488
|
+
async onDisconnect({ documentName }) {
|
|
489
|
+
console.error(`[Hocuspocus] Client disconnected from: ${documentName}`);
|
|
490
|
+
},
|
|
491
|
+
async onLoadDocument({ document, documentName }) {
|
|
492
|
+
console.error(`[Hocuspocus] Loading document: ${documentName}`);
|
|
493
|
+
const existing = documents.get(documentName);
|
|
494
|
+
if (existing && existing !== document) {
|
|
495
|
+
const update = Y2.encodeStateAsUpdate(existing);
|
|
496
|
+
Y2.applyUpdate(document, update);
|
|
497
|
+
existing.destroy();
|
|
498
|
+
console.error(`[Hocuspocus] Merged pre-existing content into document: ${documentName}`);
|
|
499
|
+
}
|
|
500
|
+
documents.set(documentName, document);
|
|
501
|
+
if (onDocSwapped) {
|
|
502
|
+
onDocSwapped(documentName, document);
|
|
503
|
+
} else {
|
|
504
|
+
console.error(
|
|
505
|
+
`[Tandem] WARN: onDocSwapped callback not registered during doc load for ${documentName}. Server-side observers will NOT be attached. Call setDocLifecycleCallbacks() before starting Hocuspocus.`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
return document;
|
|
509
|
+
},
|
|
510
|
+
async afterUnloadDocument({ documentName }) {
|
|
511
|
+
if (shouldKeepDocument?.(documentName)) {
|
|
512
|
+
console.error(`[Hocuspocus] Kept document in map (MCP still tracking): ${documentName}`);
|
|
484
513
|
return;
|
|
485
514
|
}
|
|
486
|
-
if (
|
|
487
|
-
|
|
515
|
+
if (documents.has(documentName)) {
|
|
516
|
+
onDocUnloaded?.(documentName);
|
|
517
|
+
documents.delete(documentName);
|
|
518
|
+
console.error(`[Hocuspocus] Unloaded document from map: ${documentName}`);
|
|
488
519
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
await hocuspocusInstance.listen();
|
|
523
|
+
const internal = hocuspocusInstance.server?.httpServer;
|
|
524
|
+
if (internal) {
|
|
525
|
+
internal.on("error", (err) => {
|
|
526
|
+
console.error(`[Tandem] Hocuspocus httpServer error: ${err.message}`);
|
|
495
527
|
});
|
|
496
|
-
} catch (err) {
|
|
497
|
-
console.error(`[FileWatcher] Failed to watch ${filePath}:`, err);
|
|
498
|
-
return;
|
|
499
528
|
}
|
|
500
|
-
|
|
501
|
-
console.error(`[FileWatcher] Watcher error for ${filePath}:`, err);
|
|
502
|
-
unwatchFile(filePath);
|
|
503
|
-
});
|
|
504
|
-
watched.set(filePath, { watcher, timer: null, suppressed: false });
|
|
505
|
-
console.error(`[FileWatcher] Watching ${filePath}`);
|
|
529
|
+
return hocuspocusInstance;
|
|
506
530
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
531
|
+
var hocuspocusInstance, documents, shouldKeepDocument, onDocSwapped, onDocUnloaded;
|
|
532
|
+
var init_provider = __esm({
|
|
533
|
+
"src/server/yjs/provider.ts"() {
|
|
534
|
+
"use strict";
|
|
535
|
+
hocuspocusInstance = null;
|
|
536
|
+
documents = /* @__PURE__ */ new Map();
|
|
537
|
+
shouldKeepDocument = null;
|
|
538
|
+
onDocSwapped = null;
|
|
539
|
+
onDocUnloaded = null;
|
|
511
540
|
}
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// src/shared/utils.ts
|
|
544
|
+
function generateId(prefix) {
|
|
545
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
512
546
|
}
|
|
513
|
-
function
|
|
514
|
-
|
|
515
|
-
if (!entry) return;
|
|
516
|
-
if (entry.timer !== null) {
|
|
517
|
-
clearTimeout(entry.timer);
|
|
518
|
-
}
|
|
519
|
-
try {
|
|
520
|
-
entry.watcher.close();
|
|
521
|
-
} catch (err) {
|
|
522
|
-
console.error(`[FileWatcher] watcher.close() failed for ${filePath}:`, err);
|
|
523
|
-
}
|
|
524
|
-
watched.delete(filePath);
|
|
525
|
-
console.error(`[FileWatcher] Unwatched ${filePath}`);
|
|
547
|
+
function generateAnnotationId() {
|
|
548
|
+
return generateId("ann");
|
|
526
549
|
}
|
|
527
|
-
function
|
|
528
|
-
|
|
529
|
-
|
|
550
|
+
function generateMessageId() {
|
|
551
|
+
return generateId("msg");
|
|
552
|
+
}
|
|
553
|
+
function generateEventId() {
|
|
554
|
+
return generateId("evt");
|
|
555
|
+
}
|
|
556
|
+
function generateNotificationId() {
|
|
557
|
+
return generateId("ntf");
|
|
558
|
+
}
|
|
559
|
+
var init_utils = __esm({
|
|
560
|
+
"src/shared/utils.ts"() {
|
|
561
|
+
"use strict";
|
|
530
562
|
}
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// src/shared/offsets.ts
|
|
566
|
+
function headingPrefixLength(level) {
|
|
567
|
+
if (!level) return 0;
|
|
568
|
+
return level + 1;
|
|
531
569
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
570
|
+
function headingPrefix(level) {
|
|
571
|
+
return "#".repeat(level) + " ";
|
|
572
|
+
}
|
|
573
|
+
var FLAT_SEPARATOR;
|
|
574
|
+
var init_offsets = __esm({
|
|
575
|
+
"src/shared/offsets.ts"() {
|
|
535
576
|
"use strict";
|
|
536
|
-
|
|
577
|
+
FLAT_SEPARATOR = "\n";
|
|
537
578
|
}
|
|
538
579
|
});
|
|
539
580
|
|
|
@@ -855,10 +896,10 @@ var init_mdast_ydoc = __esm({
|
|
|
855
896
|
});
|
|
856
897
|
|
|
857
898
|
// src/server/file-io/markdown.ts
|
|
858
|
-
import { unified } from "unified";
|
|
859
|
-
import remarkParse from "remark-parse";
|
|
860
899
|
import remarkGfm from "remark-gfm";
|
|
900
|
+
import remarkParse from "remark-parse";
|
|
861
901
|
import remarkStringify from "remark-stringify";
|
|
902
|
+
import { unified } from "unified";
|
|
862
903
|
function loadMarkdown(doc, markdown) {
|
|
863
904
|
const tree = parser.parse(markdown);
|
|
864
905
|
mdastToYDoc(doc, tree);
|
|
@@ -883,22 +924,6 @@ var init_markdown = __esm({
|
|
|
883
924
|
}
|
|
884
925
|
});
|
|
885
926
|
|
|
886
|
-
// src/shared/offsets.ts
|
|
887
|
-
function headingPrefixLength(level) {
|
|
888
|
-
if (!level) return 0;
|
|
889
|
-
return level + 1;
|
|
890
|
-
}
|
|
891
|
-
function headingPrefix(level) {
|
|
892
|
-
return "#".repeat(level) + " ";
|
|
893
|
-
}
|
|
894
|
-
var FLAT_SEPARATOR;
|
|
895
|
-
var init_offsets = __esm({
|
|
896
|
-
"src/shared/offsets.ts"() {
|
|
897
|
-
"use strict";
|
|
898
|
-
FLAT_SEPARATOR = "\n";
|
|
899
|
-
}
|
|
900
|
-
});
|
|
901
|
-
|
|
902
927
|
// src/server/mcp/document-model.ts
|
|
903
928
|
import path3 from "path";
|
|
904
929
|
import * as Y4 from "yjs";
|
|
@@ -1428,6 +1453,42 @@ var init_types = __esm({
|
|
|
1428
1453
|
}
|
|
1429
1454
|
});
|
|
1430
1455
|
|
|
1456
|
+
// src/shared/types.ts
|
|
1457
|
+
import { z } from "zod";
|
|
1458
|
+
var AnnotationTypeSchema, AnnotationStatusSchema, HighlightColorSchema, SeveritySchema, TandemModeSchema, AuthorSchema, AnnotationActionSchema, ExportFormatSchema, DocumentFormatSchema, ToolErrorCodeSchema;
|
|
1459
|
+
var init_types2 = __esm({
|
|
1460
|
+
"src/shared/types.ts"() {
|
|
1461
|
+
"use strict";
|
|
1462
|
+
init_types();
|
|
1463
|
+
AnnotationTypeSchema = z.enum([
|
|
1464
|
+
"highlight",
|
|
1465
|
+
"comment",
|
|
1466
|
+
"suggestion",
|
|
1467
|
+
"overlay",
|
|
1468
|
+
"question",
|
|
1469
|
+
"flag"
|
|
1470
|
+
]);
|
|
1471
|
+
AnnotationStatusSchema = z.enum(["pending", "accepted", "dismissed"]);
|
|
1472
|
+
HighlightColorSchema = z.enum(["yellow", "red", "green", "blue", "purple"]);
|
|
1473
|
+
SeveritySchema = z.enum(["info", "warning", "error", "success"]);
|
|
1474
|
+
TandemModeSchema = z.enum(["solo", "tandem"]);
|
|
1475
|
+
AuthorSchema = z.enum(["user", "claude", "import"]);
|
|
1476
|
+
AnnotationActionSchema = z.enum(["accept", "dismiss"]);
|
|
1477
|
+
ExportFormatSchema = z.enum(["markdown", "json"]);
|
|
1478
|
+
DocumentFormatSchema = z.enum(["md", "txt", "html", "docx"]);
|
|
1479
|
+
ToolErrorCodeSchema = z.enum([
|
|
1480
|
+
"RANGE_GONE",
|
|
1481
|
+
"RANGE_MOVED",
|
|
1482
|
+
"FILE_LOCKED",
|
|
1483
|
+
"FILE_NOT_FOUND",
|
|
1484
|
+
"NO_DOCUMENT",
|
|
1485
|
+
"INVALID_RANGE",
|
|
1486
|
+
"FORMAT_ERROR",
|
|
1487
|
+
"PERMISSION_DENIED"
|
|
1488
|
+
]);
|
|
1489
|
+
}
|
|
1490
|
+
});
|
|
1491
|
+
|
|
1431
1492
|
// src/shared/positions/index.ts
|
|
1432
1493
|
var init_positions = __esm({
|
|
1433
1494
|
"src/shared/positions/index.ts"() {
|
|
@@ -1634,49 +1695,12 @@ function refreshAllRanges(annotations, ydoc, map) {
|
|
|
1634
1695
|
var init_positions2 = __esm({
|
|
1635
1696
|
"src/server/positions.ts"() {
|
|
1636
1697
|
"use strict";
|
|
1637
|
-
init_queue();
|
|
1638
1698
|
init_positions();
|
|
1699
|
+
init_queue();
|
|
1639
1700
|
init_document_model();
|
|
1640
1701
|
}
|
|
1641
1702
|
});
|
|
1642
1703
|
|
|
1643
|
-
// src/shared/types.ts
|
|
1644
|
-
import { z } from "zod";
|
|
1645
|
-
var AnnotationTypeSchema, AnnotationStatusSchema, AnnotationPrioritySchema, HighlightColorSchema, SeveritySchema, TandemModeSchema, AuthorSchema, AnnotationActionSchema, ExportFormatSchema, DocumentFormatSchema, ToolErrorCodeSchema;
|
|
1646
|
-
var init_types2 = __esm({
|
|
1647
|
-
"src/shared/types.ts"() {
|
|
1648
|
-
"use strict";
|
|
1649
|
-
init_types();
|
|
1650
|
-
AnnotationTypeSchema = z.enum([
|
|
1651
|
-
"highlight",
|
|
1652
|
-
"comment",
|
|
1653
|
-
"suggestion",
|
|
1654
|
-
"overlay",
|
|
1655
|
-
"question",
|
|
1656
|
-
"flag"
|
|
1657
|
-
]);
|
|
1658
|
-
AnnotationStatusSchema = z.enum(["pending", "accepted", "dismissed"]);
|
|
1659
|
-
AnnotationPrioritySchema = z.enum(["normal", "urgent"]);
|
|
1660
|
-
HighlightColorSchema = z.enum(["yellow", "red", "green", "blue", "purple"]);
|
|
1661
|
-
SeveritySchema = z.enum(["info", "warning", "error", "success"]);
|
|
1662
|
-
TandemModeSchema = z.enum(["solo", "tandem"]);
|
|
1663
|
-
AuthorSchema = z.enum(["user", "claude", "import"]);
|
|
1664
|
-
AnnotationActionSchema = z.enum(["accept", "dismiss"]);
|
|
1665
|
-
ExportFormatSchema = z.enum(["markdown", "json"]);
|
|
1666
|
-
DocumentFormatSchema = z.enum(["md", "txt", "html", "docx"]);
|
|
1667
|
-
ToolErrorCodeSchema = z.enum([
|
|
1668
|
-
"RANGE_GONE",
|
|
1669
|
-
"RANGE_MOVED",
|
|
1670
|
-
"FILE_LOCKED",
|
|
1671
|
-
"FILE_NOT_FOUND",
|
|
1672
|
-
"NO_DOCUMENT",
|
|
1673
|
-
"INVALID_RANGE",
|
|
1674
|
-
"FORMAT_ERROR",
|
|
1675
|
-
"PERMISSION_DENIED"
|
|
1676
|
-
]);
|
|
1677
|
-
}
|
|
1678
|
-
});
|
|
1679
|
-
|
|
1680
1704
|
// src/server/file-io/docx-walker.ts
|
|
1681
1705
|
import { parseDocument as parseDocument2 } from "htmlparser2";
|
|
1682
1706
|
function isElement2(node) {
|
|
@@ -1807,8 +1831,8 @@ var init_docx_walker = __esm({
|
|
|
1807
1831
|
});
|
|
1808
1832
|
|
|
1809
1833
|
// src/server/file-io/docx-comments.ts
|
|
1810
|
-
import JSZip from "jszip";
|
|
1811
1834
|
import { parseDocument as parseDocument3 } from "htmlparser2";
|
|
1835
|
+
import JSZip from "jszip";
|
|
1812
1836
|
async function extractDocxComments(buffer3) {
|
|
1813
1837
|
const zip = await JSZip.loadAsync(buffer3);
|
|
1814
1838
|
const commentsXml = await zip.file("word/comments.xml")?.async("text");
|
|
@@ -1916,9 +1940,9 @@ var init_docx_comments = __esm({
|
|
|
1916
1940
|
"src/server/file-io/docx-comments.ts"() {
|
|
1917
1941
|
"use strict";
|
|
1918
1942
|
init_constants();
|
|
1919
|
-
init_positions2();
|
|
1920
1943
|
init_types2();
|
|
1921
1944
|
init_queue();
|
|
1945
|
+
init_positions2();
|
|
1922
1946
|
init_docx_walker();
|
|
1923
1947
|
}
|
|
1924
1948
|
});
|
|
@@ -2241,9 +2265,9 @@ var init_dist2 = __esm({
|
|
|
2241
2265
|
});
|
|
2242
2266
|
|
|
2243
2267
|
// src/server/file-io/docx-apply.ts
|
|
2244
|
-
import JSZip2 from "jszip";
|
|
2245
|
-
import { parseDocument as parseDocument4 } from "htmlparser2";
|
|
2246
2268
|
import render from "dom-serializer";
|
|
2269
|
+
import { parseDocument as parseDocument4 } from "htmlparser2";
|
|
2270
|
+
import JSZip2 from "jszip";
|
|
2247
2271
|
function buildOffsetMap(xml, targetOffsets) {
|
|
2248
2272
|
const entries = /* @__PURE__ */ new Map();
|
|
2249
2273
|
const commentParagraphIds = /* @__PURE__ */ new Map();
|
|
@@ -2799,10 +2823,10 @@ var markdownAdapter, plaintextAdapter, docxAdapter, adapters;
|
|
|
2799
2823
|
var init_file_io = __esm({
|
|
2800
2824
|
"src/server/file-io/index.ts"() {
|
|
2801
2825
|
"use strict";
|
|
2802
|
-
|
|
2826
|
+
init_document_model();
|
|
2803
2827
|
init_docx();
|
|
2804
2828
|
init_docx_comments();
|
|
2805
|
-
|
|
2829
|
+
init_markdown();
|
|
2806
2830
|
init_docx_apply();
|
|
2807
2831
|
markdownAdapter = {
|
|
2808
2832
|
canSave: true,
|
|
@@ -2883,28 +2907,6 @@ var init_notifications = __esm({
|
|
|
2883
2907
|
}
|
|
2884
2908
|
});
|
|
2885
2909
|
|
|
2886
|
-
// src/shared/utils.ts
|
|
2887
|
-
function generateId(prefix) {
|
|
2888
|
-
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2889
|
-
}
|
|
2890
|
-
function generateAnnotationId() {
|
|
2891
|
-
return generateId("ann");
|
|
2892
|
-
}
|
|
2893
|
-
function generateMessageId() {
|
|
2894
|
-
return generateId("msg");
|
|
2895
|
-
}
|
|
2896
|
-
function generateEventId() {
|
|
2897
|
-
return generateId("evt");
|
|
2898
|
-
}
|
|
2899
|
-
function generateNotificationId() {
|
|
2900
|
-
return generateId("ntf");
|
|
2901
|
-
}
|
|
2902
|
-
var init_utils = __esm({
|
|
2903
|
-
"src/shared/utils.ts"() {
|
|
2904
|
-
"use strict";
|
|
2905
|
-
}
|
|
2906
|
-
});
|
|
2907
|
-
|
|
2908
2910
|
// src/server/mcp/file-opener.ts
|
|
2909
2911
|
var file_opener_exports = {};
|
|
2910
2912
|
__export(file_opener_exports, {
|
|
@@ -2912,10 +2914,10 @@ __export(file_opener_exports, {
|
|
|
2912
2914
|
openFileByPath: () => openFileByPath,
|
|
2913
2915
|
openFileFromContent: () => openFileFromContent
|
|
2914
2916
|
});
|
|
2915
|
-
import
|
|
2917
|
+
import { randomUUID } from "crypto";
|
|
2916
2918
|
import fsSync from "fs";
|
|
2919
|
+
import fs4 from "fs/promises";
|
|
2917
2920
|
import path5 from "path";
|
|
2918
|
-
import { randomUUID } from "crypto";
|
|
2919
2921
|
async function openFileByPath(filePath, options) {
|
|
2920
2922
|
let resolved = path5.resolve(filePath);
|
|
2921
2923
|
try {
|
|
@@ -3264,19 +3266,19 @@ var reloadInProgress;
|
|
|
3264
3266
|
var init_file_opener = __esm({
|
|
3265
3267
|
"src/server/mcp/file-opener.ts"() {
|
|
3266
3268
|
"use strict";
|
|
3267
|
-
init_provider();
|
|
3268
3269
|
init_constants();
|
|
3270
|
+
init_utils();
|
|
3269
3271
|
init_queue();
|
|
3272
|
+
init_docx();
|
|
3273
|
+
init_docx_comments();
|
|
3274
|
+
init_docx_html();
|
|
3270
3275
|
init_file_io();
|
|
3276
|
+
init_markdown();
|
|
3271
3277
|
init_file_watcher();
|
|
3272
|
-
init_positions2();
|
|
3273
3278
|
init_notifications();
|
|
3274
|
-
|
|
3275
|
-
init_markdown();
|
|
3276
|
-
init_docx();
|
|
3277
|
-
init_docx_html();
|
|
3278
|
-
init_docx_comments();
|
|
3279
|
+
init_positions2();
|
|
3279
3280
|
init_manager();
|
|
3281
|
+
init_provider();
|
|
3280
3282
|
init_document_model();
|
|
3281
3283
|
init_document_service();
|
|
3282
3284
|
reloadInProgress = /* @__PURE__ */ new Set();
|
|
@@ -3284,8 +3286,8 @@ var init_file_opener = __esm({
|
|
|
3284
3286
|
});
|
|
3285
3287
|
|
|
3286
3288
|
// src/server/mcp/document-service.ts
|
|
3287
|
-
import path6 from "path";
|
|
3288
3289
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3290
|
+
import path6 from "path";
|
|
3289
3291
|
function getOpenDocs() {
|
|
3290
3292
|
return openDocs;
|
|
3291
3293
|
}
|
|
@@ -3445,11 +3447,11 @@ var openDocs, activeDocId;
|
|
|
3445
3447
|
var init_document_service = __esm({
|
|
3446
3448
|
"src/server/mcp/document-service.ts"() {
|
|
3447
3449
|
"use strict";
|
|
3448
|
-
init_provider();
|
|
3449
|
-
init_manager();
|
|
3450
3450
|
init_constants();
|
|
3451
3451
|
init_queue();
|
|
3452
3452
|
init_file_watcher();
|
|
3453
|
+
init_manager();
|
|
3454
|
+
init_provider();
|
|
3453
3455
|
openDocs = /* @__PURE__ */ new Map();
|
|
3454
3456
|
setShouldKeepDocument((name) => openDocs.has(name) || name === CTRL_ROOM);
|
|
3455
3457
|
activeDocId = null;
|
|
@@ -3465,6 +3467,19 @@ var init_types3 = __esm({
|
|
|
3465
3467
|
});
|
|
3466
3468
|
|
|
3467
3469
|
// src/server/events/queue.ts
|
|
3470
|
+
function getDwellMs() {
|
|
3471
|
+
const ctrlDoc = getOrCreateDocument(CTRL_ROOM);
|
|
3472
|
+
const awareness = ctrlDoc.getMap(Y_MAP_USER_AWARENESS);
|
|
3473
|
+
const val = awareness.get(Y_MAP_DWELL_MS);
|
|
3474
|
+
if (val === void 0) return SELECTION_DWELL_DEFAULT_MS;
|
|
3475
|
+
if (typeof val === "number" && val >= SELECTION_DWELL_MIN_MS && val <= SELECTION_DWELL_MAX_MS) {
|
|
3476
|
+
return val;
|
|
3477
|
+
}
|
|
3478
|
+
console.warn(
|
|
3479
|
+
`[EventQueue] Invalid dwell time in CTRL_ROOM awareness (type=${typeof val}, value=${String(val)}); using default ${SELECTION_DWELL_DEFAULT_MS}ms`
|
|
3480
|
+
);
|
|
3481
|
+
return SELECTION_DWELL_DEFAULT_MS;
|
|
3482
|
+
}
|
|
3468
3483
|
function getTrackableId(event) {
|
|
3469
3484
|
switch (event.type) {
|
|
3470
3485
|
case "annotation:created":
|
|
@@ -3594,7 +3609,7 @@ function attachObservers(docName, doc) {
|
|
|
3594
3609
|
selectedText: selection.selectedText ?? ""
|
|
3595
3610
|
}
|
|
3596
3611
|
});
|
|
3597
|
-
},
|
|
3612
|
+
}, getDwellMs());
|
|
3598
3613
|
}
|
|
3599
3614
|
};
|
|
3600
3615
|
userAwareness.observe(awarenessObs);
|
|
@@ -3791,125 +3806,72 @@ function killClaude() {
|
|
|
3791
3806
|
console.error("[Launcher] Failed to kill Claude process:", err);
|
|
3792
3807
|
}
|
|
3793
3808
|
}
|
|
3794
|
-
claudeProcess = null;
|
|
3795
|
-
}
|
|
3796
|
-
}
|
|
3797
|
-
var claudeProcess, TANDEM_SYSTEM_PROMPT;
|
|
3798
|
-
var init_launcher = __esm({
|
|
3799
|
-
"src/server/mcp/launcher.ts"() {
|
|
3800
|
-
"use strict";
|
|
3801
|
-
init_constants();
|
|
3802
|
-
claudeProcess = null;
|
|
3803
|
-
TANDEM_SYSTEM_PROMPT = [
|
|
3804
|
-
"You are Claude, connected to Tandem \u2014 a collaborative document editor.",
|
|
3805
|
-
"You will receive real-time push notifications via the tandem-channel when users",
|
|
3806
|
-
"create annotations, send chat messages, accept/dismiss your suggestions, or switch documents.",
|
|
3807
|
-
"Use your tandem MCP tools (tandem_getTextContent, tandem_comment, tandem_highlight,",
|
|
3808
|
-
"tandem_suggest, tandem_edit, etc.) to review and annotate documents.",
|
|
3809
|
-
"Start by calling tandem_checkInbox to see what needs attention."
|
|
3810
|
-
].join(" ");
|
|
3811
|
-
}
|
|
3812
|
-
});
|
|
3813
|
-
|
|
3814
|
-
// src/server/index.ts
|
|
3815
|
-
import path10 from "path";
|
|
3816
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3817
|
-
|
|
3818
|
-
// src/server/mcp/server.ts
|
|
3819
|
-
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
3820
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3821
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3822
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3823
|
-
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
3824
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
3825
|
-
import { existsSync } from "fs";
|
|
3826
|
-
import { dirname, join } from "path";
|
|
3827
|
-
import { fileURLToPath } from "url";
|
|
3828
|
-
import { createRequire } from "module";
|
|
3829
|
-
|
|
3830
|
-
// src/server/open-browser.ts
|
|
3831
|
-
import { execFile } from "child_process";
|
|
3832
|
-
function openBrowser(url) {
|
|
3833
|
-
let command;
|
|
3834
|
-
let args;
|
|
3835
|
-
if (process.platform === "win32") {
|
|
3836
|
-
command = "cmd";
|
|
3837
|
-
args = ["/c", "start", "", url];
|
|
3838
|
-
} else if (process.platform === "darwin") {
|
|
3839
|
-
command = "open";
|
|
3840
|
-
args = [url];
|
|
3841
|
-
} else {
|
|
3842
|
-
command = "xdg-open";
|
|
3843
|
-
args = [url];
|
|
3844
|
-
}
|
|
3845
|
-
execFile(command, args, (err) => {
|
|
3846
|
-
if (err) {
|
|
3847
|
-
console.error("[Tandem] Could not open browser automatically.");
|
|
3848
|
-
console.error(`[Tandem] Open this URL manually: ${url}`);
|
|
3849
|
-
}
|
|
3850
|
-
});
|
|
3851
|
-
}
|
|
3852
|
-
|
|
3853
|
-
// src/server/mcp/annotations.ts
|
|
3854
|
-
init_constants();
|
|
3855
|
-
init_queue();
|
|
3856
|
-
init_provider();
|
|
3857
|
-
import { z as z3 } from "zod";
|
|
3858
|
-
|
|
3859
|
-
// src/server/mcp/document.ts
|
|
3860
|
-
init_provider();
|
|
3861
|
-
import { z as z2 } from "zod";
|
|
3862
|
-
import * as Y8 from "yjs";
|
|
3863
|
-
|
|
3864
|
-
// src/server/mcp/response.ts
|
|
3865
|
-
function mcpSuccess(data) {
|
|
3866
|
-
return {
|
|
3867
|
-
content: [{ type: "text", text: JSON.stringify({ error: false, data }) }]
|
|
3868
|
-
};
|
|
3869
|
-
}
|
|
3870
|
-
function mcpError(code, message, details) {
|
|
3871
|
-
return {
|
|
3872
|
-
content: [
|
|
3873
|
-
{
|
|
3874
|
-
type: "text",
|
|
3875
|
-
text: JSON.stringify({ error: true, code, message, ...details && { details } })
|
|
3876
|
-
}
|
|
3877
|
-
]
|
|
3878
|
-
};
|
|
3879
|
-
}
|
|
3880
|
-
function noDocumentError() {
|
|
3881
|
-
return mcpError("NO_DOCUMENT", "No document is open. Call tandem_open first.");
|
|
3882
|
-
}
|
|
3883
|
-
function getErrorMessage(err) {
|
|
3884
|
-
return err instanceof Error ? err.message : String(err);
|
|
3885
|
-
}
|
|
3886
|
-
function withErrorBoundary(toolName, handler) {
|
|
3887
|
-
return async (args) => {
|
|
3888
|
-
try {
|
|
3889
|
-
return await handler(args);
|
|
3890
|
-
} catch (err) {
|
|
3891
|
-
console.error(`[Tandem] Tool ${toolName} threw:`, err);
|
|
3892
|
-
return mcpError("INTERNAL_ERROR", `${toolName} failed: ${getErrorMessage(err)}`);
|
|
3893
|
-
}
|
|
3894
|
-
};
|
|
3809
|
+
claudeProcess = null;
|
|
3810
|
+
}
|
|
3895
3811
|
}
|
|
3896
|
-
|
|
3897
|
-
|
|
3812
|
+
var claudeProcess, TANDEM_SYSTEM_PROMPT;
|
|
3813
|
+
var init_launcher = __esm({
|
|
3814
|
+
"src/server/mcp/launcher.ts"() {
|
|
3815
|
+
"use strict";
|
|
3816
|
+
init_constants();
|
|
3817
|
+
claudeProcess = null;
|
|
3818
|
+
TANDEM_SYSTEM_PROMPT = [
|
|
3819
|
+
"You are Claude, connected to Tandem \u2014 a collaborative document editor.",
|
|
3820
|
+
"You will receive real-time push notifications via the tandem-channel when users",
|
|
3821
|
+
"create annotations, send chat messages, accept/dismiss your suggestions, or switch documents.",
|
|
3822
|
+
"Use your tandem MCP tools (tandem_getTextContent, tandem_comment, tandem_highlight,",
|
|
3823
|
+
"tandem_suggest, tandem_edit, etc.) to review and annotate documents.",
|
|
3824
|
+
"Start by calling tandem_checkInbox to see what needs attention."
|
|
3825
|
+
].join(" ");
|
|
3826
|
+
}
|
|
3827
|
+
});
|
|
3828
|
+
|
|
3829
|
+
// src/server/index.ts
|
|
3830
|
+
init_constants();
|
|
3831
|
+
import path10 from "path";
|
|
3832
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3833
|
+
|
|
3834
|
+
// src/server/error-filter.ts
|
|
3835
|
+
function isKnownHocuspocusError(err) {
|
|
3836
|
+
if (!(err instanceof Error)) return false;
|
|
3837
|
+
if ("code" in err) {
|
|
3838
|
+
const code = err.code;
|
|
3839
|
+
if (typeof code === "string" && code.startsWith("WS_ERR_")) {
|
|
3840
|
+
return true;
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
const msg = err.message;
|
|
3844
|
+
if (msg.startsWith("WebSocket is not open")) return true;
|
|
3845
|
+
if (msg.includes("Unexpected end of array") || msg.includes("Integer out of Range")) return true;
|
|
3846
|
+
if (msg.startsWith("Received a message with an unknown type:")) return true;
|
|
3847
|
+
return false;
|
|
3898
3848
|
}
|
|
3899
3849
|
|
|
3850
|
+
// src/server/index.ts
|
|
3851
|
+
init_queue();
|
|
3852
|
+
init_file_watcher();
|
|
3853
|
+
|
|
3900
3854
|
// src/server/mcp/document.ts
|
|
3901
|
-
|
|
3902
|
-
init_utils();
|
|
3855
|
+
init_constants();
|
|
3903
3856
|
init_offsets();
|
|
3857
|
+
init_types2();
|
|
3858
|
+
init_utils();
|
|
3859
|
+
init_queue();
|
|
3904
3860
|
init_file_io();
|
|
3905
3861
|
init_file_watcher();
|
|
3862
|
+
init_notifications();
|
|
3863
|
+
init_positions2();
|
|
3864
|
+
init_manager();
|
|
3865
|
+
init_provider();
|
|
3866
|
+
import * as Y8 from "yjs";
|
|
3867
|
+
import { z as z2 } from "zod";
|
|
3906
3868
|
|
|
3907
3869
|
// src/server/mcp/convert.ts
|
|
3870
|
+
init_file_io();
|
|
3908
3871
|
init_provider();
|
|
3909
3872
|
init_document_model();
|
|
3910
|
-
init_file_io();
|
|
3911
|
-
init_file_opener();
|
|
3912
3873
|
init_document_service();
|
|
3874
|
+
init_file_opener();
|
|
3913
3875
|
import fs5 from "fs/promises";
|
|
3914
3876
|
import path7 from "path";
|
|
3915
3877
|
async function findAvailablePath(basePath) {
|
|
@@ -4003,16 +3965,49 @@ async function convertToMarkdown(documentId, outputPath) {
|
|
|
4003
3965
|
}
|
|
4004
3966
|
|
|
4005
3967
|
// src/server/mcp/document.ts
|
|
4006
|
-
init_manager();
|
|
4007
|
-
init_file_opener();
|
|
4008
|
-
init_constants();
|
|
4009
|
-
init_types2();
|
|
4010
|
-
init_queue();
|
|
4011
3968
|
init_document_model();
|
|
4012
|
-
init_positions2();
|
|
4013
3969
|
init_document_service();
|
|
4014
|
-
|
|
3970
|
+
init_file_opener();
|
|
3971
|
+
|
|
3972
|
+
// src/server/mcp/response.ts
|
|
3973
|
+
function mcpSuccess(data) {
|
|
3974
|
+
return {
|
|
3975
|
+
content: [{ type: "text", text: JSON.stringify({ error: false, data }) }]
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3978
|
+
function mcpError(code, message, details) {
|
|
3979
|
+
return {
|
|
3980
|
+
content: [
|
|
3981
|
+
{
|
|
3982
|
+
type: "text",
|
|
3983
|
+
text: JSON.stringify({ error: true, code, message, ...details && { details } })
|
|
3984
|
+
}
|
|
3985
|
+
]
|
|
3986
|
+
};
|
|
3987
|
+
}
|
|
3988
|
+
function noDocumentError() {
|
|
3989
|
+
return mcpError("NO_DOCUMENT", "No document is open. Call tandem_open first.");
|
|
3990
|
+
}
|
|
3991
|
+
function getErrorMessage(err) {
|
|
3992
|
+
return err instanceof Error ? err.message : String(err);
|
|
3993
|
+
}
|
|
3994
|
+
function withErrorBoundary(toolName, handler) {
|
|
3995
|
+
return async (args) => {
|
|
3996
|
+
try {
|
|
3997
|
+
return await handler(args);
|
|
3998
|
+
} catch (err) {
|
|
3999
|
+
console.error(`[Tandem] Tool ${toolName} threw:`, err);
|
|
4000
|
+
return mcpError("INTERNAL_ERROR", `${toolName} failed: ${getErrorMessage(err)}`);
|
|
4001
|
+
}
|
|
4002
|
+
};
|
|
4003
|
+
}
|
|
4004
|
+
function escapeRegex(str) {
|
|
4005
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
// src/server/mcp/document.ts
|
|
4015
4009
|
init_positions2();
|
|
4010
|
+
init_document_model();
|
|
4016
4011
|
init_document_service();
|
|
4017
4012
|
init_file_opener();
|
|
4018
4013
|
function getOutline(fragment) {
|
|
@@ -4408,12 +4403,56 @@ function registerDocumentTools(server) {
|
|
|
4408
4403
|
);
|
|
4409
4404
|
}
|
|
4410
4405
|
|
|
4406
|
+
// src/server/index.ts
|
|
4407
|
+
init_document_model();
|
|
4408
|
+
init_document_service();
|
|
4409
|
+
init_file_opener();
|
|
4410
|
+
|
|
4411
|
+
// src/server/mcp/server.ts
|
|
4412
|
+
import { existsSync } from "fs";
|
|
4413
|
+
import { dirname, join } from "path";
|
|
4414
|
+
import { fileURLToPath } from "url";
|
|
4415
|
+
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
4416
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4417
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4418
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4419
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
4420
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4421
|
+
import { createRequire } from "module";
|
|
4422
|
+
|
|
4423
|
+
// src/server/open-browser.ts
|
|
4424
|
+
import { execFile } from "child_process";
|
|
4425
|
+
function openBrowser(url) {
|
|
4426
|
+
let command;
|
|
4427
|
+
let args;
|
|
4428
|
+
if (process.platform === "win32") {
|
|
4429
|
+
command = "cmd";
|
|
4430
|
+
args = ["/c", "start", "", url];
|
|
4431
|
+
} else if (process.platform === "darwin") {
|
|
4432
|
+
command = "open";
|
|
4433
|
+
args = [url];
|
|
4434
|
+
} else {
|
|
4435
|
+
command = "xdg-open";
|
|
4436
|
+
args = [url];
|
|
4437
|
+
}
|
|
4438
|
+
execFile(command, args, (err) => {
|
|
4439
|
+
if (err) {
|
|
4440
|
+
console.error("[Tandem] Could not open browser automatically.");
|
|
4441
|
+
console.error(`[Tandem] Open this URL manually: ${url}`);
|
|
4442
|
+
}
|
|
4443
|
+
});
|
|
4444
|
+
}
|
|
4445
|
+
|
|
4411
4446
|
// src/server/mcp/annotations.ts
|
|
4412
|
-
|
|
4447
|
+
init_constants();
|
|
4413
4448
|
init_types2();
|
|
4414
|
-
init_positions2();
|
|
4415
|
-
init_notifications();
|
|
4416
4449
|
init_utils();
|
|
4450
|
+
init_queue();
|
|
4451
|
+
init_docx();
|
|
4452
|
+
init_notifications();
|
|
4453
|
+
init_positions2();
|
|
4454
|
+
init_provider();
|
|
4455
|
+
import { z as z3 } from "zod";
|
|
4417
4456
|
init_positions2();
|
|
4418
4457
|
function getDocAndAnnotations(documentId) {
|
|
4419
4458
|
const doc = getCurrentDoc(documentId);
|
|
@@ -4510,16 +4549,13 @@ function registerAnnotationTools(server) {
|
|
|
4510
4549
|
color: HighlightColorSchema.describe("Highlight color"),
|
|
4511
4550
|
note: z3.string().optional().describe("Optional note for the highlight"),
|
|
4512
4551
|
documentId: z3.string().optional().describe("Target document ID (defaults to active document)"),
|
|
4513
|
-
priority: AnnotationPrioritySchema.optional().describe(
|
|
4514
|
-
"Annotation priority. Set to 'urgent' for critical issues that should be visible even when the user has interruption mode set to urgent-only. Flags and questions are implicitly urgent."
|
|
4515
|
-
),
|
|
4516
4552
|
textSnapshot: z3.string().optional().describe(
|
|
4517
4553
|
"Expected text at [from, to] \u2014 returns RANGE_MOVED with relocated range on mismatch, or RANGE_GONE if text was deleted"
|
|
4518
4554
|
)
|
|
4519
4555
|
},
|
|
4520
4556
|
withErrorBoundary(
|
|
4521
4557
|
"tandem_highlight",
|
|
4522
|
-
async ({ from: rawFrom, to: rawTo, color, note, documentId,
|
|
4558
|
+
async ({ from: rawFrom, to: rawTo, color, note, documentId, textSnapshot }) => {
|
|
4523
4559
|
const da = getDocAndAnnotations(documentId);
|
|
4524
4560
|
if (!da) return noDocumentError();
|
|
4525
4561
|
const from = toFlatOffset(rawFrom);
|
|
@@ -4532,7 +4568,6 @@ function registerAnnotationTools(server) {
|
|
|
4532
4568
|
const snap = captureSnapshot(da.ydoc, result.range.from, result.range.to);
|
|
4533
4569
|
const id = createAnnotation(da.map, da.ydoc, "highlight", result, note || "", {
|
|
4534
4570
|
color,
|
|
4535
|
-
...priority ? { priority } : {},
|
|
4536
4571
|
textSnapshot: snap
|
|
4537
4572
|
});
|
|
4538
4573
|
return mcpSuccess({ annotationId: id });
|
|
@@ -4547,16 +4582,13 @@ function registerAnnotationTools(server) {
|
|
|
4547
4582
|
to: z3.number().describe("End position"),
|
|
4548
4583
|
text: z3.string().describe("Comment text"),
|
|
4549
4584
|
documentId: z3.string().optional().describe("Target document ID (defaults to active document)"),
|
|
4550
|
-
priority: AnnotationPrioritySchema.optional().describe(
|
|
4551
|
-
"Annotation priority. Set to 'urgent' for critical issues that should be visible even when the user has interruption mode set to urgent-only. Flags and questions are implicitly urgent."
|
|
4552
|
-
),
|
|
4553
4585
|
textSnapshot: z3.string().optional().describe(
|
|
4554
4586
|
"Expected text at [from, to] \u2014 returns RANGE_MOVED with relocated range on mismatch, or RANGE_GONE if text was deleted"
|
|
4555
4587
|
)
|
|
4556
4588
|
},
|
|
4557
4589
|
withErrorBoundary(
|
|
4558
4590
|
"tandem_comment",
|
|
4559
|
-
async ({ from: rawFrom, to: rawTo, text, documentId,
|
|
4591
|
+
async ({ from: rawFrom, to: rawTo, text, documentId, textSnapshot }) => {
|
|
4560
4592
|
const da = getDocAndAnnotations(documentId);
|
|
4561
4593
|
if (!da) return noDocumentError();
|
|
4562
4594
|
const from = toFlatOffset(rawFrom);
|
|
@@ -4568,7 +4600,6 @@ function registerAnnotationTools(server) {
|
|
|
4568
4600
|
}
|
|
4569
4601
|
const snap = captureSnapshot(da.ydoc, result.range.from, result.range.to);
|
|
4570
4602
|
const id = createAnnotation(da.map, da.ydoc, "comment", result, text, {
|
|
4571
|
-
...priority ? { priority } : {},
|
|
4572
4603
|
textSnapshot: snap
|
|
4573
4604
|
});
|
|
4574
4605
|
return mcpSuccess({ annotationId: id });
|
|
@@ -4584,16 +4615,13 @@ function registerAnnotationTools(server) {
|
|
|
4584
4615
|
newText: z3.string().describe("Suggested replacement text"),
|
|
4585
4616
|
reason: z3.string().optional().describe("Reason for the suggestion"),
|
|
4586
4617
|
documentId: z3.string().optional().describe("Target document ID (defaults to active document)"),
|
|
4587
|
-
priority: AnnotationPrioritySchema.optional().describe(
|
|
4588
|
-
"Annotation priority. Set to 'urgent' for critical issues that should be visible even when the user has interruption mode set to urgent-only. Flags and questions are implicitly urgent."
|
|
4589
|
-
),
|
|
4590
4618
|
textSnapshot: z3.string().optional().describe(
|
|
4591
4619
|
"Expected text at [from, to] \u2014 returns RANGE_MOVED with relocated range on mismatch, or RANGE_GONE if text was deleted"
|
|
4592
4620
|
)
|
|
4593
4621
|
},
|
|
4594
4622
|
withErrorBoundary(
|
|
4595
4623
|
"tandem_suggest",
|
|
4596
|
-
async ({ from: rawFrom, to: rawTo, newText, reason, documentId,
|
|
4624
|
+
async ({ from: rawFrom, to: rawTo, newText, reason, documentId, textSnapshot }) => {
|
|
4597
4625
|
const da = getDocAndAnnotations(documentId);
|
|
4598
4626
|
if (!da) return noDocumentError();
|
|
4599
4627
|
const from = toFlatOffset(rawFrom);
|
|
@@ -4610,7 +4638,7 @@ function registerAnnotationTools(server) {
|
|
|
4610
4638
|
"suggestion",
|
|
4611
4639
|
result,
|
|
4612
4640
|
JSON.stringify({ newText, reason: reason || "" }),
|
|
4613
|
-
{
|
|
4641
|
+
{ textSnapshot: snap }
|
|
4614
4642
|
);
|
|
4615
4643
|
return mcpSuccess({ annotationId: id });
|
|
4616
4644
|
}
|
|
@@ -4624,16 +4652,13 @@ function registerAnnotationTools(server) {
|
|
|
4624
4652
|
to: z3.number().describe("End position"),
|
|
4625
4653
|
note: z3.string().optional().describe("Reason for flagging"),
|
|
4626
4654
|
documentId: z3.string().optional().describe("Target document ID (defaults to active document)"),
|
|
4627
|
-
priority: AnnotationPrioritySchema.optional().describe(
|
|
4628
|
-
"Annotation priority. Set to 'urgent' for critical issues that should be visible even when the user has interruption mode set to urgent-only. Flags and questions are implicitly urgent."
|
|
4629
|
-
),
|
|
4630
4655
|
textSnapshot: z3.string().optional().describe(
|
|
4631
4656
|
"Expected text at [from, to] \u2014 returns RANGE_MOVED with relocated range on mismatch, or RANGE_GONE if text was deleted"
|
|
4632
4657
|
)
|
|
4633
4658
|
},
|
|
4634
4659
|
withErrorBoundary(
|
|
4635
4660
|
"tandem_flag",
|
|
4636
|
-
async ({ from: rawFrom, to: rawTo, note, documentId,
|
|
4661
|
+
async ({ from: rawFrom, to: rawTo, note, documentId, textSnapshot }) => {
|
|
4637
4662
|
const da = getDocAndAnnotations(documentId);
|
|
4638
4663
|
if (!da) return noDocumentError();
|
|
4639
4664
|
const from = toFlatOffset(rawFrom);
|
|
@@ -4645,7 +4670,6 @@ function registerAnnotationTools(server) {
|
|
|
4645
4670
|
}
|
|
4646
4671
|
const snap = captureSnapshot(da.ydoc, result.range.from, result.range.to);
|
|
4647
4672
|
const id = createAnnotation(da.map, da.ydoc, "flag", result, note || "", {
|
|
4648
|
-
...priority ? { priority } : {},
|
|
4649
4673
|
textSnapshot: snap
|
|
4650
4674
|
});
|
|
4651
4675
|
return mcpSuccess({ annotationId: id });
|
|
@@ -4801,20 +4825,20 @@ function registerAnnotationTools(server) {
|
|
|
4801
4825
|
// src/server/mcp/api-routes.ts
|
|
4802
4826
|
init_constants();
|
|
4803
4827
|
init_types2();
|
|
4828
|
+
init_notifications();
|
|
4829
|
+
init_provider();
|
|
4804
4830
|
init_document_model();
|
|
4805
|
-
init_file_opener();
|
|
4806
4831
|
init_document_service();
|
|
4807
|
-
init_notifications();
|
|
4808
4832
|
|
|
4809
4833
|
// src/server/mcp/docx-apply.ts
|
|
4810
|
-
init_document_service();
|
|
4811
4834
|
init_constants();
|
|
4835
|
+
init_file_io();
|
|
4812
4836
|
init_positions2();
|
|
4813
4837
|
init_document_model();
|
|
4814
|
-
|
|
4815
|
-
import { z as z4 } from "zod";
|
|
4838
|
+
init_document_service();
|
|
4816
4839
|
import fs6 from "fs/promises";
|
|
4817
4840
|
import path8 from "path";
|
|
4841
|
+
import { z as z4 } from "zod";
|
|
4818
4842
|
async function applyChangesCore(documentId, author, backupPath) {
|
|
4819
4843
|
const r = requireDocument(documentId);
|
|
4820
4844
|
if (!r) throw Object.assign(new Error("No document is open."), { code: "NO_DOCUMENT" });
|
|
@@ -4988,7 +5012,7 @@ function registerApplyTools(server) {
|
|
|
4988
5012
|
}
|
|
4989
5013
|
|
|
4990
5014
|
// src/server/mcp/api-routes.ts
|
|
4991
|
-
|
|
5015
|
+
init_file_opener();
|
|
4992
5016
|
function isHostAllowed(host) {
|
|
4993
5017
|
const reqHost = (host ?? "").split(":")[0];
|
|
4994
5018
|
return reqHost === "localhost" || reqHost === "127.0.0.1";
|
|
@@ -5225,12 +5249,12 @@ function registerApiRoutes(app, largeBody) {
|
|
|
5225
5249
|
}
|
|
5226
5250
|
|
|
5227
5251
|
// src/server/mcp/awareness.ts
|
|
5228
|
-
|
|
5229
|
-
import { z as z5 } from "zod";
|
|
5252
|
+
init_constants();
|
|
5230
5253
|
init_types2();
|
|
5231
5254
|
init_utils();
|
|
5232
|
-
init_constants();
|
|
5233
5255
|
init_queue();
|
|
5256
|
+
init_provider();
|
|
5257
|
+
import { z as z5 } from "zod";
|
|
5234
5258
|
var surfacedIds = /* @__PURE__ */ new Set();
|
|
5235
5259
|
function registerAwarenessTools(server) {
|
|
5236
5260
|
server.tool(
|
|
@@ -5904,60 +5928,11 @@ async function startMcpServerHttp(port, host = "127.0.0.1") {
|
|
|
5904
5928
|
});
|
|
5905
5929
|
}
|
|
5906
5930
|
|
|
5907
|
-
// src/server/index.ts
|
|
5908
|
-
init_provider();
|
|
5909
|
-
init_constants();
|
|
5910
|
-
init_manager();
|
|
5911
|
-
init_platform();
|
|
5912
|
-
|
|
5913
|
-
// src/server/version-check.ts
|
|
5914
|
-
import fs7 from "fs/promises";
|
|
5915
|
-
import path9 from "path";
|
|
5916
|
-
async function checkVersionChange(currentVersion, versionFilePath) {
|
|
5917
|
-
let storedVersion = null;
|
|
5918
|
-
try {
|
|
5919
|
-
storedVersion = (await fs7.readFile(versionFilePath, "utf-8")).trim();
|
|
5920
|
-
} catch (err) {
|
|
5921
|
-
if (err.code !== "ENOENT") {
|
|
5922
|
-
console.error("[Tandem] Failed to read last-seen-version:", err);
|
|
5923
|
-
}
|
|
5924
|
-
}
|
|
5925
|
-
const result = storedVersion === null ? "first-install" : storedVersion === currentVersion ? "current" : "upgraded";
|
|
5926
|
-
if (result !== "current") {
|
|
5927
|
-
await fs7.mkdir(path9.dirname(versionFilePath), { recursive: true });
|
|
5928
|
-
await fs7.writeFile(versionFilePath, currentVersion, "utf-8");
|
|
5929
|
-
}
|
|
5930
|
-
return result;
|
|
5931
|
-
}
|
|
5932
|
-
|
|
5933
|
-
// src/server/error-filter.ts
|
|
5934
|
-
function isKnownHocuspocusError(err) {
|
|
5935
|
-
if (!(err instanceof Error)) return false;
|
|
5936
|
-
if ("code" in err) {
|
|
5937
|
-
const code = err.code;
|
|
5938
|
-
if (typeof code === "string" && code.startsWith("WS_ERR_")) {
|
|
5939
|
-
return true;
|
|
5940
|
-
}
|
|
5941
|
-
}
|
|
5942
|
-
const msg = err.message;
|
|
5943
|
-
if (msg.startsWith("WebSocket is not open")) return true;
|
|
5944
|
-
if (msg.includes("Unexpected end of array") || msg.includes("Integer out of Range")) return true;
|
|
5945
|
-
if (msg.startsWith("Received a message with an unknown type:")) return true;
|
|
5946
|
-
return false;
|
|
5947
|
-
}
|
|
5948
|
-
|
|
5949
|
-
// src/server/index.ts
|
|
5950
|
-
init_queue();
|
|
5951
|
-
init_document_service();
|
|
5952
|
-
init_file_watcher();
|
|
5953
|
-
init_file_opener();
|
|
5954
|
-
init_document_model();
|
|
5955
|
-
|
|
5956
5931
|
// src/server/mcp/tutorial-annotations.ts
|
|
5957
5932
|
init_constants();
|
|
5933
|
+
init_types2();
|
|
5958
5934
|
init_queue();
|
|
5959
5935
|
init_positions2();
|
|
5960
|
-
init_types2();
|
|
5961
5936
|
init_document_model();
|
|
5962
5937
|
var TUTORIAL_ANNOTATIONS = [
|
|
5963
5938
|
{
|
|
@@ -6033,6 +6008,31 @@ function injectTutorialAnnotations(doc) {
|
|
|
6033
6008
|
}
|
|
6034
6009
|
|
|
6035
6010
|
// src/server/index.ts
|
|
6011
|
+
init_platform();
|
|
6012
|
+
init_manager();
|
|
6013
|
+
|
|
6014
|
+
// src/server/version-check.ts
|
|
6015
|
+
import fs7 from "fs/promises";
|
|
6016
|
+
import path9 from "path";
|
|
6017
|
+
async function checkVersionChange(currentVersion, versionFilePath) {
|
|
6018
|
+
let storedVersion = null;
|
|
6019
|
+
try {
|
|
6020
|
+
storedVersion = (await fs7.readFile(versionFilePath, "utf-8")).trim();
|
|
6021
|
+
} catch (err) {
|
|
6022
|
+
if (err.code !== "ENOENT") {
|
|
6023
|
+
console.error("[Tandem] Failed to read last-seen-version:", err);
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
const result = storedVersion === null ? "first-install" : storedVersion === currentVersion ? "current" : "upgraded";
|
|
6027
|
+
if (result !== "current") {
|
|
6028
|
+
await fs7.mkdir(path9.dirname(versionFilePath), { recursive: true });
|
|
6029
|
+
await fs7.writeFile(versionFilePath, currentVersion, "utf-8");
|
|
6030
|
+
}
|
|
6031
|
+
return result;
|
|
6032
|
+
}
|
|
6033
|
+
|
|
6034
|
+
// src/server/index.ts
|
|
6035
|
+
init_provider();
|
|
6036
6036
|
var isProduction = process.env.TANDEM_OPEN_BROWSER === "1";
|
|
6037
6037
|
var SUPPRESSED_PATTERNS = [/^\[mammoth\]/, /Invalid access/i, /^\s*add yjs type/i];
|
|
6038
6038
|
var originalStderrWrite = process.stderr.write.bind(process.stderr);
|