tandem-editor 0.14.3 → 0.16.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 +4 -4
- package/CHANGELOG.md +47 -0
- package/README.md +21 -1
- package/dist/cli/index.js +82 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/client/assets/CoworkSettings-B4LapjhG.js +11 -0
- package/dist/client/assets/CoworkSettings-C0m1IhNy.css +1 -0
- package/dist/client/assets/{dist-dYgyssYq.js → dist-CJtsfiU2.js} +1 -1
- package/dist/client/assets/index-BIEOrpU_.js +270 -0
- package/dist/client/assets/index-DdjGUhNd.css +1 -0
- package/dist/client/assets/{prod-DeXsEx17.js → prod-p35ShryZ.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/index.js +277 -64
- package/dist/server/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/client/assets/CoworkSettings-BevsTL0m.css +0 -1
- package/dist/client/assets/CoworkSettings-CAr7DoyA.js +0 -5
- package/dist/client/assets/index-BbxWZ4FI.css +0 -1
- package/dist/client/assets/index-wtM-9NgF.js +0 -262
package/dist/server/index.js
CHANGED
|
@@ -61735,6 +61735,16 @@ var init_positions2 = __esm({
|
|
|
61735
61735
|
}
|
|
61736
61736
|
});
|
|
61737
61737
|
|
|
61738
|
+
// src/server/file-io/docx-comment-id.ts
|
|
61739
|
+
function isCanonicalWordId(raw) {
|
|
61740
|
+
return !!raw && /^\d{1,9}$/.test(raw) && String(Number(raw)) === raw;
|
|
61741
|
+
}
|
|
61742
|
+
var init_docx_comment_id = __esm({
|
|
61743
|
+
"src/server/file-io/docx-comment-id.ts"() {
|
|
61744
|
+
"use strict";
|
|
61745
|
+
}
|
|
61746
|
+
});
|
|
61747
|
+
|
|
61738
61748
|
// src/server/file-io/docx-walker.ts
|
|
61739
61749
|
function isElement3(node3) {
|
|
61740
61750
|
return node3.type === "tag";
|
|
@@ -61871,7 +61881,7 @@ function importAnnotationId(commentId, from3, to, bodyText) {
|
|
|
61871
61881
|
return `import-${hash2}`;
|
|
61872
61882
|
}
|
|
61873
61883
|
function importReplyId(rootCommentId, replyCommentId, bodyText) {
|
|
61874
|
-
const hash2 = crypto3.createHash("sha256").update(`${rootCommentId}
|
|
61884
|
+
const hash2 = crypto3.createHash("sha256").update(`${rootCommentId} ${replyCommentId} ${bodyText}`).digest("hex").slice(0, 12);
|
|
61875
61885
|
return `import-reply-${hash2}`;
|
|
61876
61886
|
}
|
|
61877
61887
|
async function extractDocxComments(buffer4) {
|
|
@@ -62018,7 +62028,28 @@ function injectCommentsAsAnnotations(doc, comments, fileName) {
|
|
|
62018
62028
|
const sourceFile = fileName ?? "unknown";
|
|
62019
62029
|
let injected = 0;
|
|
62020
62030
|
let migrated = 0;
|
|
62031
|
+
let reanchored = 0;
|
|
62021
62032
|
let injectedReplies = 0;
|
|
62033
|
+
const byCommentId = /* @__PURE__ */ new Map();
|
|
62034
|
+
for (const [key, val] of map5) {
|
|
62035
|
+
const cid = val?.importSource?.commentId;
|
|
62036
|
+
if (!isCanonicalWordId(cid)) continue;
|
|
62037
|
+
const isPromoted = val.promotedFrom === "note";
|
|
62038
|
+
if (val.author !== "import" && !isPromoted) continue;
|
|
62039
|
+
const existing = byCommentId.get(cid);
|
|
62040
|
+
if (!existing) {
|
|
62041
|
+
byCommentId.set(cid, { key, ann: val });
|
|
62042
|
+
} else if (isPromoted && existing.ann.promotedFrom !== "note") {
|
|
62043
|
+
byCommentId.set(cid, { key, ann: val });
|
|
62044
|
+
console.error(
|
|
62045
|
+
`[docx-comments] Duplicate imported commentId ${cid}: preferred promoted record ${key} over import note ${existing.key}.`
|
|
62046
|
+
);
|
|
62047
|
+
} else {
|
|
62048
|
+
console.error(
|
|
62049
|
+
`[docx-comments] Duplicate imported commentId ${cid}: kept ${existing.key}, ignored ${key}.`
|
|
62050
|
+
);
|
|
62051
|
+
}
|
|
62052
|
+
}
|
|
62022
62053
|
withInternal(doc, () => {
|
|
62023
62054
|
for (const comment of comments) {
|
|
62024
62055
|
const result2 = anchoredRange(doc, toFlatOffset(comment.from), toFlatOffset(comment.to));
|
|
@@ -62028,60 +62059,79 @@ function injectCommentsAsAnnotations(doc, comments, fileName) {
|
|
|
62028
62059
|
);
|
|
62029
62060
|
continue;
|
|
62030
62061
|
}
|
|
62031
|
-
const
|
|
62032
|
-
|
|
62033
|
-
|
|
62062
|
+
const offsetId = importAnnotationId(
|
|
62063
|
+
comment.commentId,
|
|
62064
|
+
comment.from,
|
|
62065
|
+
comment.to,
|
|
62066
|
+
comment.bodyText
|
|
62067
|
+
);
|
|
62068
|
+
let effectiveKey = offsetId;
|
|
62069
|
+
const importSource = {
|
|
62070
|
+
author: comment.authorName,
|
|
62071
|
+
file: sourceFile,
|
|
62072
|
+
commentId: comment.commentId.slice(0, IMPORT_COMMENT_ID_MAX)
|
|
62073
|
+
};
|
|
62074
|
+
if (map5.has(offsetId)) {
|
|
62075
|
+
const existing = map5.get(offsetId);
|
|
62034
62076
|
if (existing && existing.author === "import" && (existing.type === "comment" || existing.audience !== "private")) {
|
|
62035
|
-
map5.set(
|
|
62077
|
+
map5.set(offsetId, {
|
|
62036
62078
|
...existing,
|
|
62037
62079
|
type: "note",
|
|
62038
62080
|
audience: "private",
|
|
62039
62081
|
content: comment.bodyText,
|
|
62040
|
-
importSource
|
|
62041
|
-
author: comment.authorName,
|
|
62042
|
-
file: sourceFile,
|
|
62043
|
-
commentId: comment.commentId.slice(0, IMPORT_COMMENT_ID_MAX)
|
|
62044
|
-
},
|
|
62082
|
+
importSource,
|
|
62045
62083
|
rev: nextRev(existing)
|
|
62046
62084
|
});
|
|
62047
62085
|
migrated++;
|
|
62048
62086
|
} else if (existing && existing.author === "import" && existing.importSource && existing.importSource.commentId === void 0) {
|
|
62049
|
-
map5.set(
|
|
62087
|
+
map5.set(offsetId, {
|
|
62050
62088
|
...existing,
|
|
62051
|
-
importSource: {
|
|
62052
|
-
...existing.importSource,
|
|
62053
|
-
commentId: comment.commentId.slice(0, IMPORT_COMMENT_ID_MAX)
|
|
62054
|
-
},
|
|
62089
|
+
importSource: { ...existing.importSource, commentId: importSource.commentId },
|
|
62055
62090
|
rev: nextRev(existing)
|
|
62056
62091
|
});
|
|
62057
62092
|
}
|
|
62058
62093
|
} else {
|
|
62059
|
-
const
|
|
62060
|
-
|
|
62061
|
-
|
|
62062
|
-
|
|
62063
|
-
|
|
62064
|
-
|
|
62065
|
-
|
|
62066
|
-
|
|
62067
|
-
|
|
62068
|
-
|
|
62069
|
-
|
|
62070
|
-
|
|
62071
|
-
|
|
62072
|
-
|
|
62073
|
-
|
|
62074
|
-
|
|
62075
|
-
|
|
62076
|
-
|
|
62077
|
-
|
|
62094
|
+
const drift = isCanonicalWordId(comment.commentId) ? byCommentId.get(comment.commentId) : void 0;
|
|
62095
|
+
if (drift && drift.ann.author === "import") {
|
|
62096
|
+
effectiveKey = drift.key;
|
|
62097
|
+
const { relRange: _staleRel, textSnapshot: _staleSnap, ...existingRest } = drift.ann;
|
|
62098
|
+
map5.set(drift.key, {
|
|
62099
|
+
...existingRest,
|
|
62100
|
+
type: "note",
|
|
62101
|
+
audience: "private",
|
|
62102
|
+
content: comment.bodyText,
|
|
62103
|
+
range: { from: result2.range.from, to: result2.range.to },
|
|
62104
|
+
importSource,
|
|
62105
|
+
rev: nextRev(drift.ann),
|
|
62106
|
+
...result2.fullyAnchored ? { relRange: result2.relRange } : {}
|
|
62107
|
+
});
|
|
62108
|
+
reanchored++;
|
|
62109
|
+
} else if (drift && drift.ann.promotedFrom === "note") {
|
|
62110
|
+
effectiveKey = drift.key;
|
|
62111
|
+
} else {
|
|
62112
|
+
const annotation = {
|
|
62113
|
+
id: offsetId,
|
|
62114
|
+
author: "import",
|
|
62115
|
+
type: "note",
|
|
62116
|
+
audience: "private",
|
|
62117
|
+
range: { from: result2.range.from, to: result2.range.to },
|
|
62118
|
+
content: comment.bodyText,
|
|
62119
|
+
status: "pending",
|
|
62120
|
+
timestamp: comment.date ? new Date(comment.date).getTime() : Date.now(),
|
|
62121
|
+
rev: nextRev(),
|
|
62122
|
+
importSource,
|
|
62123
|
+
...result2.fullyAnchored ? { relRange: result2.relRange } : {}
|
|
62124
|
+
};
|
|
62125
|
+
map5.set(offsetId, annotation);
|
|
62126
|
+
injected++;
|
|
62127
|
+
}
|
|
62078
62128
|
}
|
|
62079
62129
|
for (const reply of comment.replies ?? []) {
|
|
62080
62130
|
const replyId = importReplyId(comment.commentId, reply.commentId, reply.bodyText);
|
|
62081
62131
|
if (repliesMap.has(replyId)) continue;
|
|
62082
62132
|
const replyRecord = {
|
|
62083
62133
|
id: replyId,
|
|
62084
|
-
annotationId:
|
|
62134
|
+
annotationId: effectiveKey,
|
|
62085
62135
|
author: "import",
|
|
62086
62136
|
text: reply.bodyText.slice(0, IMPORT_REPLY_BODY_CAP),
|
|
62087
62137
|
timestamp: reply.date ? new Date(reply.date).getTime() : Date.now(),
|
|
@@ -62094,9 +62144,9 @@ function injectCommentsAsAnnotations(doc, comments, fileName) {
|
|
|
62094
62144
|
}
|
|
62095
62145
|
}
|
|
62096
62146
|
});
|
|
62097
|
-
if (injected > 0 || migrated > 0 ||
|
|
62147
|
+
if (injected > 0 || migrated > 0 || reanchored > 0 || injectedReplies > 0) {
|
|
62098
62148
|
console.error(
|
|
62099
|
-
`[docx-comments] Imported ${injected}/${comments.length} Word comments as private notes` + (injectedReplies > 0 ? ` + ${injectedReplies} threaded replies` : "") + (migrated > 0 ? ` (migrated ${migrated} legacy records to note shape)` : "")
|
|
62149
|
+
`[docx-comments] Imported ${injected}/${comments.length} Word comments as private notes` + (injectedReplies > 0 ? ` + ${injectedReplies} threaded replies` : "") + (migrated > 0 ? ` (migrated ${migrated} legacy records to note shape)` : "") + (reanchored > 0 ? ` (re-anchored ${reanchored} drifted notes)` : "")
|
|
62100
62150
|
);
|
|
62101
62151
|
}
|
|
62102
62152
|
return injected;
|
|
@@ -62112,6 +62162,7 @@ var init_docx_comments = __esm({
|
|
|
62112
62162
|
init_types3();
|
|
62113
62163
|
init_schema();
|
|
62114
62164
|
init_positions2();
|
|
62165
|
+
init_docx_comment_id();
|
|
62115
62166
|
init_docx_walker();
|
|
62116
62167
|
MAX_THREAD_DEPTH = 64;
|
|
62117
62168
|
IMPORT_COMMENT_ID_MAX = 32;
|
|
@@ -82618,10 +82669,7 @@ function isImportReply(reply) {
|
|
|
82618
82669
|
return reply.author === "import" && typeof reply.importAuthor === "string" && reply.importAuthor.length > 0;
|
|
82619
82670
|
}
|
|
82620
82671
|
function reusableCommentId(raw) {
|
|
82621
|
-
|
|
82622
|
-
const n = Number(raw);
|
|
82623
|
-
if (String(n) !== raw) return null;
|
|
82624
|
-
return n;
|
|
82672
|
+
return isCanonicalWordId(raw) ? Number(raw) : null;
|
|
82625
82673
|
}
|
|
82626
82674
|
function authorLabel(ann) {
|
|
82627
82675
|
const imported = ann.importSource?.author?.trim();
|
|
@@ -82753,6 +82801,7 @@ var init_docx_comment_export = __esm({
|
|
|
82753
82801
|
init_sanitize();
|
|
82754
82802
|
init_document_model();
|
|
82755
82803
|
init_positions2();
|
|
82804
|
+
init_docx_comment_id();
|
|
82756
82805
|
}
|
|
82757
82806
|
});
|
|
82758
82807
|
|
|
@@ -93454,6 +93503,7 @@ __export(apply_exports, {
|
|
|
93454
93503
|
refreshSkillIfStale: () => refreshSkillIfStale,
|
|
93455
93504
|
removeConfigEntries: () => removeConfigEntries,
|
|
93456
93505
|
resolveChannelDist: () => resolveChannelDist,
|
|
93506
|
+
resolveCliVersion: () => resolveCliVersion,
|
|
93457
93507
|
shouldRegisterChannelShim: () => shouldRegisterChannelShim,
|
|
93458
93508
|
validateChannelShimPrereq: () => validateChannelShimPrereq
|
|
93459
93509
|
});
|
|
@@ -93492,6 +93542,19 @@ function resolveChannelDist(env3 = process.env, exists = existsSync) {
|
|
|
93492
93542
|
}
|
|
93493
93543
|
return resolve3(PACKAGE_ROOT, "dist/channel/index.js");
|
|
93494
93544
|
}
|
|
93545
|
+
function resolveCliVersion() {
|
|
93546
|
+
if (true) return "0.16.0";
|
|
93547
|
+
if (true) return "0.16.0";
|
|
93548
|
+
for (const rel of ["../../package.json", "../../../package.json"]) {
|
|
93549
|
+
try {
|
|
93550
|
+
const pkg = JSON.parse(readFileSync2(resolve3(__dirname2, rel), "utf8"));
|
|
93551
|
+
if (pkg.version) return pkg.version;
|
|
93552
|
+
} catch {
|
|
93553
|
+
}
|
|
93554
|
+
}
|
|
93555
|
+
console.error("[Tandem] Could not resolve CLI version for npx pin; using unpinned fallback");
|
|
93556
|
+
return "0.0.0-unknown";
|
|
93557
|
+
}
|
|
93495
93558
|
function applyOpsForCli(create9, opts) {
|
|
93496
93559
|
return {
|
|
93497
93560
|
create: create9,
|
|
@@ -93508,7 +93571,7 @@ function buildMcpEntries(channelPath, opts = {}) {
|
|
|
93508
93571
|
}
|
|
93509
93572
|
tandemEntry = {
|
|
93510
93573
|
command: "npx",
|
|
93511
|
-
args: ["-y",
|
|
93574
|
+
args: ["-y", `tandem-editor@${CLI_VERSION}`, "mcp-stdio"],
|
|
93512
93575
|
env: env3
|
|
93513
93576
|
};
|
|
93514
93577
|
} else {
|
|
@@ -93928,7 +93991,7 @@ async function applyConfigWithToken(token, opts = {}) {
|
|
|
93928
93991
|
}
|
|
93929
93992
|
return { updated, errors };
|
|
93930
93993
|
}
|
|
93931
|
-
var __dirname2, PACKAGE_ROOT, CHANNEL_DIST, MCP_URL, MAX_CONFIG_BYTES, PathRejectedError, DEFAULT_ROOTS_CACHE, MSIX_PACKAGE_PATTERN, BUNDLED_SKILL_VERSION, lastSkillRefreshError;
|
|
93994
|
+
var __dirname2, PACKAGE_ROOT, CHANNEL_DIST, MCP_URL, CLI_VERSION, MAX_CONFIG_BYTES, PathRejectedError, DEFAULT_ROOTS_CACHE, MSIX_PACKAGE_PATTERN, BUNDLED_SKILL_VERSION, lastSkillRefreshError;
|
|
93932
93995
|
var init_apply = __esm({
|
|
93933
93996
|
"src/server/integrations/apply.ts"() {
|
|
93934
93997
|
"use strict";
|
|
@@ -93945,6 +94008,7 @@ var init_apply = __esm({
|
|
|
93945
94008
|
})();
|
|
93946
94009
|
CHANNEL_DIST = resolveChannelDist();
|
|
93947
94010
|
MCP_URL = `http://127.0.0.1:${DEFAULT_MCP_PORT}`;
|
|
94011
|
+
CLI_VERSION = resolveCliVersion();
|
|
93948
94012
|
MAX_CONFIG_BYTES = 5 * 1024 * 1024;
|
|
93949
94013
|
PathRejectedError = class extends Error {
|
|
93950
94014
|
constructor(path33, reason, message) {
|
|
@@ -94694,7 +94758,7 @@ var init_license_gate = __esm({
|
|
|
94694
94758
|
});
|
|
94695
94759
|
|
|
94696
94760
|
// src/server/mcp/output-schemas.ts
|
|
94697
|
-
var FlatRangeSchema, RelativeRangeSchema2, VisibleAnnotationTypeSchema, annotationBaseShape, VisibleReplySchema, openDocumentEntry, statusOutputShape, getTextContentOutputShape, annotationWithRepliesSchema, getAnnotationsOutputShape, userActionSchema, userResponseSchema, inboxChatMessageSchema, checkInboxOutputShape, listDocumentsOutputShape, searchOutputShape;
|
|
94761
|
+
var FlatRangeSchema, RelativeRangeSchema2, VisibleAnnotationTypeSchema, annotationBaseShape, VisibleReplySchema, openDocumentEntry, statusOutputShape, getTextContentOutputShape, annotationWithRepliesSchema, getAnnotationsOutputShape, userActionSchema, userResponseSchema, inboxChatMessageSchema, checkInboxOutputShape, listDocumentsOutputShape, searchOutputShape, doctorResultSchema, diagnosticsOutputShape;
|
|
94698
94762
|
var init_output_schemas = __esm({
|
|
94699
94763
|
"src/server/mcp/output-schemas.ts"() {
|
|
94700
94764
|
"use strict";
|
|
@@ -94828,6 +94892,28 @@ var init_output_schemas = __esm({
|
|
|
94828
94892
|
matches: external_exports.array(external_exports.object({ from: external_exports.number(), to: external_exports.number(), text: external_exports.string() })),
|
|
94829
94893
|
count: external_exports.number()
|
|
94830
94894
|
};
|
|
94895
|
+
doctorResultSchema = external_exports.object({
|
|
94896
|
+
check: external_exports.string(),
|
|
94897
|
+
status: external_exports.enum(["pass", "warn", "fail"]),
|
|
94898
|
+
message: external_exports.string(),
|
|
94899
|
+
fix: external_exports.string().optional().describe("Suggested remediation when the check did not pass"),
|
|
94900
|
+
data: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("Per-check detail bag")
|
|
94901
|
+
});
|
|
94902
|
+
diagnosticsOutputShape = {
|
|
94903
|
+
ok: external_exports.boolean().describe("True when no checks failed"),
|
|
94904
|
+
crashed: external_exports.boolean().describe("True if the diagnostic collector itself threw"),
|
|
94905
|
+
failures: external_exports.number(),
|
|
94906
|
+
warnings: external_exports.number(),
|
|
94907
|
+
summary: external_exports.string().describe("One-line human-readable summary"),
|
|
94908
|
+
error: external_exports.string().nullable().describe("Collector-level error message, or null"),
|
|
94909
|
+
results: external_exports.array(doctorResultSchema),
|
|
94910
|
+
version: external_exports.string().describe("Running Tandem version"),
|
|
94911
|
+
transport: external_exports.enum(["http", "stdio"]).describe("Transport this MCP server is serving"),
|
|
94912
|
+
platform: external_exports.string().describe("process.platform"),
|
|
94913
|
+
arch: external_exports.string().describe("process.arch"),
|
|
94914
|
+
nodeVersion: external_exports.string().describe("process.version"),
|
|
94915
|
+
tauriSidecar: external_exports.boolean().describe("True when running as the Tauri desktop sidecar")
|
|
94916
|
+
};
|
|
94831
94917
|
}
|
|
94832
94918
|
});
|
|
94833
94919
|
|
|
@@ -129341,6 +129427,7 @@ var init_convert2 = __esm({
|
|
|
129341
129427
|
});
|
|
129342
129428
|
|
|
129343
129429
|
// src/cli/doctor.ts
|
|
129430
|
+
import { execFile as execFile3 } from "child_process";
|
|
129344
129431
|
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
129345
129432
|
import { request } from "http";
|
|
129346
129433
|
import { createConnection } from "net";
|
|
@@ -129716,6 +129803,58 @@ function summarizeDoctorResults(failures, warnings) {
|
|
|
129716
129803
|
return `${warnings} warning(s) \u2014 Tandem should work, but check the items above.`;
|
|
129717
129804
|
return "All checks passed. Tandem is ready.";
|
|
129718
129805
|
}
|
|
129806
|
+
function globalTandemEditorVersion() {
|
|
129807
|
+
return new Promise((resolve5) => {
|
|
129808
|
+
execFile3(
|
|
129809
|
+
"npm",
|
|
129810
|
+
["ls", "-g", "--depth=0", "--json", "tandem-editor"],
|
|
129811
|
+
{ shell: true, windowsHide: true, timeout: 4e3, maxBuffer: 8 * 1024 * 1024 },
|
|
129812
|
+
(_err, stdout) => {
|
|
129813
|
+
if (!stdout || stdout.trim().length === 0) {
|
|
129814
|
+
resolve5(null);
|
|
129815
|
+
return;
|
|
129816
|
+
}
|
|
129817
|
+
try {
|
|
129818
|
+
const parsed = JSON.parse(stdout);
|
|
129819
|
+
resolve5(parsed.dependencies?.["tandem-editor"]?.version ?? null);
|
|
129820
|
+
} catch {
|
|
129821
|
+
resolve5(null);
|
|
129822
|
+
}
|
|
129823
|
+
}
|
|
129824
|
+
);
|
|
129825
|
+
});
|
|
129826
|
+
}
|
|
129827
|
+
function evaluateStaleGlobal(bundled, globalVersion) {
|
|
129828
|
+
if (globalVersion === null) {
|
|
129829
|
+
return null;
|
|
129830
|
+
}
|
|
129831
|
+
if (globalVersion === bundled) {
|
|
129832
|
+
return { status: "pass", message: `Global tandem-editor@${globalVersion} matches this build` };
|
|
129833
|
+
}
|
|
129834
|
+
return {
|
|
129835
|
+
status: "warn",
|
|
129836
|
+
message: `Global tandem-editor@${globalVersion} differs from this build (${bundled}) \u2014 a stale global can break \`npx tandem-editor\` (e.g. Claude Desktop's MCP bridge).`,
|
|
129837
|
+
fix: "npm uninstall -g tandem-editor (or: npm install -g tandem-editor@latest)",
|
|
129838
|
+
data: { globalVersion, bundledVersion: bundled }
|
|
129839
|
+
};
|
|
129840
|
+
}
|
|
129841
|
+
async function checkStaleGlobal(r) {
|
|
129842
|
+
const bundled = true ? "0.16.0" : null;
|
|
129843
|
+
if (!bundled) return;
|
|
129844
|
+
let globalVersion;
|
|
129845
|
+
try {
|
|
129846
|
+
globalVersion = await globalTandemEditorVersion();
|
|
129847
|
+
} catch {
|
|
129848
|
+
return;
|
|
129849
|
+
}
|
|
129850
|
+
const result2 = evaluateStaleGlobal(bundled, globalVersion);
|
|
129851
|
+
if (!result2) return;
|
|
129852
|
+
if (result2.status === "pass") {
|
|
129853
|
+
r.pass(result2.message);
|
|
129854
|
+
} else {
|
|
129855
|
+
r.warn(result2.message, result2.fix, result2.data);
|
|
129856
|
+
}
|
|
129857
|
+
}
|
|
129719
129858
|
async function runDoctor(opts = {}) {
|
|
129720
129859
|
const wsPort2 = opts.wsPort ?? DEFAULT_WS_PORT;
|
|
129721
129860
|
const mcpPort2 = opts.mcpPort ?? DEFAULT_MCP_PORT;
|
|
@@ -129725,6 +129864,7 @@ async function runDoctor(opts = {}) {
|
|
|
129725
129864
|
await r.check("mcp-json", () => checkMcpJson(r));
|
|
129726
129865
|
await r.check("user-mcp-config", () => checkUserMcpConfig(r));
|
|
129727
129866
|
await r.check("annotation-store", () => checkAnnotationStore(r));
|
|
129867
|
+
await r.check("stale-global", () => checkStaleGlobal(r));
|
|
129728
129868
|
const { mcp } = await r.check("ports", () => checkPorts(r, wsPort2, mcpPort2));
|
|
129729
129869
|
if (mcp) {
|
|
129730
129870
|
const healthy = await r.check("health", () => checkHealth(r, mcpPort2));
|
|
@@ -129914,12 +130054,20 @@ var init_document_reload = __esm({
|
|
|
129914
130054
|
async function handleResolveDocxConflict(req, res) {
|
|
129915
130055
|
if (assertOriginAllowlisted(req, res, API_DOCX_CONFLICT_RESOLVE)) return;
|
|
129916
130056
|
if (assertLoopbackForMutation(req, res)) return;
|
|
129917
|
-
const { choice } = req.body ?? {};
|
|
130057
|
+
const { documentId, choice } = req.body ?? {};
|
|
129918
130058
|
if (choice !== "keep" && choice !== "reload") {
|
|
129919
130059
|
res.status(400).json({ error: "BAD_REQUEST", message: 'choice must be "keep" or "reload".' });
|
|
129920
130060
|
return;
|
|
129921
130061
|
}
|
|
129922
|
-
|
|
130062
|
+
if (documentId !== void 0 && typeof documentId !== "string") {
|
|
130063
|
+
res.status(400).json({ error: "BAD_REQUEST", message: "documentId must be a string" });
|
|
130064
|
+
return;
|
|
130065
|
+
}
|
|
130066
|
+
if (typeof documentId === "string" && documentId.length > 0 && !hasDoc(documentId)) {
|
|
130067
|
+
res.status(400).json({ error: "NO_DOCUMENT", message: `Document ${documentId} is not open.` });
|
|
130068
|
+
return;
|
|
130069
|
+
}
|
|
130070
|
+
const docId = documentId?.length ? documentId : getActiveDocId();
|
|
129923
130071
|
if (!docId) {
|
|
129924
130072
|
res.status(400).json({ error: "BAD_REQUEST", message: "No active document." });
|
|
129925
130073
|
return;
|
|
@@ -130838,11 +130986,11 @@ function validateTandemEntry(entry) {
|
|
|
130838
130986
|
}
|
|
130839
130987
|
const args2 = Array.isArray(entry.args) ? entry.args : [];
|
|
130840
130988
|
if (entry.command === "npx") {
|
|
130841
|
-
const argsOk = args2.length ===
|
|
130989
|
+
const argsOk = args2.length === 3 && args2[0] === TANDEM_STDIO_NPX_HEAD[0] && typeof args2[1] === "string" && TANDEM_EDITOR_SPEC_RE.test(args2[1]) && args2[2] === TANDEM_STDIO_NPX_TAIL[0];
|
|
130842
130990
|
if (!argsOk) {
|
|
130843
130991
|
return {
|
|
130844
130992
|
status: "invalid-args",
|
|
130845
|
-
reason: `npx args must be ${JSON.stringify(TANDEM_STDIO_NPX_ARGS)}; got ${JSON.stringify(args2)}`
|
|
130993
|
+
reason: `npx args must be ${JSON.stringify(TANDEM_STDIO_NPX_ARGS)} (the package may be version-pinned as 'tandem-editor@<version>'); got ${JSON.stringify(args2)}`
|
|
130846
130994
|
};
|
|
130847
130995
|
}
|
|
130848
130996
|
return { status: "valid" };
|
|
@@ -130929,19 +131077,22 @@ function extractEntry(mcp, name2) {
|
|
|
130929
131077
|
function hasExistingTandemEntry(installs) {
|
|
130930
131078
|
return installs.some((i) => i.tandemEntry !== void 0);
|
|
130931
131079
|
}
|
|
130932
|
-
var TANDEM_STDIO_NPX_ARGS;
|
|
131080
|
+
var TANDEM_STDIO_NPX_HEAD, TANDEM_STDIO_NPX_TAIL, TANDEM_EDITOR_SPEC_RE, TANDEM_STDIO_NPX_ARGS;
|
|
130933
131081
|
var init_existing_config = __esm({
|
|
130934
131082
|
"src/server/integrations/existing-config.ts"() {
|
|
130935
131083
|
"use strict";
|
|
130936
131084
|
init_shared();
|
|
130937
131085
|
init_apply();
|
|
130938
131086
|
init_schema2();
|
|
130939
|
-
|
|
131087
|
+
TANDEM_STDIO_NPX_HEAD = ["-y"];
|
|
131088
|
+
TANDEM_STDIO_NPX_TAIL = ["mcp-stdio"];
|
|
131089
|
+
TANDEM_EDITOR_SPEC_RE = /^tandem-editor(@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)?$/;
|
|
131090
|
+
TANDEM_STDIO_NPX_ARGS = [...TANDEM_STDIO_NPX_HEAD, "tandem-editor", ...TANDEM_STDIO_NPX_TAIL];
|
|
130940
131091
|
}
|
|
130941
131092
|
});
|
|
130942
131093
|
|
|
130943
131094
|
// src/server/integrations/install-claude-cli.ts
|
|
130944
|
-
import { execFile as
|
|
131095
|
+
import { execFile as execFile4 } from "child_process";
|
|
130945
131096
|
import { mkdtemp, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
130946
131097
|
import { get as httpsGetDefault } from "https";
|
|
130947
131098
|
import { tmpdir as tmpdir3 } from "os";
|
|
@@ -131139,7 +131290,7 @@ var init_install_claude_cli = __esm({
|
|
|
131139
131290
|
"use strict";
|
|
131140
131291
|
init_acl_win();
|
|
131141
131292
|
init_apply();
|
|
131142
|
-
execFileAsyncDefault = promisify3(
|
|
131293
|
+
execFileAsyncDefault = promisify3(execFile4);
|
|
131143
131294
|
INSTALLER_HOST = "claude.ai";
|
|
131144
131295
|
INSTALLER_URL_POSIX = "https://claude.ai/install.sh";
|
|
131145
131296
|
INSTALLER_URL_WIN = "https://claude.ai/install.ps1";
|
|
@@ -132124,15 +132275,20 @@ var init_contract2 = __esm({
|
|
|
132124
132275
|
// src/server/launcher/supervisor.ts
|
|
132125
132276
|
var supervisor_exports = {};
|
|
132126
132277
|
__export(supervisor_exports, {
|
|
132278
|
+
RESUME_CONFIRM_MS: () => RESUME_CONFIRM_MS,
|
|
132127
132279
|
createSupervisor: () => createSupervisor,
|
|
132128
132280
|
resolveRouteCwd: () => resolveRouteCwd,
|
|
132129
|
-
resolveSafeCwd: () => resolveSafeCwd
|
|
132281
|
+
resolveSafeCwd: () => resolveSafeCwd,
|
|
132282
|
+
shouldClearSession: () => shouldClearSession
|
|
132130
132283
|
});
|
|
132131
132284
|
import { spawn as spawn2 } from "child_process";
|
|
132132
132285
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
132133
132286
|
import fs19 from "fs";
|
|
132134
132287
|
import os2 from "os";
|
|
132135
132288
|
import path28 from "path";
|
|
132289
|
+
function shouldClearSession(opts) {
|
|
132290
|
+
return opts.resuming && opts.code !== null && opts.code !== 0 && !opts.resumeConfirmed;
|
|
132291
|
+
}
|
|
132136
132292
|
function createSupervisor(opts) {
|
|
132137
132293
|
let child = null;
|
|
132138
132294
|
let currentCwd;
|
|
@@ -132141,6 +132297,7 @@ function createSupervisor(opts) {
|
|
|
132141
132297
|
let stopRequested = false;
|
|
132142
132298
|
let restartIndex = 0;
|
|
132143
132299
|
let restartTimer = null;
|
|
132300
|
+
let confirmTimer = null;
|
|
132144
132301
|
let recentAttempts = [];
|
|
132145
132302
|
let breakerTripped = false;
|
|
132146
132303
|
let opLock = Promise.resolve();
|
|
@@ -132266,11 +132423,22 @@ function createSupervisor(opts) {
|
|
|
132266
132423
|
writeSavedSession(plan.sessionId);
|
|
132267
132424
|
}
|
|
132268
132425
|
const spawnedAt = Date.now();
|
|
132426
|
+
let resumeConfirmed = !plan.resuming;
|
|
132427
|
+
if (plan.resuming) {
|
|
132428
|
+
confirmTimer = setTimeout(() => {
|
|
132429
|
+
resumeConfirmed = true;
|
|
132430
|
+
confirmTimer = null;
|
|
132431
|
+
}, RESUME_CONFIRM_MS);
|
|
132432
|
+
}
|
|
132269
132433
|
child.stderr?.on("data", (chunk2) => {
|
|
132270
132434
|
const line = chunk2.toString().trimEnd();
|
|
132271
132435
|
if (line) console.error(`[Claude] ${line}`);
|
|
132272
132436
|
});
|
|
132273
132437
|
child.on("error", (err) => {
|
|
132438
|
+
if (confirmTimer) {
|
|
132439
|
+
clearTimeout(confirmTimer);
|
|
132440
|
+
confirmTimer = null;
|
|
132441
|
+
}
|
|
132274
132442
|
child = null;
|
|
132275
132443
|
currentCwd = void 0;
|
|
132276
132444
|
currentSessionId = void 0;
|
|
@@ -132291,8 +132459,12 @@ function createSupervisor(opts) {
|
|
|
132291
132459
|
const ranFor = Date.now() - spawnedAt;
|
|
132292
132460
|
console.error(`[Launcher] Reaper exited (code=${code3} signal=${signal} after ${ranFor}ms)`);
|
|
132293
132461
|
child = null;
|
|
132294
|
-
if (
|
|
132295
|
-
|
|
132462
|
+
if (confirmTimer) {
|
|
132463
|
+
clearTimeout(confirmTimer);
|
|
132464
|
+
confirmTimer = null;
|
|
132465
|
+
}
|
|
132466
|
+
if (shouldClearSession({ resuming: plan.resuming, code: code3, resumeConfirmed })) {
|
|
132467
|
+
console.error("[Launcher] Resume failed before confirmation \u2014 clearing saved session");
|
|
132296
132468
|
clearSavedSession();
|
|
132297
132469
|
}
|
|
132298
132470
|
if (stopRequested) return;
|
|
@@ -132360,7 +132532,7 @@ function createSupervisor(opts) {
|
|
|
132360
132532
|
await spawnOnce(plan);
|
|
132361
132533
|
setTimeout(() => {
|
|
132362
132534
|
if (child) restartIndex = 0;
|
|
132363
|
-
},
|
|
132535
|
+
}, RESUME_CONFIRM_MS);
|
|
132364
132536
|
} catch (err) {
|
|
132365
132537
|
console.error("[Launcher] Spawn failed:", err);
|
|
132366
132538
|
}
|
|
@@ -132384,6 +132556,10 @@ function createSupervisor(opts) {
|
|
|
132384
132556
|
clearTimeout(restartTimer);
|
|
132385
132557
|
restartTimer = null;
|
|
132386
132558
|
}
|
|
132559
|
+
if (confirmTimer) {
|
|
132560
|
+
clearTimeout(confirmTimer);
|
|
132561
|
+
confirmTimer = null;
|
|
132562
|
+
}
|
|
132387
132563
|
const c = child;
|
|
132388
132564
|
if (!c || c.killed) return;
|
|
132389
132565
|
try {
|
|
@@ -132476,14 +132652,14 @@ function resolveRouteCwd(candidate, opts = {}) {
|
|
|
132476
132652
|
if (rel.startsWith("..") || path28.isAbsolute(rel)) return null;
|
|
132477
132653
|
return safe2;
|
|
132478
132654
|
}
|
|
132479
|
-
var SESSION_FILE_NAME,
|
|
132655
|
+
var SESSION_FILE_NAME, RESUME_CONFIRM_MS, RESTART_BACKOFFS_MS, CIRCUIT_BREAKER_MAX_ATTEMPTS, CIRCUIT_BREAKER_WINDOW_MS, UUID_V4_PATTERN;
|
|
132480
132656
|
var init_supervisor = __esm({
|
|
132481
132657
|
"src/server/launcher/supervisor.ts"() {
|
|
132482
132658
|
"use strict";
|
|
132483
132659
|
init_contract2();
|
|
132484
132660
|
init_storage2();
|
|
132485
132661
|
SESSION_FILE_NAME = "launcher-session.json";
|
|
132486
|
-
|
|
132662
|
+
RESUME_CONFIRM_MS = 3e4;
|
|
132487
132663
|
RESTART_BACKOFFS_MS = [1e3, 5e3, 3e4];
|
|
132488
132664
|
CIRCUIT_BREAKER_MAX_ATTEMPTS = 10;
|
|
132489
132665
|
CIRCUIT_BREAKER_WINDOW_MS = 5 * 6e4;
|
|
@@ -157904,7 +158080,7 @@ var init_childProcess = __esm({
|
|
|
157904
158080
|
});
|
|
157905
158081
|
|
|
157906
158082
|
// node_modules/@sentry/node-core/build/esm/integrations/context.js
|
|
157907
|
-
import { execFile as
|
|
158083
|
+
import { execFile as execFile5 } from "child_process";
|
|
157908
158084
|
import { readFile as readFile3, readdir as readdir2 } from "fs";
|
|
157909
158085
|
import * as os3 from "os";
|
|
157910
158086
|
import { join as join10 } from "path";
|
|
@@ -158081,7 +158257,7 @@ async function getDarwinInfo() {
|
|
|
158081
158257
|
};
|
|
158082
158258
|
try {
|
|
158083
158259
|
const output = await new Promise((resolve5, reject2) => {
|
|
158084
|
-
|
|
158260
|
+
execFile5("/usr/bin/sw_vers", (error5, stdout) => {
|
|
158085
158261
|
if (error5) {
|
|
158086
158262
|
reject2(error5);
|
|
158087
158263
|
return;
|
|
@@ -187897,12 +188073,48 @@ function registerChannelRoutes(app, apiMiddleware2) {
|
|
|
187897
188073
|
});
|
|
187898
188074
|
}
|
|
187899
188075
|
|
|
188076
|
+
// src/server/mcp/diagnostics.ts
|
|
188077
|
+
init_doctor();
|
|
188078
|
+
init_constants();
|
|
188079
|
+
init_output_schemas();
|
|
188080
|
+
init_response();
|
|
188081
|
+
init_diagnostics();
|
|
188082
|
+
function registerDiagnosticsTools(server, deps = {}) {
|
|
188083
|
+
const version3 = deps.version ?? "unknown";
|
|
188084
|
+
const transport = deps.transport ?? "http";
|
|
188085
|
+
const wsPort2 = deps.wsPort ?? DEFAULT_WS_PORT;
|
|
188086
|
+
const mcpPort2 = deps.mcpPort ?? DEFAULT_MCP_PORT;
|
|
188087
|
+
const collect = deps.collect ?? runDoctor;
|
|
188088
|
+
server.registerTool(
|
|
188089
|
+
"tandem_diagnostics",
|
|
188090
|
+
{
|
|
188091
|
+
description: "Read connection and boot health (Node version, config registration, port probes, /health, SSE) as a structured report. No params. Use this to self-diagnose a broken Tandem connection over MCP instead of asking the user to run `tandem doctor`.",
|
|
188092
|
+
inputSchema: {},
|
|
188093
|
+
outputSchema: diagnosticsOutputShape
|
|
188094
|
+
},
|
|
188095
|
+
withStructuredErrors(
|
|
188096
|
+
withErrorBoundary("tandem_diagnostics", async () => {
|
|
188097
|
+
const report = filterDevRepoChecks(await collect({ wsPort: wsPort2, mcpPort: mcpPort2 }));
|
|
188098
|
+
return mcpStructured({
|
|
188099
|
+
...report,
|
|
188100
|
+
version: version3,
|
|
188101
|
+
transport,
|
|
188102
|
+
platform: process.platform,
|
|
188103
|
+
arch: process.arch,
|
|
188104
|
+
nodeVersion: process.version,
|
|
188105
|
+
tauriSidecar: process.env.TANDEM_TAURI_SIDECAR === "1"
|
|
188106
|
+
});
|
|
188107
|
+
})
|
|
188108
|
+
)
|
|
188109
|
+
);
|
|
188110
|
+
}
|
|
188111
|
+
|
|
187900
188112
|
// src/server/mcp/server.ts
|
|
187901
188113
|
init_document2();
|
|
187902
188114
|
init_document_service();
|
|
187903
188115
|
init_docx_apply2();
|
|
187904
188116
|
var esmRequire2 = createRequire2(import.meta.url);
|
|
187905
|
-
var APP_VERSION = true ? "0.
|
|
188117
|
+
var APP_VERSION = true ? "0.16.0" : _readVersionFromDisk();
|
|
187906
188118
|
var MCP_SDK_VERSION = true ? "1.27.1" : "0.0.0-unknown";
|
|
187907
188119
|
var __dirname3 = dirname3(fileURLToPath4(import.meta.url));
|
|
187908
188120
|
var CLIENT_DIST = join7(__dirname3, "../client");
|
|
@@ -187928,7 +188140,7 @@ var WORKFLOWS_PATH = findRepoFile(__dirname3, "docs/workflows.md");
|
|
|
187928
188140
|
var mcpServer = null;
|
|
187929
188141
|
var currentTransport = null;
|
|
187930
188142
|
var connectingPromise = null;
|
|
187931
|
-
function createMcpServer() {
|
|
188143
|
+
function createMcpServer(diagnostics = {}) {
|
|
187932
188144
|
const server = new McpServer({
|
|
187933
188145
|
name: "tandem",
|
|
187934
188146
|
version: APP_VERSION
|
|
@@ -187938,6 +188150,7 @@ function createMcpServer() {
|
|
|
187938
188150
|
registerNavigationTools(server);
|
|
187939
188151
|
registerAwarenessTools(server);
|
|
187940
188152
|
registerApplyTools(server);
|
|
188153
|
+
registerDiagnosticsTools(server, diagnostics);
|
|
187941
188154
|
return server;
|
|
187942
188155
|
}
|
|
187943
188156
|
function jsonrpcId(body) {
|
|
@@ -187973,7 +188186,7 @@ async function closeMcpSession() {
|
|
|
187973
188186
|
}
|
|
187974
188187
|
}
|
|
187975
188188
|
async function startMcpServerStdio() {
|
|
187976
|
-
const server = createMcpServer();
|
|
188189
|
+
const server = createMcpServer({ version: APP_VERSION, transport: "stdio" });
|
|
187977
188190
|
const transport = new StdioServerTransport();
|
|
187978
188191
|
await server.connect(transport);
|
|
187979
188192
|
}
|
|
@@ -187986,7 +188199,7 @@ function snapshotToolCount(server) {
|
|
|
187986
188199
|
return Object.keys(tools).length;
|
|
187987
188200
|
}
|
|
187988
188201
|
async function startMcpServerHttp(port, host = DEFAULT_BIND_HOST, token, resolvedLanIP, launcher, wsPort2 = DEFAULT_WS_PORT, shutdownWiring) {
|
|
187989
|
-
mcpServer = createMcpServer();
|
|
188202
|
+
mcpServer = createMcpServer({ version: APP_VERSION, transport: "http", wsPort: wsPort2, mcpPort: port });
|
|
187990
188203
|
const toolCount = snapshotToolCount(mcpServer);
|
|
187991
188204
|
const { default: express2 } = await Promise.resolve().then(() => __toESM(require_express2(), 1));
|
|
187992
188205
|
const app = express2();
|