tandem-editor 0.11.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +135 -65
- package/README.md +9 -9
- package/dist/channel/index.js +59 -18
- package/dist/channel/index.js.map +1 -1
- package/dist/cli/index.js +102 -31
- package/dist/cli/index.js.map +1 -1
- package/dist/client/assets/{CoworkSettings-DK3jjdwK.js → CoworkSettings-C0cS9R7L.js} +1 -1
- package/dist/client/assets/event-CNdo2oXa.js +1 -0
- package/dist/client/assets/index-Dn5JwXA3.css +1 -0
- package/dist/client/assets/index-n-vFW5By.js +299 -0
- package/dist/client/assets/webview-Bhf-n_os.js +1 -0
- package/dist/client/assets/window-DePn7tLG.js +1 -0
- package/dist/client/index.html +4 -2
- package/dist/monitor/index.js +23 -16
- package/dist/monitor/index.js.map +1 -1
- package/dist/server/index.js +388 -331
- package/dist/server/index.js.map +1 -1
- package/package.json +7 -3
- package/dist/client/assets/index-CfT503n4.js +0 -297
- package/dist/client/assets/index-DeJe09pn.css +0 -1
- package/dist/client/assets/webview-Ben21ZLJ.js +0 -1
- package/dist/client/assets/window-BxBvHL5k.js +0 -1
package/dist/server/index.js
CHANGED
|
@@ -40,7 +40,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
40
40
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
41
41
|
|
|
42
42
|
// src/shared/constants.ts
|
|
43
|
-
var DEFAULT_WS_PORT, DEFAULT_MCP_PORT, TANDEM_REPO_URL, TANDEM_ISSUES_NEW_URL, SUPPORTED_EXTENSIONS, MAX_FILE_SIZE,
|
|
43
|
+
var DEFAULT_WS_PORT, DEFAULT_MCP_PORT, TANDEM_REPO_URL, TANDEM_ISSUES_NEW_URL, SUPPORTED_EXTENSIONS, MAX_FILE_SIZE, 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_ANNOTATION_REPLIES, Y_MAP_SAVED_AT_VERSION, Y_MAP_AUTHORSHIP, Y_MAP_SELECTION, Y_MAP_ACTIVITY, Y_MAP_CLAUDE, Y_MAP_OPEN_DOCUMENTS, Y_MAP_ACTIVE_DOCUMENT_ID, Y_MAP_GENERATION_ID, Y_MAP_READ_ONLY, Y_MAP_STORE_READ_ONLY, NOTIFICATION_BUFFER_SIZE, TUTORIAL_ANNOTATION_PREFIX, CHANNEL_EVENT_BUFFER_SIZE, CHANNEL_EVENT_BUFFER_AGE_MS, CHANNEL_SSE_KEEPALIVE_MS, TOKEN_FILE_NAME, DEFAULT_BIND_HOST, TANDEM_ALLOW_UNAUTHENTICATED_LAN_ENV, TAURI_HOSTNAME;
|
|
44
44
|
var init_constants = __esm({
|
|
45
45
|
"src/shared/constants.ts"() {
|
|
46
46
|
"use strict";
|
|
@@ -50,8 +50,6 @@ var init_constants = __esm({
|
|
|
50
50
|
TANDEM_ISSUES_NEW_URL = `${TANDEM_REPO_URL}/issues/new`;
|
|
51
51
|
SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".txt", ".html", ".htm", ".docx"]);
|
|
52
52
|
MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
53
|
-
MAX_WS_PAYLOAD = 10 * 1024 * 1024;
|
|
54
|
-
IDLE_TIMEOUT = 30 * 60 * 1e3;
|
|
55
53
|
SESSION_MAX_AGE = 30 * 24 * 60 * 60 * 1e3;
|
|
56
54
|
TANDEM_MODE_DEFAULT = "tandem";
|
|
57
55
|
SELECTION_DWELL_DEFAULT_MS = 1e3;
|
|
@@ -73,9 +71,11 @@ var init_constants = __esm({
|
|
|
73
71
|
Y_MAP_AUTHORSHIP = "authorship";
|
|
74
72
|
Y_MAP_SELECTION = "selection";
|
|
75
73
|
Y_MAP_ACTIVITY = "activity";
|
|
74
|
+
Y_MAP_CLAUDE = "claude";
|
|
76
75
|
Y_MAP_OPEN_DOCUMENTS = "openDocuments";
|
|
77
76
|
Y_MAP_ACTIVE_DOCUMENT_ID = "activeDocumentId";
|
|
78
77
|
Y_MAP_GENERATION_ID = "generationId";
|
|
78
|
+
Y_MAP_READ_ONLY = "readOnly";
|
|
79
79
|
Y_MAP_STORE_READ_ONLY = "storeReadOnly";
|
|
80
80
|
NOTIFICATION_BUFFER_SIZE = 50;
|
|
81
81
|
TUTORIAL_ANNOTATION_PREFIX = "tutorial-";
|
|
@@ -18580,7 +18580,7 @@ var init_constructs = __esm({
|
|
|
18580
18580
|
});
|
|
18581
18581
|
|
|
18582
18582
|
// node_modules/micromark/lib/create-tokenizer.js
|
|
18583
|
-
function createTokenizer(
|
|
18583
|
+
function createTokenizer(parser, initialize, from3) {
|
|
18584
18584
|
let point3 = {
|
|
18585
18585
|
_bufferIndex: -1,
|
|
18586
18586
|
_index: 0,
|
|
@@ -18609,7 +18609,7 @@ function createTokenizer(parser2, initialize, from3) {
|
|
|
18609
18609
|
defineSkip,
|
|
18610
18610
|
events: [],
|
|
18611
18611
|
now,
|
|
18612
|
-
parser
|
|
18612
|
+
parser,
|
|
18613
18613
|
previous: null,
|
|
18614
18614
|
sliceSerialize,
|
|
18615
18615
|
sliceStream,
|
|
@@ -18917,7 +18917,7 @@ function parse(options) {
|
|
|
18917
18917
|
/** @type {FullNormalizedExtension} */
|
|
18918
18918
|
combineExtensions([constructs_exports, ...settings.extensions || []])
|
|
18919
18919
|
);
|
|
18920
|
-
const
|
|
18920
|
+
const parser = {
|
|
18921
18921
|
constructs: constructs2,
|
|
18922
18922
|
content: create9(content2),
|
|
18923
18923
|
defined: [],
|
|
@@ -18927,11 +18927,11 @@ function parse(options) {
|
|
|
18927
18927
|
string: create9(string),
|
|
18928
18928
|
text: create9(text3)
|
|
18929
18929
|
};
|
|
18930
|
-
return
|
|
18930
|
+
return parser;
|
|
18931
18931
|
function create9(initial2) {
|
|
18932
18932
|
return creator;
|
|
18933
18933
|
function creator(from3) {
|
|
18934
|
-
return createTokenizer(
|
|
18934
|
+
return createTokenizer(parser, initial2, from3);
|
|
18935
18935
|
}
|
|
18936
18936
|
}
|
|
18937
18937
|
}
|
|
@@ -19828,8 +19828,8 @@ var init_mdast_util_from_markdown = __esm({
|
|
|
19828
19828
|
// node_modules/remark-parse/lib/index.js
|
|
19829
19829
|
function remarkParse(options) {
|
|
19830
19830
|
const self2 = this;
|
|
19831
|
-
self2.parser =
|
|
19832
|
-
function
|
|
19831
|
+
self2.parser = parser;
|
|
19832
|
+
function parser(doc) {
|
|
19833
19833
|
return fromMarkdown(doc, {
|
|
19834
19834
|
...self2.data("settings"),
|
|
19835
19835
|
...options,
|
|
@@ -21027,9 +21027,9 @@ var init_lib22 = __esm({
|
|
|
21027
21027
|
parse(file) {
|
|
21028
21028
|
this.freeze();
|
|
21029
21029
|
const realFile = vfile(file);
|
|
21030
|
-
const
|
|
21031
|
-
assertParser("parse",
|
|
21032
|
-
return
|
|
21030
|
+
const parser = this.parser || this.Parser;
|
|
21031
|
+
assertParser("parse", parser);
|
|
21032
|
+
return parser(String(realFile), realFile);
|
|
21033
21033
|
}
|
|
21034
21034
|
/**
|
|
21035
21035
|
* Process the given file as configured on the processor.
|
|
@@ -21914,15 +21914,50 @@ var init_mdast_ydoc = __esm({
|
|
|
21914
21914
|
});
|
|
21915
21915
|
|
|
21916
21916
|
// src/server/file-io/markdown.ts
|
|
21917
|
+
function normalizeLabel(s) {
|
|
21918
|
+
return s.replace(/[\t\n\r ]+/g, " ").trim().toLowerCase();
|
|
21919
|
+
}
|
|
21917
21920
|
function loadMarkdown(doc, markdown) {
|
|
21918
|
-
const tree =
|
|
21921
|
+
const tree = mdParser.parse(markdown);
|
|
21919
21922
|
mdastToYDoc(doc, tree);
|
|
21920
21923
|
}
|
|
21921
21924
|
function saveMarkdown(doc) {
|
|
21922
|
-
|
|
21923
|
-
|
|
21925
|
+
return serializeMdast(yDocToMdast(doc));
|
|
21926
|
+
}
|
|
21927
|
+
function serializeMdast(tree) {
|
|
21928
|
+
const refDefs = /* @__PURE__ */ new Set();
|
|
21929
|
+
visit(tree, "definition", (node2) => {
|
|
21930
|
+
refDefs.add(node2.identifier);
|
|
21931
|
+
});
|
|
21932
|
+
return unified().use(remarkGfm).use(remarkStringify, {
|
|
21933
|
+
...stringifyOptions,
|
|
21934
|
+
handlers: {
|
|
21935
|
+
// Call state.safe() first (mirroring the default text handler) so
|
|
21936
|
+
// block-context escapes (line-leading `# `, `- `, `> `, fence runs,
|
|
21937
|
+
// table pipes, setext underlines) remain intact, then selectively
|
|
21938
|
+
// un-escape intra-text noise that the default `unsafe` table over-flags.
|
|
21939
|
+
//
|
|
21940
|
+
// GFM extensions (autolink-literal `@`/`.`/`:`, strikethrough `~~`,
|
|
21941
|
+
// table `|`) register no `text` handler and contribute `unsafe` entries
|
|
21942
|
+
// that flow through safe(). Of those, only `~` is un-escaped below
|
|
21943
|
+
// (single `~` is harmless because GFM strikethrough requires `~~`).
|
|
21944
|
+
text(node2, _parent, state, info) {
|
|
21945
|
+
let s = state.safe(node2.value, info);
|
|
21946
|
+
const nextStartsBracket = typeof info.after === "string" && info.after.startsWith("[");
|
|
21947
|
+
s = s.replace(/\\\[([^\\\]\n`]+)\](?!\s*[:([])/g, (match, label, offset) => {
|
|
21948
|
+
const atEnd = offset + match.length === s.length;
|
|
21949
|
+
if (atEnd && nextStartsBracket) return match;
|
|
21950
|
+
return refDefs.has(normalizeLabel(label)) ? match : `[${label}]`;
|
|
21951
|
+
});
|
|
21952
|
+
s = s.replace(/(?<=\w)\\_(?=\w)/g, "_");
|
|
21953
|
+
s = s.replace(/(?<![`\\])\\`(?!`)/g, "`");
|
|
21954
|
+
s = s.replace(/\\~(?!~)/g, "~");
|
|
21955
|
+
return s;
|
|
21956
|
+
}
|
|
21957
|
+
}
|
|
21958
|
+
}).stringify(tree);
|
|
21924
21959
|
}
|
|
21925
|
-
var
|
|
21960
|
+
var mdParser, stringifyOptions;
|
|
21926
21961
|
var init_markdown = __esm({
|
|
21927
21962
|
"src/server/file-io/markdown.ts"() {
|
|
21928
21963
|
"use strict";
|
|
@@ -21930,15 +21965,16 @@ var init_markdown = __esm({
|
|
|
21930
21965
|
init_remark_parse();
|
|
21931
21966
|
init_remark_stringify();
|
|
21932
21967
|
init_unified();
|
|
21968
|
+
init_unist_util_visit();
|
|
21933
21969
|
init_mdast_ydoc();
|
|
21934
|
-
|
|
21935
|
-
|
|
21970
|
+
mdParser = unified().use(remarkParse).use(remarkGfm).freeze();
|
|
21971
|
+
stringifyOptions = {
|
|
21936
21972
|
bullet: "-",
|
|
21937
21973
|
emphasis: "*",
|
|
21938
21974
|
strong: "*",
|
|
21939
21975
|
listItemIndent: "one",
|
|
21940
21976
|
rule: "-"
|
|
21941
|
-
}
|
|
21977
|
+
};
|
|
21942
21978
|
}
|
|
21943
21979
|
});
|
|
21944
21980
|
|
|
@@ -51802,8 +51838,8 @@ var require_parser = __commonJS({
|
|
|
51802
51838
|
"use strict";
|
|
51803
51839
|
var TokenIterator = require_TokenIterator();
|
|
51804
51840
|
exports3.Parser = function(options) {
|
|
51805
|
-
var parseTokens = function(
|
|
51806
|
-
return
|
|
51841
|
+
var parseTokens = function(parser, tokens) {
|
|
51842
|
+
return parser(new TokenIterator(tokens));
|
|
51807
51843
|
};
|
|
51808
51844
|
return {
|
|
51809
51845
|
parseTokens
|
|
@@ -52127,16 +52163,16 @@ var require_rules = __commonJS({
|
|
|
52127
52163
|
parsers = Array.prototype.slice.call(arguments, 1);
|
|
52128
52164
|
}
|
|
52129
52165
|
return function(input) {
|
|
52130
|
-
return lazyIterators.fromArray(parsers).map(function(
|
|
52131
|
-
return
|
|
52166
|
+
return lazyIterators.fromArray(parsers).map(function(parser) {
|
|
52167
|
+
return parser(input);
|
|
52132
52168
|
}).filter(function(result2) {
|
|
52133
52169
|
return result2.isSuccess() || result2.isError();
|
|
52134
52170
|
}).first() || describeTokenMismatch(input, name2);
|
|
52135
52171
|
};
|
|
52136
52172
|
};
|
|
52137
|
-
exports3.then = function(
|
|
52173
|
+
exports3.then = function(parser, func) {
|
|
52138
52174
|
return function(input) {
|
|
52139
|
-
var result2 =
|
|
52175
|
+
var result2 = parser(input);
|
|
52140
52176
|
if (!result2.map) {
|
|
52141
52177
|
console.log(result2);
|
|
52142
52178
|
}
|
|
@@ -52146,19 +52182,19 @@ var require_rules = __commonJS({
|
|
|
52146
52182
|
exports3.sequence = function() {
|
|
52147
52183
|
var parsers = Array.prototype.slice.call(arguments, 0);
|
|
52148
52184
|
var rule = function(input) {
|
|
52149
|
-
var result2 = _3.foldl(parsers, function(memo,
|
|
52185
|
+
var result2 = _3.foldl(parsers, function(memo, parser) {
|
|
52150
52186
|
var result3 = memo.result;
|
|
52151
52187
|
var hasCut = memo.hasCut;
|
|
52152
52188
|
if (!result3.isSuccess()) {
|
|
52153
52189
|
return { result: result3, hasCut };
|
|
52154
52190
|
}
|
|
52155
|
-
var subResult =
|
|
52191
|
+
var subResult = parser(result3.remaining());
|
|
52156
52192
|
if (subResult.isCut()) {
|
|
52157
52193
|
return { result: result3, hasCut: true };
|
|
52158
52194
|
} else if (subResult.isSuccess()) {
|
|
52159
52195
|
var values2;
|
|
52160
|
-
if (
|
|
52161
|
-
values2 = result3.value().withValue(
|
|
52196
|
+
if (parser.isCaptured) {
|
|
52197
|
+
values2 = result3.value().withValue(parser, subResult.value());
|
|
52162
52198
|
} else {
|
|
52163
52199
|
values2 = result3.value();
|
|
52164
52200
|
}
|
|
@@ -52536,8 +52572,8 @@ var require_bottom_up = __commonJS({
|
|
|
52536
52572
|
}
|
|
52537
52573
|
exports3.infix = function(name2, ruleBuilder) {
|
|
52538
52574
|
function map5(func) {
|
|
52539
|
-
return exports3.infix(name2, function(
|
|
52540
|
-
var rule = ruleBuilder(
|
|
52575
|
+
return exports3.infix(name2, function(parser) {
|
|
52576
|
+
var rule = ruleBuilder(parser);
|
|
52541
52577
|
return function(tokens) {
|
|
52542
52578
|
var result2 = rule(tokens);
|
|
52543
52579
|
return result2.map(function(right) {
|
|
@@ -53089,8 +53125,8 @@ var require_style_reader = __commonJS({
|
|
|
53089
53125
|
);
|
|
53090
53126
|
function parseString(rule, string5) {
|
|
53091
53127
|
var tokens = tokenise(string5);
|
|
53092
|
-
var
|
|
53093
|
-
var parseResult =
|
|
53128
|
+
var parser = lop.Parser();
|
|
53129
|
+
var parseResult = parser.parseTokens(rule, tokens);
|
|
53094
53130
|
if (parseResult.isSuccess()) {
|
|
53095
53131
|
return results.success(parseResult.value());
|
|
53096
53132
|
} else {
|
|
@@ -55712,8 +55748,8 @@ var init_dist2 = __esm({
|
|
|
55712
55748
|
this.options = options ?? defaultOptions;
|
|
55713
55749
|
this.elementCB = elementCB ?? null;
|
|
55714
55750
|
}
|
|
55715
|
-
onparserinit(
|
|
55716
|
-
this.parser =
|
|
55751
|
+
onparserinit(parser) {
|
|
55752
|
+
this.parser = parser;
|
|
55717
55753
|
}
|
|
55718
55754
|
// Resets the handler back to starting state
|
|
55719
55755
|
onreset() {
|
|
@@ -60792,7 +60828,7 @@ var init_types2 = __esm({
|
|
|
60792
60828
|
});
|
|
60793
60829
|
|
|
60794
60830
|
// src/shared/types.ts
|
|
60795
|
-
var AnnotationTypeSchema, AnnotationStatusSchema, HighlightColorSchema, SeveritySchema, TandemModeSchema, AuthorSchema, ReplyAuthorSchema, AnnotationActionSchema, ExportFormatSchema, DocumentFormatSchema, ToolErrorCodeSchema;
|
|
60831
|
+
var AnnotationTypeSchema, AnnotationStatusSchema, HighlightColorSchema, SeveritySchema, TandemModeSchema, AuthorSchema, ReplyAuthorSchema, AnnotationActionSchema, ExportFormatSchema, DocumentFormatSchema, ToolErrorCodeSchema, ChannelErrorCodeSchema;
|
|
60796
60832
|
var init_types3 = __esm({
|
|
60797
60833
|
"src/shared/types.ts"() {
|
|
60798
60834
|
"use strict";
|
|
@@ -60815,9 +60851,13 @@ var init_types3 = __esm({
|
|
|
60815
60851
|
"FILE_NOT_FOUND",
|
|
60816
60852
|
"NO_DOCUMENT",
|
|
60817
60853
|
"INVALID_RANGE",
|
|
60854
|
+
"INVALID_ARGUMENT",
|
|
60855
|
+
"NOT_FOUND",
|
|
60856
|
+
"ANNOTATION_RESOLVED",
|
|
60818
60857
|
"FORMAT_ERROR",
|
|
60819
60858
|
"PERMISSION_DENIED"
|
|
60820
60859
|
]);
|
|
60860
|
+
ChannelErrorCodeSchema = external_exports.enum(["CHANNEL_CONNECT_FAILED", "MONITOR_CONNECT_FAILED"]);
|
|
60821
60861
|
}
|
|
60822
60862
|
});
|
|
60823
60863
|
|
|
@@ -67354,7 +67394,7 @@ async function startHocuspocus(port) {
|
|
|
67354
67394
|
throw new Error("Connection rejected: missing origin header");
|
|
67355
67395
|
}
|
|
67356
67396
|
const url = new URL(origin);
|
|
67357
|
-
if (url.hostname !== "
|
|
67397
|
+
if (url.hostname !== "127.0.0.1" && url.hostname !== TAURI_HOSTNAME) {
|
|
67358
67398
|
console.error(`[Hocuspocus] Rejected connection from origin: ${origin}`);
|
|
67359
67399
|
throw new Error("Connection rejected: invalid origin");
|
|
67360
67400
|
}
|
|
@@ -67924,7 +67964,7 @@ function makeAwarenessObserver(deps) {
|
|
|
67924
67964
|
const userAwareness = doc.getMap(Y_MAP_USER_AWARENESS);
|
|
67925
67965
|
let selectionDwellTimer = null;
|
|
67926
67966
|
const awarenessObs = (event, txn) => {
|
|
67927
|
-
if (txn.origin === MCP_ORIGIN) return;
|
|
67967
|
+
if (txn.origin === MCP_ORIGIN || txn.origin === FILE_SYNC_ORIGIN) return;
|
|
67928
67968
|
if (event.keysChanged.has(Y_MAP_SELECTION)) {
|
|
67929
67969
|
const selection = userAwareness.get(Y_MAP_SELECTION);
|
|
67930
67970
|
if (selectionDwellTimer) {
|
|
@@ -68169,13 +68209,18 @@ function refreshRange(ann, ydoc, map5) {
|
|
|
68169
68209
|
if (map5) map5.set(ann.id, updated);
|
|
68170
68210
|
return updated;
|
|
68171
68211
|
}
|
|
68172
|
-
function refreshAllRanges(annotations, ydoc, map5) {
|
|
68212
|
+
function refreshAllRanges(annotations, ydoc, map5, opts) {
|
|
68173
68213
|
const results = [];
|
|
68174
|
-
|
|
68214
|
+
const run2 = () => {
|
|
68175
68215
|
for (const ann of annotations) {
|
|
68176
68216
|
results.push(refreshRange(ann, ydoc, map5));
|
|
68177
68217
|
}
|
|
68178
|
-
}
|
|
68218
|
+
};
|
|
68219
|
+
if (opts?.skipTransact) {
|
|
68220
|
+
run2();
|
|
68221
|
+
} else {
|
|
68222
|
+
ydoc.transact(run2, MCP_ORIGIN);
|
|
68223
|
+
}
|
|
68179
68224
|
return results;
|
|
68180
68225
|
}
|
|
68181
68226
|
var init_positions2 = __esm({
|
|
@@ -69350,7 +69395,7 @@ function registerDocumentTools(server) {
|
|
|
69350
69395
|
const doc = getOrCreateDocument(current.docName);
|
|
69351
69396
|
const awarenessMap = doc.getMap(Y_MAP_AWARENESS);
|
|
69352
69397
|
doc.transact(
|
|
69353
|
-
() => awarenessMap.set(
|
|
69398
|
+
() => awarenessMap.set(Y_MAP_CLAUDE, {
|
|
69354
69399
|
status: text5,
|
|
69355
69400
|
timestamp: Date.now(),
|
|
69356
69401
|
active: true,
|
|
@@ -69540,6 +69585,13 @@ function addReplyToAnnotation(ydoc, annotationsMap, annotationId, text5, author,
|
|
|
69540
69585
|
const raw = annotationsMap.get(annotationId);
|
|
69541
69586
|
if (!raw) return { ok: false, error: `Annotation ${annotationId} not found`, code: "NOT_FOUND" };
|
|
69542
69587
|
const ann = sanitizeAnnotation(raw, makeOnLossy(void 0));
|
|
69588
|
+
if (ann.type !== "comment") {
|
|
69589
|
+
return {
|
|
69590
|
+
ok: false,
|
|
69591
|
+
error: `Cannot reply to a ${ann.type} annotation; only comments support replies`,
|
|
69592
|
+
code: "INVALID_ARGUMENT"
|
|
69593
|
+
};
|
|
69594
|
+
}
|
|
69543
69595
|
if (ann.status !== "pending") {
|
|
69544
69596
|
return {
|
|
69545
69597
|
ok: false,
|
|
@@ -69787,7 +69839,7 @@ function registerAnnotationTools(server) {
|
|
|
69787
69839
|
const repliesMap = getRepliesMap(da.ydoc);
|
|
69788
69840
|
const annotationsWithReplies = results.map((ann) => ({
|
|
69789
69841
|
...ann,
|
|
69790
|
-
replies: collectRepliesForAnnotation(repliesMap, ann.id)
|
|
69842
|
+
replies: ann.type === "comment" ? collectRepliesForAnnotation(repliesMap, ann.id) : []
|
|
69791
69843
|
}));
|
|
69792
69844
|
return mcpSuccess({
|
|
69793
69845
|
annotations: annotationsWithReplies,
|
|
@@ -69808,8 +69860,11 @@ function registerAnnotationTools(server) {
|
|
|
69808
69860
|
const da = getDocAndAnnotations(documentId);
|
|
69809
69861
|
if (!da) return noDocumentError();
|
|
69810
69862
|
const raw = da.map.get(id2);
|
|
69811
|
-
if (!raw) return mcpError("
|
|
69863
|
+
if (!raw) return mcpError("NOT_FOUND", `Annotation ${id2} not found`);
|
|
69812
69864
|
const ann = sanitizeAnnotation(raw, makeOnLossy(da.docHash));
|
|
69865
|
+
if (ann.status !== "pending") {
|
|
69866
|
+
return mcpError("ANNOTATION_NOT_PENDING", `Annotation ${id2} is already ${ann.status}`);
|
|
69867
|
+
}
|
|
69813
69868
|
const updated = {
|
|
69814
69869
|
...ann,
|
|
69815
69870
|
status: action === "accept" ? "accepted" : "dismissed",
|
|
@@ -69830,7 +69885,7 @@ function registerAnnotationTools(server) {
|
|
|
69830
69885
|
const da = getDocAndAnnotations(documentId);
|
|
69831
69886
|
if (!da) return noDocumentError();
|
|
69832
69887
|
const result2 = removeAnnotationById(da.ydoc, da.map, da.filePath, id2);
|
|
69833
|
-
if (!result2.ok) return mcpError("
|
|
69888
|
+
if (!result2.ok) return mcpError("NOT_FOUND", result2.error);
|
|
69834
69889
|
return mcpSuccess({ removed: true, id: id2 });
|
|
69835
69890
|
})
|
|
69836
69891
|
);
|
|
@@ -69850,20 +69905,20 @@ function registerAnnotationTools(server) {
|
|
|
69850
69905
|
const da = getDocAndAnnotations(documentId);
|
|
69851
69906
|
if (!da) return noDocumentError();
|
|
69852
69907
|
const raw = da.map.get(id2);
|
|
69853
|
-
if (!raw) return mcpError("
|
|
69908
|
+
if (!raw) return mcpError("NOT_FOUND", `Annotation ${id2} not found`);
|
|
69854
69909
|
const ann = sanitizeAnnotation(raw, makeOnLossy(da.docHash));
|
|
69855
69910
|
if (ann.status !== "pending") {
|
|
69856
|
-
return mcpError("
|
|
69911
|
+
return mcpError("ANNOTATION_RESOLVED", `Cannot edit a ${ann.status} annotation`);
|
|
69857
69912
|
}
|
|
69858
69913
|
if (content3 === void 0 && newText === void 0 && reason === void 0) {
|
|
69859
69914
|
return mcpError(
|
|
69860
|
-
"
|
|
69915
|
+
"INVALID_ARGUMENT",
|
|
69861
69916
|
"No editable fields provided. Use content, newText, or reason."
|
|
69862
69917
|
);
|
|
69863
69918
|
}
|
|
69864
69919
|
if (newText !== void 0 && ann.type !== "comment") {
|
|
69865
69920
|
return mcpError(
|
|
69866
|
-
"
|
|
69921
|
+
"INVALID_ARGUMENT",
|
|
69867
69922
|
`Cannot set replacement text on a ${ann.type} annotation. Only comments support suggestedText.`
|
|
69868
69923
|
);
|
|
69869
69924
|
}
|
|
@@ -69903,7 +69958,8 @@ function registerAnnotationTools(server) {
|
|
|
69903
69958
|
const fullText = extractText(ydoc);
|
|
69904
69959
|
const enriched = exportable.map((ann) => ({
|
|
69905
69960
|
...ann,
|
|
69906
|
-
|
|
69961
|
+
// ADR-027: only comments surface replies (see tandem_getAnnotations).
|
|
69962
|
+
replies: ann.type === "comment" ? collectRepliesForAnnotation(repliesMap, ann.id) : [],
|
|
69907
69963
|
textSnippet: fullText.slice(
|
|
69908
69964
|
Math.max(0, ann.range.from),
|
|
69909
69965
|
Math.min(fullText.length, ann.range.to)
|
|
@@ -69935,7 +69991,7 @@ function registerAnnotationTools(server) {
|
|
|
69935
69991
|
MCP_ORIGIN
|
|
69936
69992
|
);
|
|
69937
69993
|
if (!result2.ok) {
|
|
69938
|
-
const code3 = result2.code === "NOT_FOUND" ? "NOT_FOUND" : result2.code === "ANNOTATION_RESOLVED" ? "ANNOTATION_RESOLVED" : "INVALID_RANGE";
|
|
69994
|
+
const code3 = result2.code === "NOT_FOUND" ? "NOT_FOUND" : result2.code === "ANNOTATION_RESOLVED" ? "ANNOTATION_RESOLVED" : result2.code === "INVALID_ARGUMENT" ? "INVALID_ARGUMENT" : "INVALID_RANGE";
|
|
69939
69995
|
return mcpError(code3, result2.error);
|
|
69940
69996
|
}
|
|
69941
69997
|
return mcpSuccess({ replyId: result2.replyId, annotationId });
|
|
@@ -69972,6 +70028,7 @@ var init_annotations2 = __esm({
|
|
|
69972
70028
|
var file_opener_exports = {};
|
|
69973
70029
|
__export(file_opener_exports, {
|
|
69974
70030
|
SUPPORTED_EXTENSIONS: () => SUPPORTED_EXTENSIONS,
|
|
70031
|
+
__testEvictPartialDocState: () => evictPartialDocState,
|
|
69975
70032
|
openFileByPath: () => openFileByPath,
|
|
69976
70033
|
openFileFromContent: () => openFileFromContent,
|
|
69977
70034
|
openScratchpad: () => openScratchpad
|
|
@@ -69995,7 +70052,8 @@ async function openFileByPath(filePath, options) {
|
|
|
69995
70052
|
const forceReload = options?.force === true;
|
|
69996
70053
|
if (forceReload) {
|
|
69997
70054
|
const doc2 = getDocument(id2) ?? getOrCreateDocument(id2);
|
|
69998
|
-
await
|
|
70055
|
+
const reloadBuffer = format === "docx" ? await fs4.readFile(resolved) : await fs4.readFile(resolved, "utf-8");
|
|
70056
|
+
await clearAndReload(id2, doc2, resolved, format, existing, reloadBuffer);
|
|
69999
70057
|
addDoc(id2, { id: id2, filePath: resolved, format, readOnly: readOnly2, source: "file" });
|
|
70000
70058
|
setActiveDocId(id2);
|
|
70001
70059
|
await wireAnnotationStore(id2, doc2, resolved);
|
|
@@ -70027,7 +70085,7 @@ async function openFileByPath(filePath, options) {
|
|
|
70027
70085
|
const doc = getOrCreateDocument(id2);
|
|
70028
70086
|
const restoredFromSession = await maybeRestoreSession(resolved, doc, fileName);
|
|
70029
70087
|
if (!restoredFromSession) {
|
|
70030
|
-
await loadContentIntoDoc(doc, format, resolved);
|
|
70088
|
+
await loadContentIntoDoc(doc, format, resolved, id2);
|
|
70031
70089
|
}
|
|
70032
70090
|
await finalizeDocOpen(id2, doc, resolved, fileName, format, readOnly2);
|
|
70033
70091
|
return {
|
|
@@ -70062,8 +70120,10 @@ async function openFileFromContent(fileName, content3) {
|
|
|
70062
70120
|
const syntheticPath = `${UPLOAD_PREFIX}${randomUUID2()}/${fileName}`;
|
|
70063
70121
|
const id2 = docIdFromPath(syntheticPath);
|
|
70064
70122
|
const doc = getOrCreateDocument(id2);
|
|
70065
|
-
|
|
70066
|
-
|
|
70123
|
+
await populateDocFromContent(doc, format, content3, id2, {
|
|
70124
|
+
displayName: fileName,
|
|
70125
|
+
dedupSource: syntheticPath
|
|
70126
|
+
});
|
|
70067
70127
|
addDoc(id2, { id: id2, filePath: syntheticPath, format, readOnly: readOnly2, source: "upload" });
|
|
70068
70128
|
setActiveDocId(id2);
|
|
70069
70129
|
writeDocMeta(doc, id2, fileName, format, readOnly2);
|
|
@@ -70151,7 +70211,7 @@ function handleAlreadyOpen(id2, doc, format, resolved, readOnly2, existing, expl
|
|
|
70151
70211
|
if (explicitReadOnly && !existing.readOnly) {
|
|
70152
70212
|
addDoc(id2, { ...existing, readOnly: true });
|
|
70153
70213
|
const meta2 = doc.getMap(Y_MAP_DOCUMENT_META);
|
|
70154
|
-
doc.transact(() => meta2.set(
|
|
70214
|
+
doc.transact(() => meta2.set(Y_MAP_READ_ONLY, true), MCP_ORIGIN);
|
|
70155
70215
|
}
|
|
70156
70216
|
setActiveDocId(id2);
|
|
70157
70217
|
broadcastOpenDocs();
|
|
@@ -70185,11 +70245,131 @@ async function maybeRestoreSession(resolved, doc, fileName) {
|
|
|
70185
70245
|
}
|
|
70186
70246
|
return false;
|
|
70187
70247
|
}
|
|
70188
|
-
async function
|
|
70189
|
-
|
|
70190
|
-
|
|
70191
|
-
|
|
70192
|
-
|
|
70248
|
+
async function prepareContent(format, source, ctx) {
|
|
70249
|
+
if (format === "docx") {
|
|
70250
|
+
if (!Buffer.isBuffer(source)) {
|
|
70251
|
+
throw Object.assign(new Error("prepareContent: docx requires Buffer source"), {
|
|
70252
|
+
code: "INVALID_SOURCE"
|
|
70253
|
+
});
|
|
70254
|
+
}
|
|
70255
|
+
const buffer3 = source;
|
|
70256
|
+
const [html2, comments] = await Promise.all([
|
|
70257
|
+
loadDocx(buffer3),
|
|
70258
|
+
extractDocxComments(buffer3).catch((err) => {
|
|
70259
|
+
console.error("[docx-comments] Comment extraction failed:", err);
|
|
70260
|
+
pushNotification({
|
|
70261
|
+
id: generateNotificationId(),
|
|
70262
|
+
type: "annotation-error",
|
|
70263
|
+
severity: "warning",
|
|
70264
|
+
message: `Failed to import Word comments from ${ctx.displayName}. Document opened without comments.`,
|
|
70265
|
+
dedupKey: `docx-comments:${ctx.dedupSource}`,
|
|
70266
|
+
timestamp: Date.now()
|
|
70267
|
+
});
|
|
70268
|
+
return [];
|
|
70269
|
+
})
|
|
70270
|
+
]);
|
|
70271
|
+
return { format: "docx", html: html2, comments };
|
|
70272
|
+
}
|
|
70273
|
+
const content3 = typeof source === "string" ? source : source.toString("utf-8");
|
|
70274
|
+
if (format === "md") return { format: "md", content: content3 };
|
|
70275
|
+
return { format: "other", content: content3 };
|
|
70276
|
+
}
|
|
70277
|
+
function applyPreparedContent(doc, prepared, ctx) {
|
|
70278
|
+
switch (prepared.format) {
|
|
70279
|
+
case "docx": {
|
|
70280
|
+
htmlToYDoc(doc, prepared.html);
|
|
70281
|
+
if (prepared.comments.length > 0) {
|
|
70282
|
+
const annotMap = doc.getMap(Y_MAP_ANNOTATIONS);
|
|
70283
|
+
const before2 = new Set(annotMap.keys());
|
|
70284
|
+
try {
|
|
70285
|
+
injectCommentsAsAnnotations(doc, prepared.comments);
|
|
70286
|
+
} catch (err) {
|
|
70287
|
+
for (const k of annotMap.keys()) {
|
|
70288
|
+
if (!before2.has(k)) annotMap.delete(k);
|
|
70289
|
+
}
|
|
70290
|
+
console.error(
|
|
70291
|
+
"[docx-comments] inject failed mid-transact; document loads without imported comments:",
|
|
70292
|
+
err
|
|
70293
|
+
);
|
|
70294
|
+
pushNotification({
|
|
70295
|
+
id: generateNotificationId(),
|
|
70296
|
+
type: "annotation-error",
|
|
70297
|
+
severity: "warning",
|
|
70298
|
+
message: `Failed to import some Word comments from ${ctx.displayName}. Document opened, but comments may be missing.`,
|
|
70299
|
+
dedupKey: `docx-comments-inject:${ctx.dedupSource}`,
|
|
70300
|
+
timestamp: Date.now()
|
|
70301
|
+
});
|
|
70302
|
+
}
|
|
70303
|
+
}
|
|
70304
|
+
return;
|
|
70305
|
+
}
|
|
70306
|
+
case "md":
|
|
70307
|
+
loadMarkdown(doc, prepared.content);
|
|
70308
|
+
return;
|
|
70309
|
+
case "other":
|
|
70310
|
+
populateYDoc(doc, prepared.content);
|
|
70311
|
+
return;
|
|
70312
|
+
}
|
|
70313
|
+
}
|
|
70314
|
+
async function loadContentIntoDoc(doc, format, resolved, docId) {
|
|
70315
|
+
const buffer3 = await fs4.readFile(resolved);
|
|
70316
|
+
await populateDocFromContent(doc, format, buffer3, docId, {
|
|
70317
|
+
displayName: path8.basename(resolved),
|
|
70318
|
+
dedupSource: resolved
|
|
70319
|
+
});
|
|
70320
|
+
}
|
|
70321
|
+
async function populateDocFromContent(doc, format, source, docId, ctx) {
|
|
70322
|
+
const prepared = await prepareContent(format, source, ctx);
|
|
70323
|
+
try {
|
|
70324
|
+
doc.transact(() => applyPreparedContent(doc, prepared, ctx), MCP_ORIGIN);
|
|
70325
|
+
} catch (err) {
|
|
70326
|
+
let cleanupOk = true;
|
|
70327
|
+
try {
|
|
70328
|
+
doc.transact(() => {
|
|
70329
|
+
const fragment = doc.getXmlFragment("default");
|
|
70330
|
+
fragment.delete(0, fragment.length);
|
|
70331
|
+
const annotations = doc.getMap(Y_MAP_ANNOTATIONS);
|
|
70332
|
+
for (const k of [...annotations.keys()]) annotations.delete(k);
|
|
70333
|
+
}, MCP_ORIGIN);
|
|
70334
|
+
} catch (cleanupErr) {
|
|
70335
|
+
cleanupOk = false;
|
|
70336
|
+
console.error(
|
|
70337
|
+
"[Tandem] populateDocFromContent: cleanup after populate failure also failed:",
|
|
70338
|
+
cleanupErr
|
|
70339
|
+
);
|
|
70340
|
+
try {
|
|
70341
|
+
evictPartialDocState(doc, docId);
|
|
70342
|
+
} catch (evictErr) {
|
|
70343
|
+
console.error(
|
|
70344
|
+
"[Tandem] populateDocFromContent: eviction after cleanup failure also failed:",
|
|
70345
|
+
evictErr
|
|
70346
|
+
);
|
|
70347
|
+
}
|
|
70348
|
+
}
|
|
70349
|
+
console.error(
|
|
70350
|
+
"[Tandem] populateDocFromContent: populate failed; partial state cleared before rethrow.",
|
|
70351
|
+
{ format, displayName: ctx.displayName, cleanupOk },
|
|
70352
|
+
err
|
|
70353
|
+
);
|
|
70354
|
+
throw err;
|
|
70355
|
+
}
|
|
70356
|
+
}
|
|
70357
|
+
function evictPartialDocState(doc, docId) {
|
|
70358
|
+
if (docId) {
|
|
70359
|
+
clearFileSyncContext(docId);
|
|
70360
|
+
}
|
|
70361
|
+
doc.transact(() => {
|
|
70362
|
+
const annotations = doc.getMap(Y_MAP_ANNOTATIONS);
|
|
70363
|
+
annotations.forEach((_3, k) => annotations.delete(k));
|
|
70364
|
+
const annotationReplies = doc.getMap(Y_MAP_ANNOTATION_REPLIES);
|
|
70365
|
+
annotationReplies.forEach((_3, k) => annotationReplies.delete(k));
|
|
70366
|
+
const awareness = doc.getMap(Y_MAP_AWARENESS);
|
|
70367
|
+
awareness.forEach((_3, k) => awareness.delete(k));
|
|
70368
|
+
const userAwareness = doc.getMap(Y_MAP_USER_AWARENESS);
|
|
70369
|
+
userAwareness.forEach((_3, k) => userAwareness.delete(k));
|
|
70370
|
+
const fragment = doc.getXmlFragment("default");
|
|
70371
|
+
fragment.delete(0, fragment.length);
|
|
70372
|
+
}, FILE_SYNC_ORIGIN);
|
|
70193
70373
|
}
|
|
70194
70374
|
async function finalizeDocOpen(id2, doc, resolved, fileName, format, readOnly2) {
|
|
70195
70375
|
addDoc(id2, { id: id2, filePath: resolved, format, readOnly: readOnly2, source: "file" });
|
|
@@ -70226,7 +70406,7 @@ async function wireAnnotationStore(id2, doc, filePath) {
|
|
|
70226
70406
|
});
|
|
70227
70407
|
}
|
|
70228
70408
|
}
|
|
70229
|
-
async function clearAndReload(id2, doc,
|
|
70409
|
+
async function clearAndReload(id2, doc, resolved, format, existing, source) {
|
|
70230
70410
|
console.error(`[Tandem] clearAndReload: reloading ${id2} from disk`);
|
|
70231
70411
|
const dropped = clearFileSyncContext(id2);
|
|
70232
70412
|
if (dropped) {
|
|
@@ -70236,25 +70416,12 @@ async function clearAndReload(id2, doc, filePath, format, existing) {
|
|
|
70236
70416
|
console.error("[Tandem] clearAndReload: store.clear failed for %s:", id2, err);
|
|
70237
70417
|
}
|
|
70238
70418
|
}
|
|
70419
|
+
const ctx = {
|
|
70420
|
+
displayName: path8.basename(resolved),
|
|
70421
|
+
dedupSource: resolved
|
|
70422
|
+
};
|
|
70423
|
+
const prepared = await prepareContent(format, source, ctx);
|
|
70239
70424
|
const isDocx = format === "docx";
|
|
70240
|
-
let preparedHtml;
|
|
70241
|
-
let preparedComments;
|
|
70242
|
-
let preparedContent;
|
|
70243
|
-
if (isDocx) {
|
|
70244
|
-
const buffer3 = await fs4.readFile(filePath);
|
|
70245
|
-
[preparedHtml, preparedComments] = await Promise.all([
|
|
70246
|
-
loadDocx(buffer3),
|
|
70247
|
-
extractDocxComments(buffer3).catch((err) => {
|
|
70248
|
-
console.error(
|
|
70249
|
-
"[docx-comments] Comment extraction failed; document will reload without imported comments:",
|
|
70250
|
-
err
|
|
70251
|
-
);
|
|
70252
|
-
return [];
|
|
70253
|
-
})
|
|
70254
|
-
]);
|
|
70255
|
-
} else {
|
|
70256
|
-
preparedContent = await fs4.readFile(filePath, "utf-8");
|
|
70257
|
-
}
|
|
70258
70425
|
try {
|
|
70259
70426
|
doc.transact(() => {
|
|
70260
70427
|
const annotations = doc.getMap(Y_MAP_ANNOTATIONS);
|
|
@@ -70265,21 +70432,12 @@ async function clearAndReload(id2, doc, filePath, format, existing) {
|
|
|
70265
70432
|
awareness.forEach((_3, k) => awareness.delete(k));
|
|
70266
70433
|
const userAwareness = doc.getMap(Y_MAP_USER_AWARENESS);
|
|
70267
70434
|
userAwareness.forEach((_3, k) => userAwareness.delete(k));
|
|
70268
|
-
|
|
70269
|
-
htmlToYDoc(doc, preparedHtml);
|
|
70270
|
-
if (preparedComments && preparedComments.length > 0) {
|
|
70271
|
-
injectCommentsAsAnnotations(doc, preparedComments);
|
|
70272
|
-
}
|
|
70273
|
-
} else if (format === "md" && preparedContent !== void 0) {
|
|
70274
|
-
loadMarkdown(doc, preparedContent);
|
|
70275
|
-
} else if (preparedContent !== void 0) {
|
|
70276
|
-
populateYDoc(doc, preparedContent);
|
|
70277
|
-
}
|
|
70435
|
+
applyPreparedContent(doc, prepared, ctx);
|
|
70278
70436
|
const meta2 = doc.getMap(Y_MAP_DOCUMENT_META);
|
|
70279
|
-
meta2.set(
|
|
70437
|
+
meta2.set(Y_MAP_READ_ONLY, isDocx);
|
|
70280
70438
|
meta2.set("format", format);
|
|
70281
70439
|
meta2.set("documentId", id2);
|
|
70282
|
-
meta2.set("fileName", path8.basename(
|
|
70440
|
+
meta2.set("fileName", path8.basename(resolved));
|
|
70283
70441
|
meta2.set(Y_MAP_SAVED_AT_VERSION, Date.now());
|
|
70284
70442
|
}, MCP_ORIGIN);
|
|
70285
70443
|
attachObservers(id2, doc);
|
|
@@ -70307,7 +70465,7 @@ async function initSavedBaseline(doc, filePath) {
|
|
|
70307
70465
|
function writeDocMeta(doc, id2, fileName, format, readOnly2) {
|
|
70308
70466
|
const meta2 = doc.getMap(Y_MAP_DOCUMENT_META);
|
|
70309
70467
|
doc.transact(() => {
|
|
70310
|
-
meta2.set(
|
|
70468
|
+
meta2.set(Y_MAP_READ_ONLY, readOnly2);
|
|
70311
70469
|
meta2.set("format", format);
|
|
70312
70470
|
meta2.set("documentId", id2);
|
|
70313
70471
|
meta2.set("fileName", fileName);
|
|
@@ -70354,7 +70512,7 @@ async function reloadFromDisk(id2, filePath, format) {
|
|
|
70354
70512
|
} else {
|
|
70355
70513
|
populateYDoc(doc, fileContent);
|
|
70356
70514
|
}
|
|
70357
|
-
},
|
|
70515
|
+
}, FILE_SYNC_ORIGIN);
|
|
70358
70516
|
const annotationMap = doc.getMap(Y_MAP_ANNOTATIONS);
|
|
70359
70517
|
const annotations = [];
|
|
70360
70518
|
const reloadDocHash = docHash(filePath);
|
|
@@ -70367,8 +70525,8 @@ async function reloadFromDisk(id2, filePath, format) {
|
|
|
70367
70525
|
)
|
|
70368
70526
|
);
|
|
70369
70527
|
if (annotations.length > 0) {
|
|
70370
|
-
const refreshed = refreshAllRanges(annotations, doc, annotationMap);
|
|
70371
70528
|
doc.transact(() => {
|
|
70529
|
+
const refreshed = refreshAllRanges(annotations, doc, annotationMap, { skipTransact: true });
|
|
70372
70530
|
for (const ann of refreshed) {
|
|
70373
70531
|
if (!ann.textSnapshot) continue;
|
|
70374
70532
|
const vr = validateRange(doc, ann.range.from, ann.range.to, {
|
|
@@ -70840,7 +70998,8 @@ function makeRepliesObserver(deps) {
|
|
|
70840
70998
|
const reply = repliesMap.get(key);
|
|
70841
70999
|
if (!reply || reply.author !== "user") continue;
|
|
70842
71000
|
const parentAnn = annotationsMap.get(reply.annotationId);
|
|
70843
|
-
|
|
71001
|
+
if (!parentAnn || parentAnn.type !== "comment") continue;
|
|
71002
|
+
const textSnippet = parentAnn.textSnapshot ?? "";
|
|
70844
71003
|
pushEvent2({
|
|
70845
71004
|
id: generateEventId(),
|
|
70846
71005
|
type: "annotation:reply",
|
|
@@ -72687,133 +72846,6 @@ var require_browser = __commonJS({
|
|
|
72687
72846
|
}
|
|
72688
72847
|
});
|
|
72689
72848
|
|
|
72690
|
-
// node_modules/has-flag/index.js
|
|
72691
|
-
var require_has_flag = __commonJS({
|
|
72692
|
-
"node_modules/has-flag/index.js"(exports3, module3) {
|
|
72693
|
-
"use strict";
|
|
72694
|
-
module3.exports = (flag, argv = process.argv) => {
|
|
72695
|
-
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
72696
|
-
const position2 = argv.indexOf(prefix + flag);
|
|
72697
|
-
const terminatorPosition = argv.indexOf("--");
|
|
72698
|
-
return position2 !== -1 && (terminatorPosition === -1 || position2 < terminatorPosition);
|
|
72699
|
-
};
|
|
72700
|
-
}
|
|
72701
|
-
});
|
|
72702
|
-
|
|
72703
|
-
// node_modules/supports-color/index.js
|
|
72704
|
-
var require_supports_color = __commonJS({
|
|
72705
|
-
"node_modules/supports-color/index.js"(exports3, module3) {
|
|
72706
|
-
"use strict";
|
|
72707
|
-
var os2 = __require("os");
|
|
72708
|
-
var tty = __require("tty");
|
|
72709
|
-
var hasFlag = require_has_flag();
|
|
72710
|
-
var { env: env3 } = process;
|
|
72711
|
-
var flagForceColor;
|
|
72712
|
-
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
72713
|
-
flagForceColor = 0;
|
|
72714
|
-
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
72715
|
-
flagForceColor = 1;
|
|
72716
|
-
}
|
|
72717
|
-
function envForceColor() {
|
|
72718
|
-
if ("FORCE_COLOR" in env3) {
|
|
72719
|
-
if (env3.FORCE_COLOR === "true") {
|
|
72720
|
-
return 1;
|
|
72721
|
-
}
|
|
72722
|
-
if (env3.FORCE_COLOR === "false") {
|
|
72723
|
-
return 0;
|
|
72724
|
-
}
|
|
72725
|
-
return env3.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env3.FORCE_COLOR, 10), 3);
|
|
72726
|
-
}
|
|
72727
|
-
}
|
|
72728
|
-
function translateLevel(level) {
|
|
72729
|
-
if (level === 0) {
|
|
72730
|
-
return false;
|
|
72731
|
-
}
|
|
72732
|
-
return {
|
|
72733
|
-
level,
|
|
72734
|
-
hasBasic: true,
|
|
72735
|
-
has256: level >= 2,
|
|
72736
|
-
has16m: level >= 3
|
|
72737
|
-
};
|
|
72738
|
-
}
|
|
72739
|
-
function supportsColor2(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
72740
|
-
const noFlagForceColor = envForceColor();
|
|
72741
|
-
if (noFlagForceColor !== void 0) {
|
|
72742
|
-
flagForceColor = noFlagForceColor;
|
|
72743
|
-
}
|
|
72744
|
-
const forceColor2 = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
72745
|
-
if (forceColor2 === 0) {
|
|
72746
|
-
return 0;
|
|
72747
|
-
}
|
|
72748
|
-
if (sniffFlags) {
|
|
72749
|
-
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
72750
|
-
return 3;
|
|
72751
|
-
}
|
|
72752
|
-
if (hasFlag("color=256")) {
|
|
72753
|
-
return 2;
|
|
72754
|
-
}
|
|
72755
|
-
}
|
|
72756
|
-
if (haveStream && !streamIsTTY && forceColor2 === void 0) {
|
|
72757
|
-
return 0;
|
|
72758
|
-
}
|
|
72759
|
-
const min5 = forceColor2 || 0;
|
|
72760
|
-
if (env3.TERM === "dumb") {
|
|
72761
|
-
return min5;
|
|
72762
|
-
}
|
|
72763
|
-
if (process.platform === "win32") {
|
|
72764
|
-
const osRelease = os2.release().split(".");
|
|
72765
|
-
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
72766
|
-
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
72767
|
-
}
|
|
72768
|
-
return 1;
|
|
72769
|
-
}
|
|
72770
|
-
if ("CI" in env3) {
|
|
72771
|
-
if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE", "DRONE"].some((sign) => sign in env3) || env3.CI_NAME === "codeship") {
|
|
72772
|
-
return 1;
|
|
72773
|
-
}
|
|
72774
|
-
return min5;
|
|
72775
|
-
}
|
|
72776
|
-
if ("TEAMCITY_VERSION" in env3) {
|
|
72777
|
-
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env3.TEAMCITY_VERSION) ? 1 : 0;
|
|
72778
|
-
}
|
|
72779
|
-
if (env3.COLORTERM === "truecolor") {
|
|
72780
|
-
return 3;
|
|
72781
|
-
}
|
|
72782
|
-
if ("TERM_PROGRAM" in env3) {
|
|
72783
|
-
const version3 = Number.parseInt((env3.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
72784
|
-
switch (env3.TERM_PROGRAM) {
|
|
72785
|
-
case "iTerm.app":
|
|
72786
|
-
return version3 >= 3 ? 3 : 2;
|
|
72787
|
-
case "Apple_Terminal":
|
|
72788
|
-
return 2;
|
|
72789
|
-
}
|
|
72790
|
-
}
|
|
72791
|
-
if (/-256(color)?$/i.test(env3.TERM)) {
|
|
72792
|
-
return 2;
|
|
72793
|
-
}
|
|
72794
|
-
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env3.TERM)) {
|
|
72795
|
-
return 1;
|
|
72796
|
-
}
|
|
72797
|
-
if ("COLORTERM" in env3) {
|
|
72798
|
-
return 1;
|
|
72799
|
-
}
|
|
72800
|
-
return min5;
|
|
72801
|
-
}
|
|
72802
|
-
function getSupportLevel(stream, options = {}) {
|
|
72803
|
-
const level = supportsColor2(stream, {
|
|
72804
|
-
streamIsTTY: stream && stream.isTTY,
|
|
72805
|
-
...options
|
|
72806
|
-
});
|
|
72807
|
-
return translateLevel(level);
|
|
72808
|
-
}
|
|
72809
|
-
module3.exports = {
|
|
72810
|
-
supportsColor: getSupportLevel,
|
|
72811
|
-
stdout: getSupportLevel({ isTTY: tty.isatty(1) }),
|
|
72812
|
-
stderr: getSupportLevel({ isTTY: tty.isatty(2) })
|
|
72813
|
-
};
|
|
72814
|
-
}
|
|
72815
|
-
});
|
|
72816
|
-
|
|
72817
72849
|
// node_modules/debug/src/node.js
|
|
72818
72850
|
var require_node2 = __commonJS({
|
|
72819
72851
|
"node_modules/debug/src/node.js"(exports3, module3) {
|
|
@@ -72833,7 +72865,7 @@ var require_node2 = __commonJS({
|
|
|
72833
72865
|
);
|
|
72834
72866
|
exports3.colors = [6, 2, 3, 4, 5, 1];
|
|
72835
72867
|
try {
|
|
72836
|
-
const supportsColor2 =
|
|
72868
|
+
const supportsColor2 = __require("supports-color");
|
|
72837
72869
|
if (supportsColor2 && (supportsColor2.stderr || supportsColor2).level >= 2) {
|
|
72838
72870
|
exports3.colors = [
|
|
72839
72871
|
20,
|
|
@@ -102722,7 +102754,6 @@ var require_dist3 = __commonJS({
|
|
|
102722
102754
|
// src/server/mcp/launcher.ts
|
|
102723
102755
|
var launcher_exports = {};
|
|
102724
102756
|
__export(launcher_exports, {
|
|
102725
|
-
killClaude: () => killClaude,
|
|
102726
102757
|
launchClaude: () => launchClaude
|
|
102727
102758
|
});
|
|
102728
102759
|
import { spawn } from "child_process";
|
|
@@ -102731,7 +102762,7 @@ function launchClaude() {
|
|
|
102731
102762
|
return { status: "already_running", pid: claudeProcess.pid };
|
|
102732
102763
|
}
|
|
102733
102764
|
const claudeCmd = process.env.TANDEM_CLAUDE_CMD || "claude";
|
|
102734
|
-
const tandemUrl = `http://
|
|
102765
|
+
const tandemUrl = `http://127.0.0.1:${process.env.TANDEM_MCP_PORT || DEFAULT_MCP_PORT}`;
|
|
102735
102766
|
const args2 = [
|
|
102736
102767
|
"--dangerously-load-development-channels",
|
|
102737
102768
|
"server:tandem-channel",
|
|
@@ -102777,20 +102808,6 @@ function launchClaude() {
|
|
|
102777
102808
|
console.error(`[Launcher] Claude Code launched (pid: ${pid})`);
|
|
102778
102809
|
return { status: "launched", pid };
|
|
102779
102810
|
}
|
|
102780
|
-
function killClaude() {
|
|
102781
|
-
if (claudeProcess && !claudeProcess.killed) {
|
|
102782
|
-
console.error(`[Launcher] Killing Claude Code (pid: ${claudeProcess.pid})`);
|
|
102783
|
-
try {
|
|
102784
|
-
claudeProcess.kill("SIGTERM");
|
|
102785
|
-
} catch (err) {
|
|
102786
|
-
const code3 = err.code;
|
|
102787
|
-
if (code3 !== "ESRCH") {
|
|
102788
|
-
console.error("[Launcher] Failed to kill Claude process:", err);
|
|
102789
|
-
}
|
|
102790
|
-
}
|
|
102791
|
-
claudeProcess = null;
|
|
102792
|
-
}
|
|
102793
|
-
}
|
|
102794
102811
|
var claudeProcess, TANDEM_SYSTEM_PROMPT;
|
|
102795
102812
|
var init_launcher = __esm({
|
|
102796
102813
|
"src/server/mcp/launcher.ts"() {
|
|
@@ -114664,33 +114681,34 @@ function createAuthMiddleware(getToken) {
|
|
|
114664
114681
|
};
|
|
114665
114682
|
}
|
|
114666
114683
|
|
|
114667
|
-
// src/server/open-browser.ts
|
|
114668
|
-
import { execFile } from "child_process";
|
|
114669
|
-
function openBrowser(url) {
|
|
114670
|
-
let command;
|
|
114671
|
-
let args2;
|
|
114672
|
-
if (process.platform === "win32") {
|
|
114673
|
-
command = "cmd";
|
|
114674
|
-
args2 = ["/c", "start", "", url];
|
|
114675
|
-
} else if (process.platform === "darwin") {
|
|
114676
|
-
command = "open";
|
|
114677
|
-
args2 = [url];
|
|
114678
|
-
} else {
|
|
114679
|
-
command = "xdg-open";
|
|
114680
|
-
args2 = [url];
|
|
114681
|
-
}
|
|
114682
|
-
execFile(command, args2, (err) => {
|
|
114683
|
-
if (err) {
|
|
114684
|
-
console.error("[Tandem] Could not open browser automatically.");
|
|
114685
|
-
console.error(`[Tandem] Open this URL manually: ${url}`);
|
|
114686
|
-
}
|
|
114687
|
-
});
|
|
114688
|
-
}
|
|
114689
|
-
|
|
114690
114684
|
// src/server/mcp/server.ts
|
|
114691
114685
|
init_platform();
|
|
114692
114686
|
init_annotations2();
|
|
114693
114687
|
|
|
114688
|
+
// src/shared/api-paths.ts
|
|
114689
|
+
var API_EVENTS = "/api/events";
|
|
114690
|
+
var API_NOTIFY_STREAM = "/api/notify-stream";
|
|
114691
|
+
var API_CHANNEL_AWARENESS = "/api/channel-awareness";
|
|
114692
|
+
var API_CHANNEL_ERROR = "/api/channel-error";
|
|
114693
|
+
var API_CHANNEL_REPLY = "/api/channel-reply";
|
|
114694
|
+
var API_CHANNEL_PERMISSION = "/api/channel-permission";
|
|
114695
|
+
var API_CHANNEL_PERMISSION_VERDICT = "/api/channel-permission-verdict";
|
|
114696
|
+
var API_LAUNCH_CLAUDE = "/api/launch-claude";
|
|
114697
|
+
var API_MODE = "/api/mode";
|
|
114698
|
+
var API_INFO = "/api/info";
|
|
114699
|
+
var API_OPEN = "/api/open";
|
|
114700
|
+
var API_CLOSE = "/api/close";
|
|
114701
|
+
var API_SAVE = "/api/save";
|
|
114702
|
+
var API_UPLOAD = "/api/upload";
|
|
114703
|
+
var API_SCRATCHPAD = "/api/scratchpad";
|
|
114704
|
+
var API_CONVERT = "/api/convert";
|
|
114705
|
+
var API_APPLY_CHANGES = "/api/apply-changes";
|
|
114706
|
+
var API_ANNOTATION_REPLY = "/api/annotation-reply";
|
|
114707
|
+
var API_REMOVE_ANNOTATION = "/api/remove-annotation";
|
|
114708
|
+
var API_CHAT = "/api/chat";
|
|
114709
|
+
var API_SETUP = "/api/setup";
|
|
114710
|
+
var API_ROTATE_TOKEN = "/api/rotate-token";
|
|
114711
|
+
|
|
114694
114712
|
// src/server/mcp/api-routes.ts
|
|
114695
114713
|
init_constants();
|
|
114696
114714
|
|
|
@@ -114720,7 +114738,7 @@ function handleAnnotationReply(req, res) {
|
|
|
114720
114738
|
const annotationsMap = ydoc.getMap(Y_MAP_ANNOTATIONS);
|
|
114721
114739
|
const result2 = addReplyToAnnotation(ydoc, annotationsMap, annotationId, text5, "user");
|
|
114722
114740
|
if (!result2.ok) {
|
|
114723
|
-
const status = result2.code === "ANNOTATION_RESOLVED" ? 409 : 404;
|
|
114741
|
+
const status = result2.code === "ANNOTATION_RESOLVED" ? 409 : result2.code === "INVALID_ARGUMENT" ? 400 : 404;
|
|
114724
114742
|
console.warn(`[Tandem] API error (${status}): annotation reply failed: ${result2.error}`);
|
|
114725
114743
|
pushNotification({
|
|
114726
114744
|
id: generateNotificationId(),
|
|
@@ -114945,11 +114963,15 @@ function errorCodeToHttpStatus(code3) {
|
|
|
114945
114963
|
case "ENOENT":
|
|
114946
114964
|
case "FILE_NOT_FOUND":
|
|
114947
114965
|
case "NO_DOCUMENT":
|
|
114966
|
+
case "NOT_FOUND":
|
|
114948
114967
|
return 404;
|
|
114949
114968
|
case "INVALID_PATH":
|
|
114950
114969
|
case "UNSUPPORTED_FORMAT":
|
|
114951
114970
|
case "NO_SUGGESTIONS":
|
|
114971
|
+
case "INVALID_ARGUMENT":
|
|
114952
114972
|
return 400;
|
|
114973
|
+
case "ANNOTATION_RESOLVED":
|
|
114974
|
+
return 409;
|
|
114953
114975
|
case "FILE_TOO_LARGE":
|
|
114954
114976
|
return 413;
|
|
114955
114977
|
case "EBUSY":
|
|
@@ -114968,12 +114990,16 @@ function errorCodeToLabel(code3) {
|
|
|
114968
114990
|
case "ENOENT":
|
|
114969
114991
|
case "FILE_NOT_FOUND":
|
|
114970
114992
|
case "NO_DOCUMENT":
|
|
114993
|
+
case "NOT_FOUND":
|
|
114971
114994
|
return "NOT_FOUND";
|
|
114972
114995
|
case "INVALID_PATH":
|
|
114973
114996
|
return "INVALID_PATH";
|
|
114974
114997
|
case "UNSUPPORTED_FORMAT":
|
|
114975
114998
|
case "NO_SUGGESTIONS":
|
|
114999
|
+
case "INVALID_ARGUMENT":
|
|
114976
115000
|
return "BAD_REQUEST";
|
|
115001
|
+
case "ANNOTATION_RESOLVED":
|
|
115002
|
+
return "ANNOTATION_RESOLVED";
|
|
114977
115003
|
case "FILE_TOO_LARGE":
|
|
114978
115004
|
return "FILE_TOO_LARGE";
|
|
114979
115005
|
case "EBUSY":
|
|
@@ -115320,7 +115346,7 @@ var SKILL_CONTENT = readFileSync(SKILL_PATH, "utf-8");
|
|
|
115320
115346
|
var __dirname2 = dirname2(fileURLToPath3(import.meta.url));
|
|
115321
115347
|
var PACKAGE_ROOT = resolve3(__dirname2, "../..");
|
|
115322
115348
|
var CHANNEL_DIST = resolve3(PACKAGE_ROOT, "dist/channel/index.js");
|
|
115323
|
-
var MCP_URL = `http://
|
|
115349
|
+
var MCP_URL = `http://127.0.0.1:${DEFAULT_MCP_PORT}`;
|
|
115324
115350
|
function buildMcpEntries(channelPath, opts = {}) {
|
|
115325
115351
|
const isDesktop = opts.targetKind === "claude-desktop";
|
|
115326
115352
|
let tandemEntry;
|
|
@@ -115584,7 +115610,7 @@ async function handleUpload(req, res) {
|
|
|
115584
115610
|
// src/server/mcp/api-routes.ts
|
|
115585
115611
|
function isHostAllowed(host, extraHosts = []) {
|
|
115586
115612
|
const reqHost = (host ?? "").split(":")[0];
|
|
115587
|
-
if (reqHost === "
|
|
115613
|
+
if (reqHost === "127.0.0.1" || reqHost === TAURI_HOSTNAME) {
|
|
115588
115614
|
return true;
|
|
115589
115615
|
}
|
|
115590
115616
|
return extraHosts.includes(reqHost);
|
|
@@ -115593,7 +115619,7 @@ function escapeRegExp2(s) {
|
|
|
115593
115619
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
115594
115620
|
}
|
|
115595
115621
|
var LOCALHOST_ORIGIN_RE = new RegExp(
|
|
115596
|
-
`^https?://(
|
|
115622
|
+
`^https?://(127\\.0\\.0\\.1|${escapeRegExp2(TAURI_HOSTNAME)})(:\\d+)?$`
|
|
115597
115623
|
);
|
|
115598
115624
|
function isLocalhostOrigin(origin) {
|
|
115599
115625
|
return LOCALHOST_ORIGIN_RE.test(origin ?? "");
|
|
@@ -115617,32 +115643,32 @@ function createApiMiddleware(extraHosts = []) {
|
|
|
115617
115643
|
}
|
|
115618
115644
|
var apiMiddleware = createApiMiddleware();
|
|
115619
115645
|
function registerApiRoutes(app, largeBody, token, mw, setCurrentToken, getCurrentToken, infoHandlerDeps) {
|
|
115620
|
-
app.get(
|
|
115621
|
-
app.get(
|
|
115622
|
-
app.get(
|
|
115623
|
-
app.options(
|
|
115624
|
-
app.post(
|
|
115625
|
-
app.options(
|
|
115626
|
-
app.post(
|
|
115627
|
-
app.options(
|
|
115628
|
-
app.post(
|
|
115629
|
-
app.options(
|
|
115630
|
-
app.post(
|
|
115631
|
-
app.options(
|
|
115632
|
-
app.post(
|
|
115633
|
-
app.options(
|
|
115634
|
-
app.post(
|
|
115635
|
-
app.options(
|
|
115636
|
-
app.post(
|
|
115637
|
-
app.options(
|
|
115638
|
-
app.post(
|
|
115639
|
-
app.options(
|
|
115640
|
-
app.post(
|
|
115641
|
-
app.options(
|
|
115642
|
-
app.post(
|
|
115643
|
-
app.options(
|
|
115646
|
+
app.get(API_INFO, mw, makeInfoHandler(infoHandlerDeps));
|
|
115647
|
+
app.get(API_NOTIFY_STREAM, mw, handleNotifyStream);
|
|
115648
|
+
app.get(API_MODE, mw, handleMode);
|
|
115649
|
+
app.options(API_OPEN, mw);
|
|
115650
|
+
app.post(API_OPEN, mw, largeBody, handleOpen);
|
|
115651
|
+
app.options(API_CLOSE, mw);
|
|
115652
|
+
app.post(API_CLOSE, mw, largeBody, handleClose);
|
|
115653
|
+
app.options(API_SAVE, mw);
|
|
115654
|
+
app.post(API_SAVE, mw, largeBody, handleSave);
|
|
115655
|
+
app.options(API_UPLOAD, mw);
|
|
115656
|
+
app.post(API_UPLOAD, mw, largeBody, handleUpload);
|
|
115657
|
+
app.options(API_SCRATCHPAD, mw);
|
|
115658
|
+
app.post(API_SCRATCHPAD, mw, handleScratchpad);
|
|
115659
|
+
app.options(API_CONVERT, mw);
|
|
115660
|
+
app.post(API_CONVERT, mw, largeBody, handleConvert);
|
|
115661
|
+
app.options(API_APPLY_CHANGES, mw);
|
|
115662
|
+
app.post(API_APPLY_CHANGES, mw, largeBody, handleApplyChanges);
|
|
115663
|
+
app.options(API_SETUP, mw);
|
|
115664
|
+
app.post(API_SETUP, mw, largeBody, makeSetupHandler({ token }));
|
|
115665
|
+
app.options(API_ANNOTATION_REPLY, mw);
|
|
115666
|
+
app.post(API_ANNOTATION_REPLY, mw, largeBody, handleAnnotationReply);
|
|
115667
|
+
app.options(API_REMOVE_ANNOTATION, mw);
|
|
115668
|
+
app.post(API_REMOVE_ANNOTATION, mw, largeBody, handleRemoveAnnotation);
|
|
115669
|
+
app.options(API_ROTATE_TOKEN, mw);
|
|
115644
115670
|
app.post(
|
|
115645
|
-
|
|
115671
|
+
API_ROTATE_TOKEN,
|
|
115646
115672
|
mw,
|
|
115647
115673
|
largeBody,
|
|
115648
115674
|
makeRotateTokenHandler({ setCurrentToken, getCurrentToken })
|
|
@@ -115847,6 +115873,7 @@ function processUnsurfacedInboxAnnotations(unsurfaced, fullText, surfaced, wasCh
|
|
|
115847
115873
|
|
|
115848
115874
|
// src/server/mcp/channel-routes.ts
|
|
115849
115875
|
init_constants();
|
|
115876
|
+
init_types3();
|
|
115850
115877
|
init_utils();
|
|
115851
115878
|
init_queue();
|
|
115852
115879
|
|
|
@@ -115904,9 +115931,9 @@ init_provider();
|
|
|
115904
115931
|
var pendingPermissions = /* @__PURE__ */ new Map();
|
|
115905
115932
|
var PERMISSION_TTL_MS = 3e4;
|
|
115906
115933
|
function registerChannelRoutes(app, apiMiddleware2) {
|
|
115907
|
-
app.get(
|
|
115908
|
-
app.options(
|
|
115909
|
-
app.post(
|
|
115934
|
+
app.get(API_EVENTS, apiMiddleware2, sseHandler);
|
|
115935
|
+
app.options(API_CHANNEL_AWARENESS, apiMiddleware2);
|
|
115936
|
+
app.post(API_CHANNEL_AWARENESS, apiMiddleware2, (req, res) => {
|
|
115910
115937
|
const { documentId, status, active, focusParagraph, focusOffset } = req.body ?? {};
|
|
115911
115938
|
if (typeof status !== "string") {
|
|
115912
115939
|
res.status(400).json({ error: "BAD_REQUEST", message: "status is required" });
|
|
@@ -115923,18 +115950,24 @@ function registerChannelRoutes(app, apiMiddleware2) {
|
|
|
115923
115950
|
focusParagraph: typeof focusParagraph === "number" ? focusParagraph : null,
|
|
115924
115951
|
focusOffset: typeof focusOffset === "number" ? focusOffset : null
|
|
115925
115952
|
};
|
|
115926
|
-
doc.transact(() => awarenessMap.set(
|
|
115953
|
+
doc.transact(() => awarenessMap.set(Y_MAP_CLAUDE, state), MCP_ORIGIN);
|
|
115927
115954
|
}
|
|
115928
115955
|
res.json({ ok: true, written: !!docId });
|
|
115929
115956
|
});
|
|
115930
|
-
app.options(
|
|
115931
|
-
app.post(
|
|
115957
|
+
app.options(API_CHANNEL_ERROR, apiMiddleware2);
|
|
115958
|
+
app.post(API_CHANNEL_ERROR, apiMiddleware2, (req, res) => {
|
|
115932
115959
|
const { error: error2, message } = req.body ?? {};
|
|
115933
|
-
|
|
115960
|
+
const parsed = ChannelErrorCodeSchema.safeParse(error2);
|
|
115961
|
+
if (!parsed.success) {
|
|
115962
|
+
console.error(`[Channel] Error: UNKNOWN_CODE (${String(error2)}) \u2014 ${message}`);
|
|
115963
|
+
res.status(400).json({ error: "BAD_REQUEST", message: "error must be a known ChannelErrorCode" });
|
|
115964
|
+
return;
|
|
115965
|
+
}
|
|
115966
|
+
console.error(`[Channel] Error: ${parsed.data} \u2014 ${message}`);
|
|
115934
115967
|
res.json({ ok: true });
|
|
115935
115968
|
});
|
|
115936
|
-
app.options(
|
|
115937
|
-
app.post(
|
|
115969
|
+
app.options(API_CHANNEL_REPLY, apiMiddleware2);
|
|
115970
|
+
app.post(API_CHANNEL_REPLY, apiMiddleware2, (req, res) => {
|
|
115938
115971
|
const { text: text5, documentId, replyTo } = req.body ?? {};
|
|
115939
115972
|
if (typeof text5 !== "string") {
|
|
115940
115973
|
res.status(400).json({ error: "BAD_REQUEST", message: "text is required" });
|
|
@@ -115955,8 +115988,8 @@ function registerChannelRoutes(app, apiMiddleware2) {
|
|
|
115955
115988
|
ctrlDoc.transact(() => chatMap.set(id2, msg), MCP_ORIGIN);
|
|
115956
115989
|
res.json({ sent: true, messageId: id2 });
|
|
115957
115990
|
});
|
|
115958
|
-
app.options(
|
|
115959
|
-
app.post(
|
|
115991
|
+
app.options(API_CHANNEL_PERMISSION, apiMiddleware2);
|
|
115992
|
+
app.post(API_CHANNEL_PERMISSION, apiMiddleware2, (req, res) => {
|
|
115960
115993
|
const { requestId, toolName, description: description2, inputPreview } = req.body ?? {};
|
|
115961
115994
|
if (typeof requestId !== "string" || typeof toolName !== "string") {
|
|
115962
115995
|
res.status(400).json({ error: "BAD_REQUEST", message: "requestId and toolName required" });
|
|
@@ -115972,15 +116005,15 @@ function registerChannelRoutes(app, apiMiddleware2) {
|
|
|
115972
116005
|
console.error(`[Channel] Permission request: ${toolName} \u2014 ${description2} (id: ${requestId})`);
|
|
115973
116006
|
res.json({ ok: true });
|
|
115974
116007
|
});
|
|
115975
|
-
app.get(
|
|
116008
|
+
app.get(API_CHANNEL_PERMISSION, apiMiddleware2, (_req, res) => {
|
|
115976
116009
|
const now = Date.now();
|
|
115977
116010
|
for (const [id2, perm] of pendingPermissions) {
|
|
115978
116011
|
if (now - perm.createdAt > PERMISSION_TTL_MS) pendingPermissions.delete(id2);
|
|
115979
116012
|
}
|
|
115980
116013
|
res.json({ pending: Array.from(pendingPermissions.values()) });
|
|
115981
116014
|
});
|
|
115982
|
-
app.options(
|
|
115983
|
-
app.post(
|
|
116015
|
+
app.options(API_CHANNEL_PERMISSION_VERDICT, apiMiddleware2);
|
|
116016
|
+
app.post(API_CHANNEL_PERMISSION_VERDICT, apiMiddleware2, (req, res) => {
|
|
115984
116017
|
const { requestId, approved } = req.body ?? {};
|
|
115985
116018
|
if (typeof requestId !== "string") {
|
|
115986
116019
|
res.status(400).json({ error: "BAD_REQUEST", message: "requestId is required" });
|
|
@@ -115990,8 +116023,8 @@ function registerChannelRoutes(app, apiMiddleware2) {
|
|
|
115990
116023
|
console.error(`[Channel] Permission verdict: ${requestId} \u2192 ${approved ? "allow" : "deny"}`);
|
|
115991
116024
|
res.json({ ok: true, requestId, behavior: approved ? "allow" : "deny" });
|
|
115992
116025
|
});
|
|
115993
|
-
app.options(
|
|
115994
|
-
app.delete(
|
|
116026
|
+
app.options(API_CHAT, apiMiddleware2);
|
|
116027
|
+
app.delete(API_CHAT, apiMiddleware2, (_req, res) => {
|
|
115995
116028
|
const ctrlDoc = getOrCreateDocument(CTRL_ROOM);
|
|
115996
116029
|
const chatMap = ctrlDoc.getMap(Y_MAP_CHAT);
|
|
115997
116030
|
const count = chatMap.size;
|
|
@@ -116002,8 +116035,8 @@ function registerChannelRoutes(app, apiMiddleware2) {
|
|
|
116002
116035
|
}, MCP_ORIGIN);
|
|
116003
116036
|
res.json({ ok: true, cleared: count });
|
|
116004
116037
|
});
|
|
116005
|
-
app.options(
|
|
116006
|
-
app.post(
|
|
116038
|
+
app.options(API_LAUNCH_CLAUDE, apiMiddleware2);
|
|
116039
|
+
app.post(API_LAUNCH_CLAUDE, apiMiddleware2, async (_req, res) => {
|
|
116007
116040
|
try {
|
|
116008
116041
|
const { launchClaude: launchClaude2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
|
|
116009
116042
|
const result2 = launchClaude2();
|
|
@@ -116146,7 +116179,7 @@ function registerNavigationTools(server) {
|
|
|
116146
116179
|
|
|
116147
116180
|
// src/server/mcp/server.ts
|
|
116148
116181
|
var esmRequire = createRequire(import.meta.url);
|
|
116149
|
-
var APP_VERSION = true ? "0.
|
|
116182
|
+
var APP_VERSION = true ? "0.12.0" : _readVersionFromDisk();
|
|
116150
116183
|
var MCP_SDK_VERSION = true ? "1.27.1" : "0.0.0-unknown";
|
|
116151
116184
|
var __dirname3 = dirname3(fileURLToPath4(import.meta.url));
|
|
116152
116185
|
var CLIENT_DIST = join3(__dirname3, "../client");
|
|
@@ -116296,9 +116329,9 @@ async function startMcpServerHttp(port, host = DEFAULT_BIND_HOST, token, resolve
|
|
|
116296
116329
|
(_req, res) => {
|
|
116297
116330
|
res.header("Access-Control-Allow-Origin", "*");
|
|
116298
116331
|
res.json({
|
|
116299
|
-
resource: `http://
|
|
116332
|
+
resource: `http://127.0.0.1:${port}/mcp`,
|
|
116300
116333
|
bearer_methods_supported: ["header"],
|
|
116301
|
-
authorization_servers: [`http://
|
|
116334
|
+
authorization_servers: [`http://127.0.0.1:${port}`]
|
|
116302
116335
|
});
|
|
116303
116336
|
}
|
|
116304
116337
|
);
|
|
@@ -116307,9 +116340,9 @@ async function startMcpServerHttp(port, host = DEFAULT_BIND_HOST, token, resolve
|
|
|
116307
116340
|
(_req, res) => {
|
|
116308
116341
|
res.header("Access-Control-Allow-Origin", "*");
|
|
116309
116342
|
res.json({
|
|
116310
|
-
resource: `http://
|
|
116343
|
+
resource: `http://127.0.0.1:${port}/mcp`,
|
|
116311
116344
|
bearer_methods_supported: ["header"],
|
|
116312
|
-
authorization_servers: [`http://
|
|
116345
|
+
authorization_servers: [`http://127.0.0.1:${port}`]
|
|
116313
116346
|
});
|
|
116314
116347
|
}
|
|
116315
116348
|
);
|
|
@@ -116352,13 +116385,6 @@ async function startMcpServerHttp(port, host = DEFAULT_BIND_HOST, token, resolve
|
|
|
116352
116385
|
httpServer2.removeListener("error", reject2);
|
|
116353
116386
|
httpServer2.on("error", (err) => console.error("[Tandem] HTTP server error:", err));
|
|
116354
116387
|
console.error(`[Tandem] MCP HTTP server on http://${host}:${port}/mcp`);
|
|
116355
|
-
if (process.env.TANDEM_OPEN_BROWSER === "1") {
|
|
116356
|
-
if (existsSync2(CLIENT_DIST)) {
|
|
116357
|
-
openBrowser(`http://localhost:${port}`);
|
|
116358
|
-
} else {
|
|
116359
|
-
console.error("[Tandem] Skipping browser open \u2014 no client assets found");
|
|
116360
|
-
}
|
|
116361
|
-
}
|
|
116362
116388
|
resolve4(httpServer2);
|
|
116363
116389
|
});
|
|
116364
116390
|
httpServer2.on("error", reject2);
|
|
@@ -116430,10 +116456,17 @@ function injectTutorialAnnotations(doc) {
|
|
|
116430
116456
|
}
|
|
116431
116457
|
const annotation = {
|
|
116432
116458
|
id: def.id,
|
|
116433
|
-
author
|
|
116459
|
+
// Notes are user-private (ADR-027); Claude can't author user-private content.
|
|
116460
|
+
// Comments and highlights are seeded as if Claude wrote them so the user
|
|
116461
|
+
// sees the cross-author authorship indicator.
|
|
116462
|
+
author: def.type === "note" ? "user" : "claude",
|
|
116434
116463
|
type: def.type,
|
|
116435
116464
|
range: result2.range,
|
|
116436
|
-
relRange
|
|
116465
|
+
// Only attach a CRDT-anchored relRange when fully resolved. Matches
|
|
116466
|
+
// the reloadFromDisk pattern (file-opener.ts) — a partial anchor
|
|
116467
|
+
// leaks a half-resolved RelativePosition that downstream code would
|
|
116468
|
+
// re-anchor anyway via the lazy-attach path in refreshRange.
|
|
116469
|
+
...result2.fullyAnchored ? { relRange: result2.relRange } : {},
|
|
116437
116470
|
content: def.content,
|
|
116438
116471
|
status: "pending",
|
|
116439
116472
|
timestamp: Date.now(),
|
|
@@ -116456,6 +116489,25 @@ init_notifications();
|
|
|
116456
116489
|
init_platform();
|
|
116457
116490
|
init_manager();
|
|
116458
116491
|
|
|
116492
|
+
// src/server/startup-file.ts
|
|
116493
|
+
init_document_service();
|
|
116494
|
+
init_file_opener();
|
|
116495
|
+
async function maybeOpenStartupFile(envPath) {
|
|
116496
|
+
if (!envPath || envPath.trim() === "") return false;
|
|
116497
|
+
let result2;
|
|
116498
|
+
try {
|
|
116499
|
+
result2 = await openFileByPath(envPath);
|
|
116500
|
+
} catch (err) {
|
|
116501
|
+
console.error(
|
|
116502
|
+
`[Tandem] TANDEM_OPEN_FILE failed (${envPath}): ${err instanceof Error ? err.message : String(err)}`
|
|
116503
|
+
);
|
|
116504
|
+
return false;
|
|
116505
|
+
}
|
|
116506
|
+
setActiveDocId(result2.documentId);
|
|
116507
|
+
console.error(`[Tandem] Opened TANDEM_OPEN_FILE on startup: ${envPath}`);
|
|
116508
|
+
return true;
|
|
116509
|
+
}
|
|
116510
|
+
|
|
116459
116511
|
// src/server/version-check.ts
|
|
116460
116512
|
import fs11 from "fs/promises";
|
|
116461
116513
|
import path15 from "path";
|
|
@@ -116478,7 +116530,7 @@ async function checkVersionChange(currentVersion, versionFilePath) {
|
|
|
116478
116530
|
|
|
116479
116531
|
// src/server/index.ts
|
|
116480
116532
|
init_provider();
|
|
116481
|
-
var isProduction = process.env.
|
|
116533
|
+
var isProduction = process.env.TANDEM_TAURI_SIDECAR === "1";
|
|
116482
116534
|
var SUPPRESSED_PATTERNS = [/^\[mammoth\]/, /Invalid access/i, /^\s*add yjs type/i];
|
|
116483
116535
|
var originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
116484
116536
|
if (isProduction) {
|
|
@@ -116709,7 +116761,12 @@ async function main2() {
|
|
|
116709
116761
|
} catch (err) {
|
|
116710
116762
|
console.error("[Tandem] Version check / changelog open failed (non-fatal):", err);
|
|
116711
116763
|
}
|
|
116764
|
+
const startupFileRequested = !!process.env.TANDEM_OPEN_FILE?.trim();
|
|
116765
|
+
const startupFileOpened = await maybeOpenStartupFile(process.env.TANDEM_OPEN_FILE);
|
|
116712
116766
|
if (docCount() === 0 && !process.env.TANDEM_NO_SAMPLE) {
|
|
116767
|
+
if (startupFileRequested && !startupFileOpened) {
|
|
116768
|
+
console.error("[Tandem] Falling back to welcome.md after TANDEM_OPEN_FILE failure");
|
|
116769
|
+
}
|
|
116713
116770
|
const sampleBase = process.env.TANDEM_DATA_DIR || projectRoot;
|
|
116714
116771
|
const samplePath = path16.join(sampleBase, "sample/welcome.md");
|
|
116715
116772
|
try {
|
|
@@ -116731,16 +116788,16 @@ async function main2() {
|
|
|
116731
116788
|
const [srv] = await Promise.all([
|
|
116732
116789
|
startMcpServerHttp(mcpPort, bindHost, authToken, resolvedLanIP),
|
|
116733
116790
|
startHocuspocus(wsPort).then(() => {
|
|
116734
|
-
console.error(`[Tandem] Hocuspocus WebSocket server running on ws://
|
|
116791
|
+
console.error(`[Tandem] Hocuspocus WebSocket server running on ws://127.0.0.1:${wsPort}`);
|
|
116735
116792
|
})
|
|
116736
116793
|
]);
|
|
116737
116794
|
httpServer = srv;
|
|
116738
116795
|
console.error("");
|
|
116739
116796
|
console.error(` Tandem v${APP_VERSION}`);
|
|
116740
116797
|
console.error("");
|
|
116741
|
-
console.error(` MCP HTTP: http://
|
|
116742
|
-
console.error(` WebSocket: ws://
|
|
116743
|
-
console.error(` Health: http://
|
|
116798
|
+
console.error(` MCP HTTP: http://127.0.0.1:${mcpPort}/mcp`);
|
|
116799
|
+
console.error(` WebSocket: ws://127.0.0.1:${wsPort}`);
|
|
116800
|
+
console.error(` Health: http://127.0.0.1:${mcpPort}/health`);
|
|
116744
116801
|
console.error("");
|
|
116745
116802
|
console.error(" Open Claude Code and ask Claude to review a document.");
|
|
116746
116803
|
console.error("");
|
|
@@ -116753,7 +116810,7 @@ async function main2() {
|
|
|
116753
116810
|
console.error(`[Tandem] ${err instanceof Error ? err.message : err} \u2014 proceeding anyway`);
|
|
116754
116811
|
}
|
|
116755
116812
|
await startHocuspocus(wsPort);
|
|
116756
|
-
console.error(`[Tandem] Hocuspocus WebSocket server running on ws://
|
|
116813
|
+
console.error(`[Tandem] Hocuspocus WebSocket server running on ws://127.0.0.1:${wsPort}`);
|
|
116757
116814
|
})().catch((err) => {
|
|
116758
116815
|
console.error("[Tandem] Hocuspocus startup error:", err);
|
|
116759
116816
|
});
|