tandem-editor 0.4.0 → 0.6.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/marketplace.json +19 -0
- package/.claude-plugin/plugin.json +27 -0
- package/CHANGELOG.md +80 -0
- package/README.md +68 -23
- package/dist/channel/index.js +171 -190
- package/dist/channel/index.js.map +1 -1
- package/dist/cli/index.js +718 -133
- package/dist/cli/index.js.map +1 -1
- package/dist/client/assets/index-mo5ZOPfU.js +349 -0
- package/dist/client/assets/webview-0tvvWtyc.js +1 -0
- package/dist/client/index.html +63 -2
- package/dist/monitor/index.js +4570 -0
- package/dist/monitor/index.js.map +1 -0
- package/dist/server/index.js +1042 -801
- package/dist/server/index.js.map +1 -1
- package/package.json +13 -8
- package/sample/welcome.md +3 -3
- package/skills/tandem/SKILL.md +93 -0
- package/dist/client/assets/index-D6wQrQ7U.js +0 -308
package/dist/channel/index.js
CHANGED
|
@@ -6799,7 +6799,7 @@ var require_dist = __commonJS({
|
|
|
6799
6799
|
}
|
|
6800
6800
|
});
|
|
6801
6801
|
|
|
6802
|
-
// src/channel/
|
|
6802
|
+
// src/channel/run.ts
|
|
6803
6803
|
import { createConnection } from "net";
|
|
6804
6804
|
|
|
6805
6805
|
// node_modules/zod/v3/external.js
|
|
@@ -17915,13 +17915,24 @@ var SESSION_MAX_AGE = 30 * 24 * 60 * 60 * 1e3;
|
|
|
17915
17915
|
var CHANNEL_MAX_RETRIES = 5;
|
|
17916
17916
|
var CHANNEL_RETRY_DELAY_MS = 2e3;
|
|
17917
17917
|
|
|
17918
|
+
// src/shared/cli-runtime.ts
|
|
17919
|
+
function redirectConsoleToStderr() {
|
|
17920
|
+
console.log = console.error;
|
|
17921
|
+
console.warn = console.error;
|
|
17922
|
+
console.info = console.error;
|
|
17923
|
+
}
|
|
17924
|
+
function resolveTandemUrl(override) {
|
|
17925
|
+
const raw = override ?? process.env.TANDEM_URL ?? `http://localhost:${DEFAULT_MCP_PORT}`;
|
|
17926
|
+
return raw.replace(/\/$/, "");
|
|
17927
|
+
}
|
|
17928
|
+
|
|
17918
17929
|
// src/server/events/types.ts
|
|
17919
17930
|
var VALID_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
17920
17931
|
"annotation:created",
|
|
17921
17932
|
"annotation:accepted",
|
|
17922
17933
|
"annotation:dismissed",
|
|
17934
|
+
"annotation:reply",
|
|
17923
17935
|
"chat:message",
|
|
17924
|
-
"selection:changed",
|
|
17925
17936
|
"document:opened",
|
|
17926
17937
|
"document:closed",
|
|
17927
17938
|
"document:switched"
|
|
@@ -17949,15 +17960,17 @@ function formatEventContent(event) {
|
|
|
17949
17960
|
const { annotationId, textSnippet } = event.payload;
|
|
17950
17961
|
return `User dismissed annotation ${annotationId}${textSnippet ? ` ("${textSnippet}")` : ""}${doc}`;
|
|
17951
17962
|
}
|
|
17963
|
+
case "annotation:reply": {
|
|
17964
|
+
const { annotationId, replyAuthor, replyText, textSnippet } = event.payload;
|
|
17965
|
+
const who = replyAuthor === "claude" ? "Claude" : "User";
|
|
17966
|
+
const snippet = textSnippet ? ` (on "${textSnippet}")` : "";
|
|
17967
|
+
return `${who} replied to annotation ${annotationId}${snippet}: ${replyText}${doc}`;
|
|
17968
|
+
}
|
|
17952
17969
|
case "chat:message": {
|
|
17953
|
-
const { text, replyTo } = event.payload;
|
|
17970
|
+
const { text, replyTo, selection } = event.payload;
|
|
17954
17971
|
const reply = replyTo ? ` (replying to ${replyTo})` : "";
|
|
17955
|
-
|
|
17956
|
-
|
|
17957
|
-
case "selection:changed": {
|
|
17958
|
-
const { from, to, selectedText } = event.payload;
|
|
17959
|
-
if (!selectedText) return `User cleared selection${doc}`;
|
|
17960
|
-
return `User is pointing at text (${from}-${to}): "${selectedText}"${doc} \u2014 respond via tandem_reply`;
|
|
17972
|
+
const sel = selection && selection.selectedText ? ` [selection: "${selection.selectedText}"${"from" in selection ? ` (${selection.from}-${selection.to})` : ""}]` : "";
|
|
17973
|
+
return `User says${reply}: ${text}${sel}${doc}`;
|
|
17961
17974
|
}
|
|
17962
17975
|
case "document:opened": {
|
|
17963
17976
|
const { fileName, format } = event.payload;
|
|
@@ -17988,11 +18001,13 @@ function formatEventMeta(event) {
|
|
|
17988
18001
|
case "annotation:dismissed":
|
|
17989
18002
|
meta.annotation_id = event.payload.annotationId;
|
|
17990
18003
|
break;
|
|
18004
|
+
case "annotation:reply":
|
|
18005
|
+
meta.annotation_id = event.payload.annotationId;
|
|
18006
|
+
meta.reply_id = event.payload.replyId;
|
|
18007
|
+
break;
|
|
17991
18008
|
case "chat:message":
|
|
17992
18009
|
meta.message_id = event.payload.messageId;
|
|
17993
|
-
|
|
17994
|
-
case "selection:changed":
|
|
17995
|
-
meta.respond_via = "tandem_reply";
|
|
18010
|
+
if (event.payload.selection?.selectedText) meta.has_selection = "true";
|
|
17996
18011
|
break;
|
|
17997
18012
|
case "document:opened":
|
|
17998
18013
|
case "document:closed":
|
|
@@ -18008,14 +18023,13 @@ function formatEventMeta(event) {
|
|
|
18008
18023
|
|
|
18009
18024
|
// src/channel/event-bridge.ts
|
|
18010
18025
|
var AWARENESS_DEBOUNCE_MS = 500;
|
|
18011
|
-
var SELECTION_DEBOUNCE_MS = 300;
|
|
18012
18026
|
var MODE_CACHE_TTL_MS = 2e3;
|
|
18013
|
-
async function startEventBridge(
|
|
18027
|
+
async function startEventBridge(mcp, tandemUrl) {
|
|
18014
18028
|
let retries = 0;
|
|
18015
18029
|
let lastEventId;
|
|
18016
18030
|
while (retries < CHANNEL_MAX_RETRIES) {
|
|
18017
18031
|
try {
|
|
18018
|
-
await connectAndStream(
|
|
18032
|
+
await connectAndStream(mcp, tandemUrl, lastEventId, (id) => {
|
|
18019
18033
|
lastEventId = id;
|
|
18020
18034
|
retries = 0;
|
|
18021
18035
|
});
|
|
@@ -18048,7 +18062,7 @@ async function startEventBridge(mcp2, tandemUrl) {
|
|
|
18048
18062
|
}
|
|
18049
18063
|
}
|
|
18050
18064
|
}
|
|
18051
|
-
async function connectAndStream(
|
|
18065
|
+
async function connectAndStream(mcp, tandemUrl, lastEventId, onEventId) {
|
|
18052
18066
|
const headers = { Accept: "text/event-stream" };
|
|
18053
18067
|
if (lastEventId) headers["Last-Event-ID"] = lastEventId;
|
|
18054
18068
|
const res = await fetch(`${tandemUrl}/api/events`, { headers });
|
|
@@ -18096,35 +18110,7 @@ async function connectAndStream(mcp2, tandemUrl, lastEventId, onEventId) {
|
|
|
18096
18110
|
if (awarenessTimer) clearTimeout(awarenessTimer);
|
|
18097
18111
|
awarenessTimer = setTimeout(flushAwareness, AWARENESS_DEBOUNCE_MS);
|
|
18098
18112
|
}
|
|
18099
|
-
let selectionTimer = null;
|
|
18100
|
-
let pendingSelection = null;
|
|
18101
|
-
let transportBroken = false;
|
|
18102
|
-
async function flushSelection() {
|
|
18103
|
-
if (!pendingSelection) return;
|
|
18104
|
-
const { event, eventId } = pendingSelection;
|
|
18105
|
-
pendingSelection = null;
|
|
18106
|
-
if (eventId) onEventId(eventId);
|
|
18107
|
-
try {
|
|
18108
|
-
await mcp2.notification({
|
|
18109
|
-
method: "notifications/claude/channel",
|
|
18110
|
-
params: {
|
|
18111
|
-
content: formatEventContent(event),
|
|
18112
|
-
meta: formatEventMeta(event)
|
|
18113
|
-
}
|
|
18114
|
-
});
|
|
18115
|
-
} catch (err) {
|
|
18116
|
-
console.error("[Channel] MCP notification failed (transport broken?):", err);
|
|
18117
|
-
transportBroken = true;
|
|
18118
|
-
return;
|
|
18119
|
-
}
|
|
18120
|
-
scheduleAwareness(event);
|
|
18121
|
-
}
|
|
18122
|
-
function isSelectionCleared(event) {
|
|
18123
|
-
const p = event.payload;
|
|
18124
|
-
return !p || p.from === p.to && !p.selectedText;
|
|
18125
|
-
}
|
|
18126
18113
|
while (true) {
|
|
18127
|
-
if (transportBroken) throw new Error("MCP transport broken (detected in debounced flush)");
|
|
18128
18114
|
const { done, value } = await reader.read();
|
|
18129
18115
|
if (done) throw new Error("SSE stream ended");
|
|
18130
18116
|
buffer += decoder.decode(value, { stream: true });
|
|
@@ -18159,17 +18145,9 @@ async function connectAndStream(mcp2, tandemUrl, lastEventId, onEventId) {
|
|
|
18159
18145
|
continue;
|
|
18160
18146
|
}
|
|
18161
18147
|
}
|
|
18162
|
-
if (event.type === "selection:changed") {
|
|
18163
|
-
if (eventId) onEventId(eventId);
|
|
18164
|
-
if (isSelectionCleared(event)) continue;
|
|
18165
|
-
pendingSelection = { event, eventId };
|
|
18166
|
-
if (selectionTimer) clearTimeout(selectionTimer);
|
|
18167
|
-
selectionTimer = setTimeout(flushSelection, SELECTION_DEBOUNCE_MS);
|
|
18168
|
-
continue;
|
|
18169
|
-
}
|
|
18170
18148
|
if (eventId) onEventId(eventId);
|
|
18171
18149
|
try {
|
|
18172
|
-
await
|
|
18150
|
+
await mcp.notification({
|
|
18173
18151
|
method: "notifications/claude/channel",
|
|
18174
18152
|
params: {
|
|
18175
18153
|
content: formatEventContent(event),
|
|
@@ -18208,11 +18186,143 @@ async function getCachedMode(tandemUrl) {
|
|
|
18208
18186
|
return cachedMode;
|
|
18209
18187
|
}
|
|
18210
18188
|
|
|
18211
|
-
// src/channel/
|
|
18212
|
-
|
|
18213
|
-
|
|
18214
|
-
|
|
18215
|
-
|
|
18189
|
+
// src/channel/run.ts
|
|
18190
|
+
async function runChannel(opts = {}) {
|
|
18191
|
+
redirectConsoleToStderr();
|
|
18192
|
+
const tandemUrl = resolveTandemUrl();
|
|
18193
|
+
const mcp = new Server(
|
|
18194
|
+
{ name: "tandem-channel", version: "0.1.0" },
|
|
18195
|
+
{
|
|
18196
|
+
capabilities: {
|
|
18197
|
+
experimental: {
|
|
18198
|
+
"claude/channel": {},
|
|
18199
|
+
"claude/channel/permission": {}
|
|
18200
|
+
},
|
|
18201
|
+
tools: {}
|
|
18202
|
+
},
|
|
18203
|
+
instructions: [
|
|
18204
|
+
'Events from Tandem arrive as <channel source="tandem-channel" event_type="..." document_id="...">.',
|
|
18205
|
+
"These are real-time push notifications of user actions in the collaborative document editor.",
|
|
18206
|
+
"Event types: annotation:created, annotation:accepted, annotation:dismissed, annotation:reply,",
|
|
18207
|
+
"chat:message, document:opened, document:closed, document:switched.",
|
|
18208
|
+
"Chat messages may include a 'selection' field with buffered selection context.",
|
|
18209
|
+
"Use your tandem MCP tools (tandem_getTextContent, tandem_comment, tandem_highlight, etc.) to act on them.",
|
|
18210
|
+
"Reply to chat messages using tandem_reply. Pass document_id from the tag attributes.",
|
|
18211
|
+
"Do not reply to non-chat events \u2014 just act on them using tools.",
|
|
18212
|
+
"If you haven't received channel notifications recently, call tandem_checkInbox as a fallback."
|
|
18213
|
+
].join(" ")
|
|
18214
|
+
}
|
|
18215
|
+
);
|
|
18216
|
+
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
18217
|
+
tools: [
|
|
18218
|
+
{
|
|
18219
|
+
name: "tandem_reply",
|
|
18220
|
+
description: "Reply to a chat message in Tandem",
|
|
18221
|
+
inputSchema: {
|
|
18222
|
+
type: "object",
|
|
18223
|
+
properties: {
|
|
18224
|
+
text: { type: "string", description: "The reply message" },
|
|
18225
|
+
documentId: {
|
|
18226
|
+
type: "string",
|
|
18227
|
+
description: "Document ID from the channel event (optional)"
|
|
18228
|
+
},
|
|
18229
|
+
replyTo: {
|
|
18230
|
+
type: "string",
|
|
18231
|
+
description: "Message ID being replied to (optional)"
|
|
18232
|
+
}
|
|
18233
|
+
},
|
|
18234
|
+
required: ["text"]
|
|
18235
|
+
}
|
|
18236
|
+
}
|
|
18237
|
+
]
|
|
18238
|
+
}));
|
|
18239
|
+
mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
18240
|
+
if (req.params.name === "tandem_reply") {
|
|
18241
|
+
const args = req.params.arguments;
|
|
18242
|
+
try {
|
|
18243
|
+
const res = await fetch(`${tandemUrl}/api/channel-reply`, {
|
|
18244
|
+
method: "POST",
|
|
18245
|
+
headers: { "Content-Type": "application/json" },
|
|
18246
|
+
body: JSON.stringify(args)
|
|
18247
|
+
});
|
|
18248
|
+
let data;
|
|
18249
|
+
try {
|
|
18250
|
+
data = await res.json();
|
|
18251
|
+
} catch {
|
|
18252
|
+
data = { message: "Non-JSON response" };
|
|
18253
|
+
}
|
|
18254
|
+
if (!res.ok) {
|
|
18255
|
+
return {
|
|
18256
|
+
content: [
|
|
18257
|
+
{
|
|
18258
|
+
type: "text",
|
|
18259
|
+
text: `Reply failed (${res.status}): ${JSON.stringify(data)}`
|
|
18260
|
+
}
|
|
18261
|
+
],
|
|
18262
|
+
isError: true
|
|
18263
|
+
};
|
|
18264
|
+
}
|
|
18265
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
18266
|
+
} catch (err) {
|
|
18267
|
+
return {
|
|
18268
|
+
content: [
|
|
18269
|
+
{
|
|
18270
|
+
type: "text",
|
|
18271
|
+
text: `Failed to send reply: ${err instanceof Error ? err.message : String(err)}`
|
|
18272
|
+
}
|
|
18273
|
+
],
|
|
18274
|
+
isError: true
|
|
18275
|
+
};
|
|
18276
|
+
}
|
|
18277
|
+
}
|
|
18278
|
+
throw new Error(`Unknown tool: ${req.params.name}`);
|
|
18279
|
+
});
|
|
18280
|
+
const PermissionRequestSchema = external_exports.object({
|
|
18281
|
+
method: external_exports.literal("notifications/claude/channel/permission_request"),
|
|
18282
|
+
params: external_exports.object({
|
|
18283
|
+
request_id: external_exports.string(),
|
|
18284
|
+
tool_name: external_exports.string(),
|
|
18285
|
+
description: external_exports.string(),
|
|
18286
|
+
input_preview: external_exports.string()
|
|
18287
|
+
})
|
|
18288
|
+
});
|
|
18289
|
+
mcp.setNotificationHandler(PermissionRequestSchema, async ({ params }) => {
|
|
18290
|
+
try {
|
|
18291
|
+
const res = await fetch(`${tandemUrl}/api/channel-permission`, {
|
|
18292
|
+
method: "POST",
|
|
18293
|
+
headers: { "Content-Type": "application/json" },
|
|
18294
|
+
body: JSON.stringify({
|
|
18295
|
+
requestId: params.request_id,
|
|
18296
|
+
toolName: params.tool_name,
|
|
18297
|
+
description: params.description,
|
|
18298
|
+
inputPreview: params.input_preview
|
|
18299
|
+
})
|
|
18300
|
+
});
|
|
18301
|
+
if (!res.ok) {
|
|
18302
|
+
console.error(
|
|
18303
|
+
`[Channel] Permission relay got HTTP ${res.status} \u2014 browser may not see prompt`
|
|
18304
|
+
);
|
|
18305
|
+
}
|
|
18306
|
+
} catch (err) {
|
|
18307
|
+
console.error("[Channel] Failed to forward permission request:", err);
|
|
18308
|
+
}
|
|
18309
|
+
});
|
|
18310
|
+
console.error(`[Channel] Tandem channel shim starting (server: ${tandemUrl})`);
|
|
18311
|
+
if (!opts.skipReachabilityLog) {
|
|
18312
|
+
const reachable = await checkServerReachable(tandemUrl);
|
|
18313
|
+
if (!reachable) {
|
|
18314
|
+
console.error(`[Channel] Cannot reach Tandem server at ${tandemUrl}`);
|
|
18315
|
+
console.error("[Channel] Start it with: tandem start");
|
|
18316
|
+
}
|
|
18317
|
+
}
|
|
18318
|
+
const transport = new StdioServerTransport();
|
|
18319
|
+
await mcp.connect(transport);
|
|
18320
|
+
console.error("[Channel] Connected to Claude Code via stdio");
|
|
18321
|
+
startEventBridge(mcp, tandemUrl).catch((err) => {
|
|
18322
|
+
console.error("[Channel] Event bridge failed unexpectedly:", err);
|
|
18323
|
+
process.exit(1);
|
|
18324
|
+
});
|
|
18325
|
+
}
|
|
18216
18326
|
async function checkServerReachable(url, timeoutMs = 2e3) {
|
|
18217
18327
|
let parsed;
|
|
18218
18328
|
try {
|
|
@@ -18241,138 +18351,9 @@ async function checkServerReachable(url, timeoutMs = 2e3) {
|
|
|
18241
18351
|
});
|
|
18242
18352
|
});
|
|
18243
18353
|
}
|
|
18244
|
-
|
|
18245
|
-
|
|
18246
|
-
|
|
18247
|
-
capabilities: {
|
|
18248
|
-
experimental: {
|
|
18249
|
-
"claude/channel": {},
|
|
18250
|
-
"claude/channel/permission": {}
|
|
18251
|
-
},
|
|
18252
|
-
tools: {}
|
|
18253
|
-
},
|
|
18254
|
-
instructions: [
|
|
18255
|
-
'Events from Tandem arrive as <channel source="tandem-channel" event_type="..." document_id="...">.',
|
|
18256
|
-
"These are real-time push notifications of user actions in the collaborative document editor.",
|
|
18257
|
-
"Event types: annotation:created, annotation:accepted, annotation:dismissed,",
|
|
18258
|
-
"chat:message, selection:changed, document:opened, document:closed, document:switched.",
|
|
18259
|
-
"Use your tandem MCP tools (tandem_getTextContent, tandem_comment, tandem_highlight, etc.) to act on them.",
|
|
18260
|
-
"Reply to chat messages using tandem_reply. Pass document_id from the tag attributes.",
|
|
18261
|
-
"Do not reply to non-chat events \u2014 just act on them using tools.",
|
|
18262
|
-
"If you haven't received channel notifications recently, call tandem_checkInbox as a fallback."
|
|
18263
|
-
].join(" ")
|
|
18264
|
-
}
|
|
18265
|
-
);
|
|
18266
|
-
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
18267
|
-
tools: [
|
|
18268
|
-
{
|
|
18269
|
-
name: "tandem_reply",
|
|
18270
|
-
description: "Reply to a chat message in Tandem",
|
|
18271
|
-
inputSchema: {
|
|
18272
|
-
type: "object",
|
|
18273
|
-
properties: {
|
|
18274
|
-
text: { type: "string", description: "The reply message" },
|
|
18275
|
-
documentId: {
|
|
18276
|
-
type: "string",
|
|
18277
|
-
description: "Document ID from the channel event (optional)"
|
|
18278
|
-
},
|
|
18279
|
-
replyTo: {
|
|
18280
|
-
type: "string",
|
|
18281
|
-
description: "Message ID being replied to (optional)"
|
|
18282
|
-
}
|
|
18283
|
-
},
|
|
18284
|
-
required: ["text"]
|
|
18285
|
-
}
|
|
18286
|
-
}
|
|
18287
|
-
]
|
|
18288
|
-
}));
|
|
18289
|
-
mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
18290
|
-
if (req.params.name === "tandem_reply") {
|
|
18291
|
-
const args = req.params.arguments;
|
|
18292
|
-
try {
|
|
18293
|
-
const res = await fetch(`${TANDEM_URL}/api/channel-reply`, {
|
|
18294
|
-
method: "POST",
|
|
18295
|
-
headers: { "Content-Type": "application/json" },
|
|
18296
|
-
body: JSON.stringify(args)
|
|
18297
|
-
});
|
|
18298
|
-
let data;
|
|
18299
|
-
try {
|
|
18300
|
-
data = await res.json();
|
|
18301
|
-
} catch {
|
|
18302
|
-
data = { message: "Non-JSON response" };
|
|
18303
|
-
}
|
|
18304
|
-
if (!res.ok) {
|
|
18305
|
-
return {
|
|
18306
|
-
content: [
|
|
18307
|
-
{
|
|
18308
|
-
type: "text",
|
|
18309
|
-
text: `Reply failed (${res.status}): ${JSON.stringify(data)}`
|
|
18310
|
-
}
|
|
18311
|
-
],
|
|
18312
|
-
isError: true
|
|
18313
|
-
};
|
|
18314
|
-
}
|
|
18315
|
-
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
18316
|
-
} catch (err) {
|
|
18317
|
-
return {
|
|
18318
|
-
content: [
|
|
18319
|
-
{
|
|
18320
|
-
type: "text",
|
|
18321
|
-
text: `Failed to send reply: ${err instanceof Error ? err.message : String(err)}`
|
|
18322
|
-
}
|
|
18323
|
-
],
|
|
18324
|
-
isError: true
|
|
18325
|
-
};
|
|
18326
|
-
}
|
|
18327
|
-
}
|
|
18328
|
-
throw new Error(`Unknown tool: ${req.params.name}`);
|
|
18329
|
-
});
|
|
18330
|
-
var PermissionRequestSchema = external_exports.object({
|
|
18331
|
-
method: external_exports.literal("notifications/claude/channel/permission_request"),
|
|
18332
|
-
params: external_exports.object({
|
|
18333
|
-
request_id: external_exports.string(),
|
|
18334
|
-
tool_name: external_exports.string(),
|
|
18335
|
-
description: external_exports.string(),
|
|
18336
|
-
input_preview: external_exports.string()
|
|
18337
|
-
})
|
|
18338
|
-
});
|
|
18339
|
-
mcp.setNotificationHandler(PermissionRequestSchema, async ({ params }) => {
|
|
18340
|
-
try {
|
|
18341
|
-
const res = await fetch(`${TANDEM_URL}/api/channel-permission`, {
|
|
18342
|
-
method: "POST",
|
|
18343
|
-
headers: { "Content-Type": "application/json" },
|
|
18344
|
-
body: JSON.stringify({
|
|
18345
|
-
requestId: params.request_id,
|
|
18346
|
-
toolName: params.tool_name,
|
|
18347
|
-
description: params.description,
|
|
18348
|
-
inputPreview: params.input_preview
|
|
18349
|
-
})
|
|
18350
|
-
});
|
|
18351
|
-
if (!res.ok) {
|
|
18352
|
-
console.error(
|
|
18353
|
-
`[Channel] Permission relay got HTTP ${res.status} \u2014 browser may not see prompt`
|
|
18354
|
-
);
|
|
18355
|
-
}
|
|
18356
|
-
} catch (err) {
|
|
18357
|
-
console.error("[Channel] Failed to forward permission request:", err);
|
|
18358
|
-
}
|
|
18359
|
-
});
|
|
18360
|
-
async function main() {
|
|
18361
|
-
console.error(`[Channel] Tandem channel shim starting (server: ${TANDEM_URL})`);
|
|
18362
|
-
const reachable = await checkServerReachable(TANDEM_URL);
|
|
18363
|
-
if (!reachable) {
|
|
18364
|
-
console.error(`[Channel] Cannot reach Tandem server at ${TANDEM_URL}`);
|
|
18365
|
-
console.error("[Channel] Start it with: npm run dev:standalone");
|
|
18366
|
-
}
|
|
18367
|
-
const transport = new StdioServerTransport();
|
|
18368
|
-
await mcp.connect(transport);
|
|
18369
|
-
console.error("[Channel] Connected to Claude Code via stdio");
|
|
18370
|
-
startEventBridge(mcp, TANDEM_URL).catch((err) => {
|
|
18371
|
-
console.error("[Channel] Event bridge failed unexpectedly:", err);
|
|
18372
|
-
process.exit(1);
|
|
18373
|
-
});
|
|
18374
|
-
}
|
|
18375
|
-
main().catch((err) => {
|
|
18354
|
+
|
|
18355
|
+
// src/channel/index.ts
|
|
18356
|
+
runChannel().catch((err) => {
|
|
18376
18357
|
console.error("[Channel] Fatal error:", err);
|
|
18377
18358
|
process.exit(1);
|
|
18378
18359
|
});
|