@ctliz/agent-intercom-claude 0.13.0-connect.9 → 0.14.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 +1 -1
- package/CHANGELOG.md +11 -0
- package/README.md +37 -3
- package/claude/cci.ts +13 -2
- package/claude/inbox-monitor.ts +34 -10
- package/claude/mcp-protocol.ts +1 -1
- package/claude/native-bridge.ts +8 -1
- package/claude/runtime.ts +72 -22
- package/claude/server.ts +47 -12
- package/claude/session-hook.ts +14 -0
- package/claude/session-lifecycle.ts +165 -0
- package/dist/cci.mjs +36 -1
- package/dist/ccim.mjs +36 -1
- package/dist/claude-server.mjs +254 -42
- package/dist/inbox-monitor.mjs +275 -14
- package/dist/session-hook.mjs +202 -0
- package/hooks/hooks.json +26 -0
- package/package.json +2 -1
- package/scripts/build.mjs +7 -0
- package/scripts/verify-packed-runtime.mjs +12 -1
package/dist/claude-server.mjs
CHANGED
|
@@ -1847,6 +1847,12 @@ function loadConfig() {
|
|
|
1847
1847
|
// claude/inbox.ts
|
|
1848
1848
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
1849
1849
|
import { dirname as dirname3, join as join5 } from "node:path";
|
|
1850
|
+
function sanitizeInboxSegment(value) {
|
|
1851
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "session";
|
|
1852
|
+
}
|
|
1853
|
+
function defaultInboxPath(sessionId, intercomDir = getIntercomDirPath()) {
|
|
1854
|
+
return join5(intercomDir, `inbox-${sanitizeInboxSegment(sessionId)}.jsonl`);
|
|
1855
|
+
}
|
|
1850
1856
|
function toInboxEntry(from, message, now = Date.now()) {
|
|
1851
1857
|
return {
|
|
1852
1858
|
ts: now,
|
|
@@ -2436,6 +2442,7 @@ var ClaudeIntercomRuntime = class {
|
|
|
2436
2442
|
reconnectTimer = null;
|
|
2437
2443
|
reconnectAttempt = 0;
|
|
2438
2444
|
reconnectEnabled = true;
|
|
2445
|
+
registrationConflict = null;
|
|
2439
2446
|
identity;
|
|
2440
2447
|
unread = [];
|
|
2441
2448
|
unresolvedAsks = /* @__PURE__ */ new Map();
|
|
@@ -2460,7 +2467,34 @@ var ClaudeIntercomRuntime = class {
|
|
|
2460
2467
|
getIdentity() {
|
|
2461
2468
|
return this.identity;
|
|
2462
2469
|
}
|
|
2470
|
+
async syncSession(identity) {
|
|
2471
|
+
const changed = identity.sessionId !== this.identity.sessionId || identity.cwd !== this.identity.cwd;
|
|
2472
|
+
if (changed) {
|
|
2473
|
+
await this.disconnect();
|
|
2474
|
+
this.unread = [];
|
|
2475
|
+
this.unresolvedAsks.clear();
|
|
2476
|
+
this.registrationConflict = null;
|
|
2477
|
+
}
|
|
2478
|
+
const renamed = identity.name !== this.identity.name;
|
|
2479
|
+
const modelChanged = identity.model !== this.identity.model;
|
|
2480
|
+
this.identity = identity;
|
|
2481
|
+
if (changed) await this.connect();
|
|
2482
|
+
else if (renamed || modelChanged) this.client?.updatePresence({ name: identity.name, model: identity.model });
|
|
2483
|
+
}
|
|
2484
|
+
pauseOnRegistrationConflict(error2) {
|
|
2485
|
+
let cause = error2;
|
|
2486
|
+
for (let depth = 0; cause instanceof Error && depth < 8; depth++, cause = cause.cause) {
|
|
2487
|
+
if (cause.code === "SESSION_ID_IN_USE") {
|
|
2488
|
+
this.registrationConflict = cause;
|
|
2489
|
+
this.reconnectEnabled = false;
|
|
2490
|
+
this.clearReconnectTimer();
|
|
2491
|
+
return true;
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
return false;
|
|
2495
|
+
}
|
|
2463
2496
|
async connect() {
|
|
2497
|
+
if (this.registrationConflict) throw this.registrationConflict;
|
|
2464
2498
|
this.reconnectEnabled = true;
|
|
2465
2499
|
this.clearReconnectTimer();
|
|
2466
2500
|
if (this.client?.isConnected()) return this.client;
|
|
@@ -2468,6 +2502,9 @@ var ClaudeIntercomRuntime = class {
|
|
|
2468
2502
|
this.connectPromise = this.connectOnce();
|
|
2469
2503
|
try {
|
|
2470
2504
|
return await this.connectPromise;
|
|
2505
|
+
} catch (error2) {
|
|
2506
|
+
this.pauseOnRegistrationConflict(error2);
|
|
2507
|
+
throw error2;
|
|
2471
2508
|
} finally {
|
|
2472
2509
|
this.connectPromise = null;
|
|
2473
2510
|
}
|
|
@@ -2479,32 +2516,36 @@ var ClaudeIntercomRuntime = class {
|
|
|
2479
2516
|
this.handleIncomingMessage(from, message);
|
|
2480
2517
|
client.acknowledgeMessage(deliveryId);
|
|
2481
2518
|
});
|
|
2519
|
+
client.on("error", (error2) => {
|
|
2520
|
+
this.pauseOnRegistrationConflict(error2);
|
|
2521
|
+
process.stderr.write(`claude-intercom: ${error2.message}
|
|
2522
|
+
`);
|
|
2523
|
+
});
|
|
2482
2524
|
client.on("disconnected", (error2) => {
|
|
2483
|
-
|
|
2484
|
-
clearTimeout(waiter.timeout);
|
|
2485
|
-
waiter.cleanup?.();
|
|
2486
|
-
waiter.reject(new Error(`Disconnected while waiting for reply: ${error2.message}`, { cause: error2 }));
|
|
2487
|
-
}
|
|
2488
|
-
this.replyWaiters.clear();
|
|
2525
|
+
this.rejectReplyWaiters(new Error(`Disconnected while waiting for reply: ${error2.message}`, { cause: error2 }));
|
|
2489
2526
|
if (this.client === client) this.client = null;
|
|
2490
2527
|
this.scheduleReconnect();
|
|
2491
2528
|
});
|
|
2529
|
+
const registeredIdentity = this.identity;
|
|
2492
2530
|
await client.connect({
|
|
2493
|
-
name:
|
|
2494
|
-
cwd:
|
|
2495
|
-
model:
|
|
2531
|
+
name: registeredIdentity.name,
|
|
2532
|
+
cwd: registeredIdentity.cwd,
|
|
2533
|
+
model: registeredIdentity.model,
|
|
2496
2534
|
pid: process.pid,
|
|
2497
|
-
startedAt:
|
|
2535
|
+
startedAt: registeredIdentity.startedAt,
|
|
2498
2536
|
lastActivity: Date.now(),
|
|
2499
2537
|
status: "idle"
|
|
2500
|
-
},
|
|
2538
|
+
}, registeredIdentity.sessionId);
|
|
2501
2539
|
this.client = client;
|
|
2540
|
+
if (registeredIdentity.name !== this.identity.name || registeredIdentity.model !== this.identity.model) {
|
|
2541
|
+
client.updatePresence({ name: this.identity.name, model: this.identity.model });
|
|
2542
|
+
}
|
|
2502
2543
|
this.reconnectAttempt = 0;
|
|
2503
2544
|
return client;
|
|
2504
2545
|
}
|
|
2505
2546
|
scheduleReconnect() {
|
|
2506
2547
|
if (!this.reconnectEnabled || this.reconnectTimer) return;
|
|
2507
|
-
const
|
|
2548
|
+
const delay2 = this.reconnectDelays[Math.min(this.reconnectAttempt, this.reconnectDelays.length - 1)];
|
|
2508
2549
|
this.reconnectTimer = setTimeout(() => {
|
|
2509
2550
|
this.reconnectTimer = null;
|
|
2510
2551
|
void this.connect().then((client) => {
|
|
@@ -2516,7 +2557,7 @@ var ClaudeIntercomRuntime = class {
|
|
|
2516
2557
|
this.reconnectAttempt += 1;
|
|
2517
2558
|
this.scheduleReconnect();
|
|
2518
2559
|
});
|
|
2519
|
-
},
|
|
2560
|
+
}, delay2);
|
|
2520
2561
|
this.reconnectTimer.unref?.();
|
|
2521
2562
|
}
|
|
2522
2563
|
clearReconnectTimer() {
|
|
@@ -2524,8 +2565,17 @@ var ClaudeIntercomRuntime = class {
|
|
|
2524
2565
|
clearTimeout(this.reconnectTimer);
|
|
2525
2566
|
this.reconnectTimer = null;
|
|
2526
2567
|
}
|
|
2568
|
+
rejectReplyWaiters(error2) {
|
|
2569
|
+
for (const waiter of this.replyWaiters.values()) {
|
|
2570
|
+
clearTimeout(waiter.timeout);
|
|
2571
|
+
waiter.cleanup?.();
|
|
2572
|
+
waiter.reject(error2);
|
|
2573
|
+
}
|
|
2574
|
+
this.replyWaiters.clear();
|
|
2575
|
+
}
|
|
2527
2576
|
async disconnect() {
|
|
2528
2577
|
this.reconnectEnabled = false;
|
|
2578
|
+
this.rejectReplyWaiters(new Error("Claude session disconnected while waiting for reply"));
|
|
2529
2579
|
this.clearReconnectTimer();
|
|
2530
2580
|
if (this.connectPromise) {
|
|
2531
2581
|
try {
|
|
@@ -2600,12 +2650,9 @@ var ClaudeIntercomRuntime = class {
|
|
|
2600
2650
|
if (message.expectsReply) {
|
|
2601
2651
|
this.unresolvedAsks.set(message.id, entry);
|
|
2602
2652
|
}
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
appendInboxMessage(inboxPath, from, message);
|
|
2607
|
-
} catch {
|
|
2608
|
-
}
|
|
2653
|
+
try {
|
|
2654
|
+
appendInboxMessage(process.env.CLAUDE_INTERCOM_INBOX || defaultInboxPath(this.identity.sessionId), from, message);
|
|
2655
|
+
} catch {
|
|
2609
2656
|
}
|
|
2610
2657
|
}
|
|
2611
2658
|
waitForReply(from, replyTo, timeoutMs = getAskTimeoutMs(), signal) {
|
|
@@ -2657,6 +2704,17 @@ cwd: ${this.identity.cwd}`,
|
|
|
2657
2704
|
return textResult(formatIntercomTeam(team), team);
|
|
2658
2705
|
}
|
|
2659
2706
|
async status() {
|
|
2707
|
+
if (this.registrationConflict) {
|
|
2708
|
+
return textResult(`Connected: No
|
|
2709
|
+
Session ID: ${this.identity.sessionId}
|
|
2710
|
+
Registration conflict: ${this.registrationConflict.message}
|
|
2711
|
+
Automatic reconnect paused; the incumbent session was not replaced.`, {
|
|
2712
|
+
connected: false,
|
|
2713
|
+
session_id: this.identity.sessionId,
|
|
2714
|
+
registration_conflict: { code: "SESSION_ID_IN_USE", message: this.registrationConflict.message },
|
|
2715
|
+
reconnect_paused: true
|
|
2716
|
+
}, true);
|
|
2717
|
+
}
|
|
2660
2718
|
const client = await this.connect();
|
|
2661
2719
|
const sessions = await client.listSessions();
|
|
2662
2720
|
return textResult(
|
|
@@ -2813,21 +2871,21 @@ var attachmentsSchema = {
|
|
|
2813
2871
|
additionalProperties: false
|
|
2814
2872
|
}
|
|
2815
2873
|
};
|
|
2816
|
-
function buildToolDefinitions(
|
|
2874
|
+
function buildToolDefinitions(runtime) {
|
|
2817
2875
|
const tools = [
|
|
2818
2876
|
{
|
|
2819
2877
|
name: "intercom_whoami",
|
|
2820
2878
|
description: "Return this Claude session's intercom identity for reliable targeting.",
|
|
2821
2879
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
2822
2880
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
2823
|
-
handler: async () =>
|
|
2881
|
+
handler: async () => runtime.whoami()
|
|
2824
2882
|
},
|
|
2825
2883
|
{
|
|
2826
2884
|
name: "intercom_team",
|
|
2827
2885
|
description: "Show your current manager and the live coworkers owned by that manager. No arguments are required.",
|
|
2828
2886
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
2829
2887
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
2830
|
-
handler: async () =>
|
|
2888
|
+
handler: async () => runtime.team()
|
|
2831
2889
|
},
|
|
2832
2890
|
{
|
|
2833
2891
|
name: "intercom_join",
|
|
@@ -2841,7 +2899,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2841
2899
|
additionalProperties: false
|
|
2842
2900
|
},
|
|
2843
2901
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
2844
|
-
handler: async (args) =>
|
|
2902
|
+
handler: async (args) => runtime.join(
|
|
2845
2903
|
typeof args.name === "string" ? args.name : void 0,
|
|
2846
2904
|
args.create === true
|
|
2847
2905
|
)
|
|
@@ -2851,7 +2909,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2851
2909
|
description: "Show intercom connection status, active sessions, unread messages, and pending asks.",
|
|
2852
2910
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
2853
2911
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
2854
|
-
handler: async () =>
|
|
2912
|
+
handler: async () => runtime.status()
|
|
2855
2913
|
},
|
|
2856
2914
|
{
|
|
2857
2915
|
name: "intercom_list",
|
|
@@ -2865,7 +2923,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2865
2923
|
additionalProperties: false
|
|
2866
2924
|
},
|
|
2867
2925
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
2868
|
-
handler: async (args) =>
|
|
2926
|
+
handler: async (args) => runtime.list(
|
|
2869
2927
|
args.scope === "directory" || args.scope === "repo" ? args.scope : "machine",
|
|
2870
2928
|
asBoolean(args.include_self, false)
|
|
2871
2929
|
)
|
|
@@ -2880,7 +2938,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2880
2938
|
additionalProperties: false
|
|
2881
2939
|
},
|
|
2882
2940
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
2883
|
-
handler: async (args) =>
|
|
2941
|
+
handler: async (args) => runtime.setSummary(asString(args.summary, "summary"))
|
|
2884
2942
|
},
|
|
2885
2943
|
{
|
|
2886
2944
|
name: "intercom_send",
|
|
@@ -2896,7 +2954,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2896
2954
|
additionalProperties: false
|
|
2897
2955
|
},
|
|
2898
2956
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
2899
|
-
handler: async (args) =>
|
|
2957
|
+
handler: async (args) => runtime.send(asString(args.to, "to"), asString(args.message, "message"), asAttachmentArray(args.attachments))
|
|
2900
2958
|
},
|
|
2901
2959
|
{
|
|
2902
2960
|
name: "intercom_ask",
|
|
@@ -2913,7 +2971,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2913
2971
|
additionalProperties: false
|
|
2914
2972
|
},
|
|
2915
2973
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
2916
|
-
handler: async (args, signal) =>
|
|
2974
|
+
handler: async (args, signal) => runtime.ask(
|
|
2917
2975
|
asString(args.to, "to"),
|
|
2918
2976
|
asString(args.message, "message"),
|
|
2919
2977
|
asAttachmentArray(args.attachments),
|
|
@@ -2930,7 +2988,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2930
2988
|
additionalProperties: false
|
|
2931
2989
|
},
|
|
2932
2990
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
2933
|
-
handler: async (args) =>
|
|
2991
|
+
handler: async (args) => runtime.pending(asBoolean(args.mark_read, false))
|
|
2934
2992
|
},
|
|
2935
2993
|
{
|
|
2936
2994
|
name: "intercom_reply",
|
|
@@ -2946,7 +3004,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2946
3004
|
additionalProperties: false
|
|
2947
3005
|
},
|
|
2948
3006
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
2949
|
-
handler: async (args) =>
|
|
3007
|
+
handler: async (args) => runtime.reply(asString(args.message, "message"), typeof args.to === "string" ? args.to : void 0, args.which === "oldest" || args.which === "latest" ? args.which : void 0)
|
|
2950
3008
|
}
|
|
2951
3009
|
];
|
|
2952
3010
|
return tools;
|
|
@@ -2957,7 +3015,7 @@ function ok(id, result) {
|
|
|
2957
3015
|
function error(id, code, message) {
|
|
2958
3016
|
return { jsonrpc: "2.0", id: id ?? null, error: { code, message } };
|
|
2959
3017
|
}
|
|
2960
|
-
async function handleMcpRequest(request,
|
|
3018
|
+
async function handleMcpRequest(request, runtime) {
|
|
2961
3019
|
if (!request.method) {
|
|
2962
3020
|
return error(request.id, -32600, "Invalid request");
|
|
2963
3021
|
}
|
|
@@ -2971,13 +3029,13 @@ async function handleMcpRequest(request, runtime2) {
|
|
|
2971
3029
|
if (request.id === void 0 && request.method.startsWith("notifications/")) {
|
|
2972
3030
|
return void 0;
|
|
2973
3031
|
}
|
|
2974
|
-
const tools = buildToolDefinitions(
|
|
3032
|
+
const tools = buildToolDefinitions(runtime);
|
|
2975
3033
|
switch (request.method) {
|
|
2976
3034
|
case "initialize":
|
|
2977
3035
|
return ok(request.id, {
|
|
2978
3036
|
protocolVersion: "2025-06-18",
|
|
2979
3037
|
capabilities: { tools: {} },
|
|
2980
|
-
serverInfo: { name: "claude-intercom", version: "0.
|
|
3038
|
+
serverInfo: { name: "claude-intercom", version: "0.14.0" }
|
|
2981
3039
|
});
|
|
2982
3040
|
case "ping":
|
|
2983
3041
|
return ok(request.id, {});
|
|
@@ -3011,19 +3069,169 @@ async function handleMcpRequest(request, runtime2) {
|
|
|
3011
3069
|
}
|
|
3012
3070
|
}
|
|
3013
3071
|
|
|
3072
|
+
// claude/session-lifecycle.ts
|
|
3073
|
+
import { execFileSync } from "node:child_process";
|
|
3074
|
+
import { closeSync as closeSync2, openSync as openSync2, readFileSync as readFileSync7, readSync, renameSync as renameSync3, statSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3075
|
+
import { basename as basename2, dirname as dirname4, join as join8 } from "node:path";
|
|
3076
|
+
import { StringDecoder } from "node:string_decoder";
|
|
3077
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
3078
|
+
function findClaudeHost(pid = process.ppid) {
|
|
3079
|
+
for (let depth = 0; pid > 1 && depth < 16; depth++) {
|
|
3080
|
+
let parts;
|
|
3081
|
+
try {
|
|
3082
|
+
parts = execFileSync(
|
|
3083
|
+
"ps",
|
|
3084
|
+
["-p", String(pid), "-o", "ppid=", "-o", "lstart=", "-o", "comm="],
|
|
3085
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
3086
|
+
).trim().split(/\s+/);
|
|
3087
|
+
} catch {
|
|
3088
|
+
return void 0;
|
|
3089
|
+
}
|
|
3090
|
+
const command = basename2(parts.slice(6).join(" "));
|
|
3091
|
+
if (/^claude(?:\.exe)?$/i.test(command)) {
|
|
3092
|
+
return { pid, startedAt: parts.slice(1, 6).join(" ") };
|
|
3093
|
+
}
|
|
3094
|
+
if (!/^(sh|bash|zsh|dash|fish)$/.test(command)) return void 0;
|
|
3095
|
+
pid = Number(parts[0]);
|
|
3096
|
+
}
|
|
3097
|
+
return void 0;
|
|
3098
|
+
}
|
|
3099
|
+
function claudeSessionMetadataPath(host) {
|
|
3100
|
+
return join8(getIntercomDirPath(), `claude-host-${host.pid}.json`);
|
|
3101
|
+
}
|
|
3102
|
+
function readClaudeSessionMetadata(path, host) {
|
|
3103
|
+
try {
|
|
3104
|
+
const data = JSON.parse(readFileSync7(path, "utf8"));
|
|
3105
|
+
if (data.hostStartedAt !== host.startedAt || typeof data.sessionId !== "string" || !/^[A-Za-z0-9_-]{1,128}$/.test(data.sessionId) || typeof data.cwd !== "string" || typeof data.transcriptPath !== "string" || typeof data.startedAt !== "number" || typeof data.ended !== "boolean" || data.name !== void 0 && typeof data.name !== "string" || data.model !== void 0 && typeof data.model !== "string") return void 0;
|
|
3106
|
+
return data;
|
|
3107
|
+
} catch {
|
|
3108
|
+
return void 0;
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
function claudeIdentityForSession(metadata, env = process.env, pid = process.pid) {
|
|
3112
|
+
const identity = buildClaudeRuntimeIdentity(env, metadata?.cwd ?? env.PWD ?? process.cwd(), pid);
|
|
3113
|
+
if (!metadata) return identity;
|
|
3114
|
+
if (!env.CLAUDE_INTERCOM_SESSION_ID?.trim() && !env.CLAUDE_PEER_ID?.trim() && !env.AGENT_INTERCOM_SESSION_ID?.trim()) {
|
|
3115
|
+
identity.sessionId = `claude-${metadata.sessionId}`;
|
|
3116
|
+
}
|
|
3117
|
+
identity.name = metadata.name?.trim() || identity.name;
|
|
3118
|
+
if (!env.CLAUDE_INTERCOM_MODEL?.trim()) identity.model = metadata.model?.trim() || identity.model;
|
|
3119
|
+
identity.startedAt = metadata.startedAt;
|
|
3120
|
+
return identity;
|
|
3121
|
+
}
|
|
3122
|
+
var ClaudeSessionReader = class {
|
|
3123
|
+
constructor(path, host) {
|
|
3124
|
+
this.path = path;
|
|
3125
|
+
this.host = host;
|
|
3126
|
+
}
|
|
3127
|
+
path;
|
|
3128
|
+
host;
|
|
3129
|
+
transcriptPath = "";
|
|
3130
|
+
sessionId = "";
|
|
3131
|
+
offset = 0;
|
|
3132
|
+
partial = "";
|
|
3133
|
+
decoder = new StringDecoder("utf8");
|
|
3134
|
+
name;
|
|
3135
|
+
startedAt = 0;
|
|
3136
|
+
read() {
|
|
3137
|
+
const metadata = readClaudeSessionMetadata(this.path, this.host);
|
|
3138
|
+
if (!metadata || metadata.ended) return metadata;
|
|
3139
|
+
if (metadata.sessionId !== this.sessionId || metadata.transcriptPath !== this.transcriptPath || metadata.startedAt !== this.startedAt) {
|
|
3140
|
+
this.sessionId = metadata.sessionId;
|
|
3141
|
+
this.transcriptPath = metadata.transcriptPath;
|
|
3142
|
+
this.startedAt = metadata.startedAt;
|
|
3143
|
+
this.name = void 0;
|
|
3144
|
+
this.partial = "";
|
|
3145
|
+
this.decoder = new StringDecoder("utf8");
|
|
3146
|
+
try {
|
|
3147
|
+
this.offset = Math.max(0, statSync(this.transcriptPath).size - 64 * 1024);
|
|
3148
|
+
} catch {
|
|
3149
|
+
this.offset = 0;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
let fd;
|
|
3153
|
+
try {
|
|
3154
|
+
fd = openSync2(this.transcriptPath, "r");
|
|
3155
|
+
const size = statSync(this.transcriptPath).size;
|
|
3156
|
+
if (size < this.offset) {
|
|
3157
|
+
this.offset = 0;
|
|
3158
|
+
this.partial = "";
|
|
3159
|
+
this.decoder = new StringDecoder("utf8");
|
|
3160
|
+
}
|
|
3161
|
+
const buffer = Buffer.alloc(Math.min(size - this.offset, 64 * 1024));
|
|
3162
|
+
const count = readSync(fd, buffer, 0, buffer.length, this.offset);
|
|
3163
|
+
this.offset += count;
|
|
3164
|
+
const lines = (this.partial + this.decoder.write(buffer.subarray(0, count))).split("\n");
|
|
3165
|
+
this.partial = lines.pop() ?? "";
|
|
3166
|
+
if (this.partial.length > 64 * 1024) this.partial = "";
|
|
3167
|
+
for (const line of lines) {
|
|
3168
|
+
if (!line.includes('"custom-title"')) continue;
|
|
3169
|
+
try {
|
|
3170
|
+
const record = JSON.parse(line);
|
|
3171
|
+
if (record.type === "custom-title" && record.sessionId === metadata.sessionId && typeof record.customTitle === "string" && record.customTitle.trim()) this.name = record.customTitle;
|
|
3172
|
+
} catch {
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
} catch {
|
|
3176
|
+
} finally {
|
|
3177
|
+
if (fd !== void 0) closeSync2(fd);
|
|
3178
|
+
}
|
|
3179
|
+
return { ...metadata, name: this.name ?? metadata.name };
|
|
3180
|
+
}
|
|
3181
|
+
};
|
|
3182
|
+
async function waitForClaudeSession(reader) {
|
|
3183
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
3184
|
+
const metadata = reader.read();
|
|
3185
|
+
if (metadata) return metadata;
|
|
3186
|
+
await delay(50);
|
|
3187
|
+
}
|
|
3188
|
+
return reader.read();
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3014
3191
|
// claude/server.ts
|
|
3015
|
-
var
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3192
|
+
var shuttingDown = false;
|
|
3193
|
+
var stopPolling = () => {
|
|
3194
|
+
};
|
|
3195
|
+
var pendingSync = Promise.resolve();
|
|
3196
|
+
var report = (error2) => {
|
|
3197
|
+
process.stderr.write(`claude-intercom: ${error2 instanceof Error ? error2.message : String(error2)}
|
|
3019
3198
|
`);
|
|
3020
|
-
|
|
3021
|
-
|
|
3199
|
+
};
|
|
3200
|
+
var runtimeReady = (async () => {
|
|
3201
|
+
const host = findClaudeHost();
|
|
3202
|
+
const reader = host ? new ClaudeSessionReader(claudeSessionMetadataPath(host), host) : void 0;
|
|
3203
|
+
const metadata = reader ? await waitForClaudeSession(reader) : void 0;
|
|
3204
|
+
const runtime = new ClaudeIntercomRuntime(claudeIdentityForSession(metadata, process.env, host?.pid));
|
|
3205
|
+
if (!shuttingDown && !metadata?.ended) void runtime.connect().catch(report);
|
|
3206
|
+
if (reader && !shuttingDown) {
|
|
3207
|
+
let previous = JSON.stringify(metadata);
|
|
3208
|
+
let syncing = false;
|
|
3209
|
+
const timer = setInterval(() => {
|
|
3210
|
+
if (syncing || shuttingDown) return;
|
|
3211
|
+
const next = reader.read();
|
|
3212
|
+
const signature = JSON.stringify(next);
|
|
3213
|
+
if (!next || signature === previous) return;
|
|
3214
|
+
previous = signature;
|
|
3215
|
+
syncing = true;
|
|
3216
|
+
pendingSync = (async () => {
|
|
3217
|
+
if (next.ended) await runtime.disconnect();
|
|
3218
|
+
else {
|
|
3219
|
+
await runtime.syncSession(claudeIdentityForSession(next, process.env, host.pid));
|
|
3220
|
+
await runtime.connect();
|
|
3221
|
+
}
|
|
3222
|
+
})().catch(report).finally(() => {
|
|
3223
|
+
syncing = false;
|
|
3224
|
+
});
|
|
3225
|
+
}, 250);
|
|
3226
|
+
timer.unref();
|
|
3227
|
+
stopPolling = () => clearInterval(timer);
|
|
3228
|
+
}
|
|
3229
|
+
return runtime;
|
|
3230
|
+
})();
|
|
3022
3231
|
var rl = readline.createInterface({
|
|
3023
3232
|
input: stdin,
|
|
3024
3233
|
crlfDelay: Infinity
|
|
3025
3234
|
});
|
|
3026
|
-
var shuttingDown = false;
|
|
3027
3235
|
var pendingRequests = 0;
|
|
3028
3236
|
function writeResponse(response) {
|
|
3029
3237
|
if (!response) return;
|
|
@@ -3032,7 +3240,11 @@ function writeResponse(response) {
|
|
|
3032
3240
|
}
|
|
3033
3241
|
function maybeShutdown() {
|
|
3034
3242
|
if (!shuttingDown || pendingRequests > 0) return;
|
|
3035
|
-
|
|
3243
|
+
stopPolling();
|
|
3244
|
+
void runtimeReady.then(async (runtime) => {
|
|
3245
|
+
await pendingSync;
|
|
3246
|
+
await runtime.disconnect();
|
|
3247
|
+
}).finally(() => process.exit(0));
|
|
3036
3248
|
}
|
|
3037
3249
|
rl.on("line", (line) => {
|
|
3038
3250
|
const trimmed = line.trim();
|
|
@@ -3041,7 +3253,7 @@ rl.on("line", (line) => {
|
|
|
3041
3253
|
void (async () => {
|
|
3042
3254
|
try {
|
|
3043
3255
|
const request = JSON.parse(trimmed);
|
|
3044
|
-
writeResponse(await handleMcpRequest(request,
|
|
3256
|
+
writeResponse(await handleMcpRequest(request, await runtimeReady));
|
|
3045
3257
|
} catch (error2) {
|
|
3046
3258
|
writeResponse({
|
|
3047
3259
|
jsonrpc: "2.0",
|