claude-threads 1.34.2 → 1.35.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.
@@ -14700,6 +14700,16 @@ var init_logger = __esm(() => {
14700
14700
  wsLogger = createLogger("ws", false);
14701
14701
  });
14702
14702
 
14703
+ // src/config/types.ts
14704
+ function isRemoteMcpServer(server) {
14705
+ return server.type === "http" || server.type === "sse";
14706
+ }
14707
+ var BOT_MCP_SERVER_NAME = "claude-threads-mcp", STDIO_KEYS, REMOTE_KEYS;
14708
+ var init_types = __esm(() => {
14709
+ STDIO_KEYS = new Set(["type", "command", "args", "env"]);
14710
+ REMOTE_KEYS = new Set(["type", "url", "headers"]);
14711
+ });
14712
+
14703
14713
  // src/utils/spawn.ts
14704
14714
  import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "child_process";
14705
14715
  function addWindowsShell(options) {
@@ -14716,10 +14726,695 @@ var init_spawn = __esm(() => {
14716
14726
  isWindows = process.platform === "win32";
14717
14727
  });
14718
14728
 
14729
+ // src/mcp/outbound-env.ts
14730
+ var OUTBOUND_ENV;
14731
+ var init_outbound_env = __esm(() => {
14732
+ OUTBOUND_ENV = {
14733
+ SESSION_WORKING_DIR: "SESSION_WORKING_DIR",
14734
+ SESSION_UPLOAD_DIR: "SESSION_UPLOAD_DIR",
14735
+ OUTBOUND_FILES_ENABLED: "OUTBOUND_FILES_ENABLED",
14736
+ OUTBOUND_FILES_MAX_BYTES: "OUTBOUND_FILES_MAX_BYTES"
14737
+ };
14738
+ });
14739
+
14740
+ // src/mcp/agent-features-env.ts
14741
+ var AGENT_FEATURES_ENV;
14742
+ var init_agent_features_env = __esm(() => {
14743
+ AGENT_FEATURES_ENV = {
14744
+ MEMORY_CHANNEL_ENABLED: "CT_MEMORY_CHANNEL_ENABLED",
14745
+ ROUTINES_ENABLED: "CT_ROUTINES_ENABLED",
14746
+ WATCHES_ENABLED: "CT_WATCHES_ENABLED",
14747
+ UNATTENDED: "CT_UNATTENDED",
14748
+ DCM: "CT_DCM"
14749
+ };
14750
+ });
14751
+
14752
+ // src/claude/rate-limit-detector.ts
14753
+ function detectRateLimit(text, now = Date.now()) {
14754
+ if (!text)
14755
+ return { detected: false };
14756
+ let matched;
14757
+ for (const phrase of RATE_LIMIT_PHRASES) {
14758
+ const m = text.match(phrase);
14759
+ if (m) {
14760
+ matched = m[0];
14761
+ break;
14762
+ }
14763
+ }
14764
+ if (!matched)
14765
+ return { detected: false };
14766
+ const resetAtEpochMs = extractResetAt(text, now);
14767
+ return { detected: true, matched, resetAtEpochMs };
14768
+ }
14769
+ function cooldownDeadline(hit, now = Date.now()) {
14770
+ if (!hit.detected)
14771
+ return now;
14772
+ return hit.resetAtEpochMs ?? now + DEFAULT_COOLDOWN_MS;
14773
+ }
14774
+ function extractResetAt(text, now) {
14775
+ const relative = text.match(/(?:retry[_\s-]?after|resets?\s+in)\s+(\d+)\s*(second|minute|hour|day)s?/i);
14776
+ if (relative) {
14777
+ const value = parseInt(relative[1], 10);
14778
+ const unit = relative[2].toLowerCase();
14779
+ const unitMs = {
14780
+ second: 1000,
14781
+ minute: 60000,
14782
+ hour: 3600000,
14783
+ day: 86400000
14784
+ };
14785
+ return now + value * unitMs[unit];
14786
+ }
14787
+ const unix = text.match(/\breset(?:_at)?\b\s*["']?\s*[:=]\s*(\d{10,13})/);
14788
+ if (unix) {
14789
+ const raw = parseInt(unix[1], 10);
14790
+ return unix[1].length === 13 ? raw : raw * 1000;
14791
+ }
14792
+ const clock = text.match(/resets?\s+at\s+(\d{1,2}):(\d{2})\s*(utc|gmt)?/i);
14793
+ if (clock) {
14794
+ const hh = parseInt(clock[1], 10);
14795
+ const mm = parseInt(clock[2], 10);
14796
+ if (hh < 24 && mm < 60) {
14797
+ const reference = new Date(now);
14798
+ const target = new Date(Date.UTC(reference.getUTCFullYear(), reference.getUTCMonth(), reference.getUTCDate(), hh, mm)).getTime();
14799
+ return target > now ? target : target + 86400000;
14800
+ }
14801
+ }
14802
+ return;
14803
+ }
14804
+ function parseRateLimitEvent(event, now = Date.now()) {
14805
+ const info = event?.rate_limit_info;
14806
+ if (!info || typeof info !== "object")
14807
+ return { detected: false };
14808
+ const { status, resetsAt } = info;
14809
+ if (status !== "rejected")
14810
+ return { detected: false };
14811
+ const PAST_SKEW_TOLERANCE_MS = 2 * 60000;
14812
+ let resetAtEpochMs;
14813
+ if (typeof resetsAt === "number") {
14814
+ const ms = resetsAt * 1000;
14815
+ if (ms > now && ms - now < 8 * 86400000) {
14816
+ resetAtEpochMs = ms;
14817
+ } else if (ms <= now && now - ms < PAST_SKEW_TOLERANCE_MS) {
14818
+ resetAtEpochMs = now + 60000;
14819
+ }
14820
+ }
14821
+ return { detected: true, matched: `rate_limit_event status=${status}`, resetAtEpochMs };
14822
+ }
14823
+ var RATE_LIMIT_PHRASES, DEFAULT_COOLDOWN_MS;
14824
+ var init_rate_limit_detector = __esm(() => {
14825
+ RATE_LIMIT_PHRASES = [
14826
+ /usage limit reached/i,
14827
+ /rate[_\s-]?limit[_\s-]?error/i,
14828
+ /you have hit the rate limit/i,
14829
+ /quota (has been )?exceeded/i,
14830
+ /\b429\b.*(rate|limit|quota)/i
14831
+ ];
14832
+ DEFAULT_COOLDOWN_MS = 60 * 60 * 1000;
14833
+ });
14834
+
14835
+ // src/claude/cli.ts
14836
+ import { EventEmitter as EventEmitter2 } from "events";
14837
+ import { resolve as resolve4, dirname as dirname5 } from "path";
14838
+ import { fileURLToPath as fileURLToPath3 } from "url";
14839
+ import { existsSync as existsSync4, readFileSync as readFileSync3, watchFile, unwatchFile, unlinkSync, statSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
14840
+ import { tmpdir as tmpdir2 } from "os";
14841
+ import { join as join6 } from "path";
14842
+ function cleanupBrowserBridgeSockets() {
14843
+ try {
14844
+ const tempDir = tmpdir2();
14845
+ const files = readdirSync(tempDir);
14846
+ for (const file2 of files) {
14847
+ if (file2.startsWith("claude-mcp-browser-bridge-")) {
14848
+ const filePath = join6(tempDir, file2);
14849
+ try {
14850
+ const stats = statSync(filePath);
14851
+ if (stats.isSocket()) {
14852
+ unlinkSync(filePath);
14853
+ log11.debug(`Removed stale browser bridge socket: ${file2}`);
14854
+ }
14855
+ } catch {}
14856
+ }
14857
+ }
14858
+ } catch (err) {
14859
+ log11.debug(`Browser bridge cleanup failed: ${err}`);
14860
+ }
14861
+ }
14862
+ function buildClaudeChildEnv(parentEnv, account, opts) {
14863
+ const env = { ...parentEnv };
14864
+ if (opts?.claudeAiConnectors !== true) {
14865
+ env.ENABLE_CLAUDEAI_MCP_SERVERS = "false";
14866
+ }
14867
+ if (env.MCP_CONNECTION_NONBLOCKING === undefined) {
14868
+ env.MCP_CONNECTION_NONBLOCKING = "true";
14869
+ }
14870
+ if (env.ENABLE_PROMPT_CACHING_1H === undefined) {
14871
+ env.ENABLE_PROMPT_CACHING_1H = "true";
14872
+ }
14873
+ if (opts?.disableAutoMemory) {
14874
+ env.CLAUDE_CODE_DISABLE_AUTO_MEMORY = "1";
14875
+ }
14876
+ if (opts?.decisionBridge && env.MCP_TOOL_TIMEOUT === undefined) {
14877
+ env.MCP_TOOL_TIMEOUT = "3600000";
14878
+ }
14879
+ if (account?.home) {
14880
+ env.HOME = account.home;
14881
+ env.USERPROFILE = account.home;
14882
+ delete env.ANTHROPIC_API_KEY;
14883
+ delete env.CLAUDE_CODE_OAUTH_TOKEN;
14884
+ delete env.ANTHROPIC_AUTH_TOKEN;
14885
+ delete env.CLAUDE_CONFIG_DIR;
14886
+ delete env.CLAUDE_SECURESTORAGE_CONFIG_DIR;
14887
+ } else if (account?.apiKey) {
14888
+ env.ANTHROPIC_API_KEY = account.apiKey;
14889
+ delete env.CLAUDE_CODE_OAUTH_TOKEN;
14890
+ delete env.ANTHROPIC_AUTH_TOKEN;
14891
+ }
14892
+ return env;
14893
+ }
14894
+ function buildInlineSettings(statusLineCommand, memory, mcp = {}) {
14895
+ const settings = {};
14896
+ if (mcp.claudeAiConnectors !== true) {
14897
+ settings.disableClaudeAiConnectors = true;
14898
+ }
14899
+ if (statusLineCommand) {
14900
+ settings.statusLine = {
14901
+ type: "command",
14902
+ command: statusLineCommand,
14903
+ padding: 0
14904
+ };
14905
+ }
14906
+ if (memory) {
14907
+ settings.autoMemoryEnabled = true;
14908
+ settings.autoMemoryDirectory = memory.autoMemoryDir;
14909
+ }
14910
+ return Object.keys(settings).length > 0 ? settings : null;
14911
+ }
14912
+ function runtimeForScriptPath(scriptPath) {
14913
+ return scriptPath.endsWith(".ts") ? process.execPath : "node";
14914
+ }
14915
+ function isErrorResultEvent(event) {
14916
+ const ev = event;
14917
+ if (typeof ev.subtype === "string" && ev.subtype.startsWith("error"))
14918
+ return true;
14919
+ if (ev.is_error === true)
14920
+ return true;
14921
+ return false;
14922
+ }
14923
+ function materializeMcpConfig(config2, sessionId, opts = {}) {
14924
+ if (opts.inline) {
14925
+ return { mode: "inline", value: JSON.stringify(config2) };
14926
+ }
14927
+ const dir = opts.tmpDirOverride ?? tmpdir2();
14928
+ const path = join6(dir, `claude-threads-mcp-${sessionId ?? process.pid}-${Date.now()}.json`);
14929
+ writeFileSync2(path, JSON.stringify(config2), { mode: 384 });
14930
+ return { mode: "file", path };
14931
+ }
14932
+ function buildPermissionArgs(opts) {
14933
+ const args = [];
14934
+ if (opts.permissionMode === "bypass" && !opts.platformConfig) {
14935
+ args.push("--dangerously-skip-permissions");
14936
+ return { args, tempFile: null };
14937
+ }
14938
+ if (!opts.platformConfig) {
14939
+ throw new Error(`platformConfig is required when permissionMode is '${opts.permissionMode}'`);
14940
+ }
14941
+ const mcpEnv = {
14942
+ PLATFORM_TYPE: opts.platformConfig.type,
14943
+ PLATFORM_URL: opts.platformConfig.url,
14944
+ PLATFORM_TOKEN: opts.platformConfig.token,
14945
+ PLATFORM_CHANNEL_ID: opts.platformConfig.channelId,
14946
+ PLATFORM_THREAD_ID: opts.threadId || "",
14947
+ ALLOWED_USERS: opts.platformConfig.allowedUsers.join(","),
14948
+ DEBUG: opts.debug ? "1" : "",
14949
+ PERMISSION_TIMEOUT_MS: String(opts.permissionTimeoutMs),
14950
+ SESSION_OWNER_USERNAME: opts.sessionOwnerUsername || ""
14951
+ };
14952
+ if (opts.decisionBridgePath) {
14953
+ mcpEnv.DECISION_BRIDGE_PATH = opts.decisionBridgePath;
14954
+ if (process.env.DECISION_BRIDGE_TIMEOUT_MS) {
14955
+ mcpEnv.DECISION_BRIDGE_TIMEOUT_MS = process.env.DECISION_BRIDGE_TIMEOUT_MS;
14956
+ }
14957
+ const features = opts.agentFeatures;
14958
+ if (features) {
14959
+ if (features.memoryChannel)
14960
+ mcpEnv[AGENT_FEATURES_ENV.MEMORY_CHANNEL_ENABLED] = "1";
14961
+ if (features.routines)
14962
+ mcpEnv[AGENT_FEATURES_ENV.ROUTINES_ENABLED] = "1";
14963
+ if (features.watches)
14964
+ mcpEnv[AGENT_FEATURES_ENV.WATCHES_ENABLED] = "1";
14965
+ if (features.unattended)
14966
+ mcpEnv[AGENT_FEATURES_ENV.UNATTENDED] = "1";
14967
+ if (features.dcm)
14968
+ mcpEnv[AGENT_FEATURES_ENV.DCM] = "1";
14969
+ }
14970
+ }
14971
+ if (opts.platformConfig.appToken) {
14972
+ mcpEnv.PLATFORM_APP_TOKEN = opts.platformConfig.appToken;
14973
+ }
14974
+ if (opts.workingDir) {
14975
+ mcpEnv[OUTBOUND_ENV.SESSION_WORKING_DIR] = opts.workingDir;
14976
+ }
14977
+ if (opts.uploadDir) {
14978
+ mcpEnv[OUTBOUND_ENV.SESSION_UPLOAD_DIR] = opts.uploadDir;
14979
+ }
14980
+ if (opts.outboundFiles?.enabled === false) {
14981
+ mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_ENABLED] = "0";
14982
+ }
14983
+ if (typeof opts.outboundFiles?.maxBytes === "number" && Number.isFinite(opts.outboundFiles.maxBytes) && opts.outboundFiles.maxBytes > 0) {
14984
+ mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_MAX_BYTES] = String(opts.outboundFiles.maxBytes);
14985
+ }
14986
+ const mcpConfig = {
14987
+ mcpServers: {
14988
+ "claude-threads-mcp": {
14989
+ type: "stdio",
14990
+ command: runtimeForScriptPath(opts.mcpServerPath),
14991
+ args: [opts.mcpServerPath],
14992
+ env: mcpEnv
14993
+ }
14994
+ }
14995
+ };
14996
+ for (const [name, server] of Object.entries(opts.platformConfig.mcpServers ?? {})) {
14997
+ if (name === BOT_MCP_SERVER_NAME)
14998
+ continue;
14999
+ mcpConfig.mcpServers[name] = isRemoteMcpServer(server) ? { type: server.type, url: server.url, ...server.headers ? { headers: server.headers } : {} } : { type: "stdio", command: server.command, args: server.args ?? [], env: server.env ?? {} };
15000
+ }
15001
+ const materialized = materializeMcpConfig(mcpConfig, opts.sessionId, { inline: opts.inline });
15002
+ let tempFile = null;
15003
+ if (materialized.mode === "file") {
15004
+ tempFile = materialized.path;
15005
+ args.push("--mcp-config", materialized.path);
15006
+ } else {
15007
+ args.push("--mcp-config", materialized.value);
15008
+ }
15009
+ if (opts.platformConfig.strictMcpConfig === true) {
15010
+ args.push("--strict-mcp-config");
15011
+ }
15012
+ if (opts.permissionMode === "bypass") {
15013
+ args.push("--dangerously-skip-permissions");
15014
+ } else {
15015
+ args.push("--permission-prompt-tool", "mcp__claude-threads-mcp__permission_prompt");
15016
+ if (opts.permissionMode === "auto") {
15017
+ args.push("--permission-mode", "auto");
15018
+ }
15019
+ }
15020
+ return { args, tempFile };
15021
+ }
15022
+ var log11, STDERR_PER_INSTANCE_CAP = 10240, STDERR_AGGREGATE_SOFT_CAP, totalStderrBytes = 0, ClaudeCli;
15023
+ var init_cli = __esm(() => {
15024
+ init_types();
15025
+ init_spawn();
15026
+ init_logger();
15027
+ init_version_check();
15028
+ init_outbound_env();
15029
+ init_agent_features_env();
15030
+ init_rate_limit_detector();
15031
+ log11 = createLogger("claude");
15032
+ STDERR_AGGREGATE_SOFT_CAP = 10 * 1024 * 1024;
15033
+ ClaudeCli = class ClaudeCli extends EventEmitter2 {
15034
+ process = null;
15035
+ options;
15036
+ buffer = "";
15037
+ debug = process.env.DEBUG === "1" || process.argv.includes("--debug");
15038
+ statusFilePath = null;
15039
+ lastStatusData = null;
15040
+ stderrBuffer = "";
15041
+ mcpConfigTempFile = null;
15042
+ lastEmittedRateLimitDeadline = 0;
15043
+ lastEmittedHitHadExplicitReset = false;
15044
+ log;
15045
+ constructor(options) {
15046
+ super();
15047
+ this.options = options;
15048
+ this.log = options.logSessionId ? createLogger("claude").forSession(options.logSessionId) : createLogger("claude");
15049
+ }
15050
+ getStatusFilePath() {
15051
+ return this.statusFilePath;
15052
+ }
15053
+ getStatusData() {
15054
+ if (!this.statusFilePath)
15055
+ return null;
15056
+ try {
15057
+ if (existsSync4(this.statusFilePath)) {
15058
+ const data = readFileSync3(this.statusFilePath, "utf8");
15059
+ this.lastStatusData = JSON.parse(data);
15060
+ }
15061
+ } catch (err) {
15062
+ this.log.debug(`Failed to read status file: ${err}`);
15063
+ }
15064
+ return this.lastStatusData;
15065
+ }
15066
+ startStatusWatch() {
15067
+ if (!this.statusFilePath) {
15068
+ this.log.debug("No status file path, skipping status watch");
15069
+ return;
15070
+ }
15071
+ this.log.debug(`Starting status watch: ${this.statusFilePath}`);
15072
+ const checkStatus = () => {
15073
+ const data = this.getStatusData();
15074
+ if (data && data.timestamp !== this.lastStatusData?.timestamp) {
15075
+ this.lastStatusData = data;
15076
+ this.emit("status", data);
15077
+ }
15078
+ };
15079
+ watchFile(this.statusFilePath, { interval: 1000 }, checkStatus);
15080
+ }
15081
+ stopStatusWatch() {
15082
+ if (this.statusFilePath) {
15083
+ unwatchFile(this.statusFilePath);
15084
+ try {
15085
+ if (existsSync4(this.statusFilePath)) {
15086
+ unlinkSync(this.statusFilePath);
15087
+ }
15088
+ } catch {}
15089
+ }
15090
+ }
15091
+ start() {
15092
+ if (this.process)
15093
+ throw new Error("Already running");
15094
+ totalStderrBytes -= this.stderrBuffer.length;
15095
+ this.stderrBuffer = "";
15096
+ this.lastEmittedRateLimitDeadline = 0;
15097
+ this.lastEmittedHitHadExplicitReset = false;
15098
+ cleanupBrowserBridgeSockets();
15099
+ const claudePath = getClaudePath();
15100
+ const args = [
15101
+ "--input-format",
15102
+ "stream-json",
15103
+ "--output-format",
15104
+ "stream-json",
15105
+ "--verbose"
15106
+ ];
15107
+ if (this.options.sessionId) {
15108
+ if (this.options.resume) {
15109
+ args.push("--resume", this.options.sessionId);
15110
+ } else {
15111
+ args.push("--session-id", this.options.sessionId);
15112
+ }
15113
+ }
15114
+ const permissionMode = this.options.permissionMode ?? "default";
15115
+ const permResult = buildPermissionArgs({
15116
+ permissionMode,
15117
+ mcpServerPath: this.getMcpServerPath(),
15118
+ platformConfig: this.options.platformConfig,
15119
+ threadId: this.options.threadId,
15120
+ sessionId: this.options.sessionId,
15121
+ permissionTimeoutMs: this.options.permissionTimeoutMs ?? 120000,
15122
+ debug: this.debug,
15123
+ workingDir: this.options.workingDir,
15124
+ uploadDir: this.options.uploadDir,
15125
+ outboundFiles: this.options.outboundFiles,
15126
+ sessionOwnerUsername: this.options.sessionOwnerUsername,
15127
+ decisionBridgePath: this.options.decisionBridgePath,
15128
+ agentFeatures: this.options.agentFeatures
15129
+ });
15130
+ args.push(...permResult.args);
15131
+ this.mcpConfigTempFile = permResult.tempFile;
15132
+ if (this.options.chrome) {
15133
+ args.push("--chrome");
15134
+ }
15135
+ if (this.options.appendSystemPrompt) {
15136
+ args.push("--append-system-prompt", this.options.appendSystemPrompt);
15137
+ }
15138
+ let statusLineCommand;
15139
+ if (this.options.sessionId) {
15140
+ this.statusFilePath = join6(tmpdir2(), `claude-threads-status-${this.options.sessionId}.json`);
15141
+ const statusLineWriterPath = this.getStatusLineWriterPath();
15142
+ const runtime = runtimeForScriptPath(statusLineWriterPath);
15143
+ statusLineCommand = `${runtime} ${statusLineWriterPath} ${this.options.sessionId}`;
15144
+ }
15145
+ const settings = buildInlineSettings(statusLineCommand, this.options.memory, {
15146
+ claudeAiConnectors: this.options.platformConfig?.claudeAiConnectors
15147
+ });
15148
+ if (settings) {
15149
+ args.push("--settings", JSON.stringify(settings));
15150
+ }
15151
+ this.log.debug(`Starting: ${claudePath} ${args.slice(0, 5).join(" ")}...`);
15152
+ const childEnv = this.buildChildEnv();
15153
+ if (this.options.account) {
15154
+ this.log.debug(`Spawning under Claude account "${this.options.account.id}"`);
15155
+ }
15156
+ this.process = crossSpawn(claudePath, args, {
15157
+ cwd: this.options.workingDir,
15158
+ env: childEnv,
15159
+ stdio: ["pipe", "pipe", "pipe"]
15160
+ });
15161
+ this.log.debug(`Claude process spawned: pid=${this.process.pid}`);
15162
+ this.process.stdout?.on("data", (chunk) => {
15163
+ this.parseOutput(chunk.toString());
15164
+ });
15165
+ this.process.stderr?.on("data", (chunk) => {
15166
+ const text = chunk.toString();
15167
+ const before = this.stderrBuffer.length;
15168
+ this.stderrBuffer += text;
15169
+ const cap = totalStderrBytes > STDERR_AGGREGATE_SOFT_CAP ? 1024 : STDERR_PER_INSTANCE_CAP;
15170
+ if (this.stderrBuffer.length > cap) {
15171
+ this.stderrBuffer = this.stderrBuffer.slice(-cap);
15172
+ }
15173
+ totalStderrBytes += this.stderrBuffer.length - before;
15174
+ this.log.debug(`stderr: ${text.trim()}`);
15175
+ if (process.env.INTEGRATION_TEST === "1") {
15176
+ process.stderr.write(text);
15177
+ }
15178
+ this.maybeEmitRateLimit(text);
15179
+ });
15180
+ this.process.on("error", (err) => {
15181
+ this.log.error(`Claude error: ${err}`);
15182
+ this.emit("error", err);
15183
+ });
15184
+ this.process.on("exit", (code) => {
15185
+ this.log.debug(`Exited ${code}`);
15186
+ this.process = null;
15187
+ this.buffer = "";
15188
+ totalStderrBytes -= this.stderrBuffer.length;
15189
+ if (this.mcpConfigTempFile) {
15190
+ const path = this.mcpConfigTempFile;
15191
+ this.mcpConfigTempFile = null;
15192
+ try {
15193
+ unlinkSync(path);
15194
+ } catch {}
15195
+ }
15196
+ this.emit("exit", code);
15197
+ });
15198
+ }
15199
+ sendMessage(content) {
15200
+ if (!this.process?.stdin)
15201
+ throw new Error("Not running");
15202
+ const msg = JSON.stringify({
15203
+ type: "user",
15204
+ message: { role: "user", content }
15205
+ }) + `
15206
+ `;
15207
+ const preview = content.substring(0, 50);
15208
+ this.log.debug(`Sending: ${preview}...`);
15209
+ if (process.env.INTEGRATION_TEST === "1") {
15210
+ const stack = new Error().stack?.split(`
15211
+ `).slice(2, 6).join(" > ").replace(/\s+at\s+/g, " < ") ?? "?";
15212
+ process.stderr.write(`[claude-cli sendMessage pid=${this.process.pid}] ${preview} | ${stack}
15213
+ `);
15214
+ }
15215
+ this.process.stdin.write(msg);
15216
+ }
15217
+ sendToolResult(toolUseId, content) {
15218
+ if (!this.process?.stdin)
15219
+ throw new Error("Not running");
15220
+ const msg = JSON.stringify({
15221
+ type: "user",
15222
+ message: {
15223
+ role: "user",
15224
+ content: [{
15225
+ type: "tool_result",
15226
+ tool_use_id: toolUseId,
15227
+ content: typeof content === "string" ? content : JSON.stringify(content)
15228
+ }]
15229
+ }
15230
+ }) + `
15231
+ `;
15232
+ this.log.debug(`Sending tool_result for ${toolUseId}`);
15233
+ this.process.stdin.write(msg);
15234
+ }
15235
+ parseOutput(data) {
15236
+ this.buffer += data;
15237
+ const lines = this.buffer.split(`
15238
+ `);
15239
+ this.buffer = lines.pop() || "";
15240
+ for (const line of lines) {
15241
+ const trimmed = line.trim();
15242
+ if (!trimmed)
15243
+ continue;
15244
+ let event;
15245
+ try {
15246
+ event = JSON.parse(trimmed);
15247
+ } catch {
15248
+ continue;
15249
+ }
15250
+ try {
15251
+ this.emit("event", event);
15252
+ } catch (err) {
15253
+ this.log.error(`'event' listener threw while handling a ${event.type} event: ${err}`);
15254
+ }
15255
+ if (event.type === "result" && isErrorResultEvent(event)) {
15256
+ this.maybeEmitRateLimit(trimmed);
15257
+ }
15258
+ if (event.type === "rate_limit_event") {
15259
+ this.maybeEmitRateLimitHit(parseRateLimitEvent(event));
15260
+ }
15261
+ }
15262
+ }
15263
+ maybeEmitRateLimit(text) {
15264
+ this.maybeEmitRateLimitHit(detectRateLimit(text));
15265
+ }
15266
+ maybeEmitRateLimitHit(hit) {
15267
+ if (!hit.detected)
15268
+ return;
15269
+ if (!hit.resetAtEpochMs && this.lastEmittedHitHadExplicitReset && this.lastEmittedRateLimitDeadline > Date.now()) {
15270
+ return;
15271
+ }
15272
+ const newDeadline = cooldownDeadline(hit);
15273
+ const MIN_ADVANCE_MS = 60000;
15274
+ if (newDeadline - this.lastEmittedRateLimitDeadline < MIN_ADVANCE_MS) {
15275
+ if (hit.resetAtEpochMs !== undefined) {
15276
+ this.lastEmittedHitHadExplicitReset = true;
15277
+ }
15278
+ return;
15279
+ }
15280
+ this.lastEmittedRateLimitDeadline = newDeadline;
15281
+ this.lastEmittedHitHadExplicitReset = hit.resetAtEpochMs !== undefined;
15282
+ this.log.warn(`Rate limit detected: ${hit.matched ?? "(no match text)"}`);
15283
+ this.emit("rate-limit", hit);
15284
+ }
15285
+ isRunning() {
15286
+ return this.process !== null;
15287
+ }
15288
+ getLastStderr() {
15289
+ return this.stderrBuffer;
15290
+ }
15291
+ isPermanentFailure() {
15292
+ const stderr = this.stderrBuffer;
15293
+ if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
15294
+ return true;
15295
+ }
15296
+ if (stderr.includes("No conversation found with session ID")) {
15297
+ return true;
15298
+ }
15299
+ return false;
15300
+ }
15301
+ getPermanentFailureReason() {
15302
+ const stderr = this.stderrBuffer;
15303
+ if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
15304
+ return "Claude browser bridge state from a previous session is no longer accessible. This typically happens when a session with Chrome integration is resumed after a restart.";
15305
+ }
15306
+ if (stderr.includes("No conversation found with session ID")) {
15307
+ return "The conversation history for this session no longer exists. This can happen if Claude's history was cleared or if the session was created on a different machine.";
15308
+ }
15309
+ return null;
15310
+ }
15311
+ kill() {
15312
+ this.stopStatusWatch();
15313
+ if (!this.process) {
15314
+ this.log.debug("Kill called but process not running");
15315
+ return Promise.resolve();
15316
+ }
15317
+ const proc = this.process;
15318
+ const pid = proc.pid;
15319
+ this.process = null;
15320
+ this.log.debug(`Killing Claude process (pid=${pid})`);
15321
+ if (process.env.INTEGRATION_TEST === "1") {
15322
+ const stack = new Error().stack?.split(`
15323
+ `).slice(2, 7).join(" > ").replace(/\s+at\s+/g, " < ") ?? "?";
15324
+ process.stderr.write(`[claude-cli kill pid=${pid}] | ${stack}
15325
+ `);
15326
+ }
15327
+ return new Promise((resolve5) => {
15328
+ this.log.debug("Sending first SIGINT");
15329
+ proc.kill("SIGINT");
15330
+ const secondSigint = setTimeout(() => {
15331
+ try {
15332
+ this.log.debug("Sending second SIGINT");
15333
+ proc.kill("SIGINT");
15334
+ } catch {}
15335
+ }, 100);
15336
+ const forceKillTimeout = setTimeout(() => {
15337
+ try {
15338
+ this.log.debug("Sending SIGTERM (force kill)");
15339
+ proc.kill("SIGTERM");
15340
+ } catch {}
15341
+ }, 2000);
15342
+ const settle = (reason) => {
15343
+ this.log.debug(`Claude process gone (${reason})`);
15344
+ clearTimeout(secondSigint);
15345
+ clearTimeout(forceKillTimeout);
15346
+ clearTimeout(lastResort);
15347
+ resolve5();
15348
+ };
15349
+ const lastResort = setTimeout(() => {
15350
+ try {
15351
+ this.log.warn("Claude process did not exit after SIGTERM — sending SIGKILL");
15352
+ proc.kill("SIGKILL");
15353
+ } catch {}
15354
+ settle("kill timeout");
15355
+ }, 5000);
15356
+ proc.once("close", (code) => settle(`closed, code=${code}`));
15357
+ proc.once("error", () => settle("spawn error"));
15358
+ });
15359
+ }
15360
+ interrupt() {
15361
+ if (!this.process) {
15362
+ this.log.debug("Interrupt called but process not running");
15363
+ return false;
15364
+ }
15365
+ this.log.debug(`Interrupting Claude process (pid=${this.process.pid})`);
15366
+ this.process.kill("SIGINT");
15367
+ return true;
15368
+ }
15369
+ buildChildEnv() {
15370
+ return buildClaudeChildEnv(process.env, this.options.account, {
15371
+ claudeAiConnectors: this.options.platformConfig?.claudeAiConnectors,
15372
+ decisionBridge: this.options.decisionBridgePath !== undefined,
15373
+ disableAutoMemory: this.options.memory === null
15374
+ });
15375
+ }
15376
+ getMcpServerPath() {
15377
+ const __filename2 = fileURLToPath3(import.meta.url);
15378
+ const __dirname4 = dirname5(__filename2);
15379
+ const bundledPath = resolve4(__dirname4, "mcp", "mcp-server.js");
15380
+ if (existsSync4(bundledPath)) {
15381
+ return bundledPath;
15382
+ }
15383
+ const sourceLayoutPath = resolve4(__dirname4, "..", "mcp", "mcp-server.js");
15384
+ if (existsSync4(sourceLayoutPath)) {
15385
+ return sourceLayoutPath;
15386
+ }
15387
+ const tsPath = resolve4(__dirname4, "..", "mcp", "mcp-server.ts");
15388
+ if (existsSync4(tsPath)) {
15389
+ return tsPath;
15390
+ }
15391
+ return sourceLayoutPath;
15392
+ }
15393
+ getStatusLineWriterPath() {
15394
+ const __filename2 = fileURLToPath3(import.meta.url);
15395
+ const __dirname4 = dirname5(__filename2);
15396
+ const bundledPath = resolve4(__dirname4, "statusline", "writer.js");
15397
+ if (existsSync4(bundledPath)) {
15398
+ return bundledPath;
15399
+ }
15400
+ const sourceLayoutPath = resolve4(__dirname4, "..", "statusline", "writer.js");
15401
+ if (existsSync4(sourceLayoutPath)) {
15402
+ return sourceLayoutPath;
15403
+ }
15404
+ const tsPath = resolve4(__dirname4, "..", "statusline", "writer.ts");
15405
+ if (existsSync4(tsPath)) {
15406
+ return tsPath;
15407
+ }
15408
+ return sourceLayoutPath;
15409
+ }
15410
+ };
15411
+ });
15412
+
14719
15413
  // src/claude/quick-query.ts
14720
15414
  var log16;
14721
15415
  var init_quick_query = __esm(() => {
14722
15416
  init_spawn();
15417
+ init_cli();
14723
15418
  init_version_check();
14724
15419
  init_logger();
14725
15420
  log16 = createLogger("query");
@@ -52680,6 +53375,12 @@ function requireJsYaml() {
52680
53375
  var jsYamlExports = requireJsYaml();
52681
53376
  var yaml = /* @__PURE__ */ getDefaultExportFromCjs(jsYamlExports);
52682
53377
 
53378
+ // src/config/index.ts
53379
+ init_types();
53380
+
53381
+ // src/config/mcp-posture.ts
53382
+ init_types();
53383
+
52683
53384
  // src/config/index.ts
52684
53385
  var CONFIG_PATH = resolve2(homedir3(), ".config", "claude-threads", "config.yaml");
52685
53386
 
@@ -53424,661 +54125,9 @@ async function requestAgentAction(path, request, timeoutMs) {
53424
54125
  return response;
53425
54126
  }
53426
54127
 
53427
- // src/claude/cli.ts
53428
- init_spawn();
53429
- init_logger();
53430
- init_version_check();
53431
- import { EventEmitter as EventEmitter2 } from "events";
53432
- import { resolve as resolve4, dirname as dirname5 } from "path";
53433
- import { fileURLToPath as fileURLToPath3 } from "url";
53434
- import { existsSync as existsSync4, readFileSync as readFileSync3, watchFile, unwatchFile, unlinkSync, statSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
53435
- import { tmpdir as tmpdir2 } from "os";
53436
- import { join as join6 } from "path";
53437
-
53438
- // src/mcp/outbound-env.ts
53439
- var OUTBOUND_ENV = {
53440
- SESSION_WORKING_DIR: "SESSION_WORKING_DIR",
53441
- SESSION_UPLOAD_DIR: "SESSION_UPLOAD_DIR",
53442
- OUTBOUND_FILES_ENABLED: "OUTBOUND_FILES_ENABLED",
53443
- OUTBOUND_FILES_MAX_BYTES: "OUTBOUND_FILES_MAX_BYTES"
53444
- };
53445
-
53446
- // src/mcp/agent-features-env.ts
53447
- var AGENT_FEATURES_ENV = {
53448
- MEMORY_CHANNEL_ENABLED: "CT_MEMORY_CHANNEL_ENABLED",
53449
- ROUTINES_ENABLED: "CT_ROUTINES_ENABLED",
53450
- WATCHES_ENABLED: "CT_WATCHES_ENABLED",
53451
- UNATTENDED: "CT_UNATTENDED",
53452
- DCM: "CT_DCM"
53453
- };
53454
-
53455
- // src/claude/rate-limit-detector.ts
53456
- var RATE_LIMIT_PHRASES = [
53457
- /usage limit reached/i,
53458
- /rate[_\s-]?limit[_\s-]?error/i,
53459
- /you have hit the rate limit/i,
53460
- /quota (has been )?exceeded/i,
53461
- /\b429\b.*(rate|limit|quota)/i
53462
- ];
53463
- var DEFAULT_COOLDOWN_MS = 60 * 60 * 1000;
53464
- function detectRateLimit(text, now = Date.now()) {
53465
- if (!text)
53466
- return { detected: false };
53467
- let matched;
53468
- for (const phrase of RATE_LIMIT_PHRASES) {
53469
- const m = text.match(phrase);
53470
- if (m) {
53471
- matched = m[0];
53472
- break;
53473
- }
53474
- }
53475
- if (!matched)
53476
- return { detected: false };
53477
- const resetAtEpochMs = extractResetAt(text, now);
53478
- return { detected: true, matched, resetAtEpochMs };
53479
- }
53480
- function cooldownDeadline(hit, now = Date.now()) {
53481
- if (!hit.detected)
53482
- return now;
53483
- return hit.resetAtEpochMs ?? now + DEFAULT_COOLDOWN_MS;
53484
- }
53485
- function extractResetAt(text, now) {
53486
- const relative = text.match(/(?:retry[_\s-]?after|resets?\s+in)\s+(\d+)\s*(second|minute|hour|day)s?/i);
53487
- if (relative) {
53488
- const value = parseInt(relative[1], 10);
53489
- const unit = relative[2].toLowerCase();
53490
- const unitMs = {
53491
- second: 1000,
53492
- minute: 60000,
53493
- hour: 3600000,
53494
- day: 86400000
53495
- };
53496
- return now + value * unitMs[unit];
53497
- }
53498
- const unix = text.match(/\breset(?:_at)?\b\s*["']?\s*[:=]\s*(\d{10,13})/);
53499
- if (unix) {
53500
- const raw = parseInt(unix[1], 10);
53501
- return unix[1].length === 13 ? raw : raw * 1000;
53502
- }
53503
- const clock = text.match(/resets?\s+at\s+(\d{1,2}):(\d{2})\s*(utc|gmt)?/i);
53504
- if (clock) {
53505
- const hh = parseInt(clock[1], 10);
53506
- const mm = parseInt(clock[2], 10);
53507
- if (hh < 24 && mm < 60) {
53508
- const reference = new Date(now);
53509
- const target = new Date(Date.UTC(reference.getUTCFullYear(), reference.getUTCMonth(), reference.getUTCDate(), hh, mm)).getTime();
53510
- return target > now ? target : target + 86400000;
53511
- }
53512
- }
53513
- return;
53514
- }
53515
- function parseRateLimitEvent(event, now = Date.now()) {
53516
- const info = event?.rate_limit_info;
53517
- if (!info || typeof info !== "object")
53518
- return { detected: false };
53519
- const { status, resetsAt } = info;
53520
- if (status !== "rejected")
53521
- return { detected: false };
53522
- const PAST_SKEW_TOLERANCE_MS = 2 * 60000;
53523
- let resetAtEpochMs;
53524
- if (typeof resetsAt === "number") {
53525
- const ms = resetsAt * 1000;
53526
- if (ms > now && ms - now < 8 * 86400000) {
53527
- resetAtEpochMs = ms;
53528
- } else if (ms <= now && now - ms < PAST_SKEW_TOLERANCE_MS) {
53529
- resetAtEpochMs = now + 60000;
53530
- }
53531
- }
53532
- return { detected: true, matched: `rate_limit_event status=${status}`, resetAtEpochMs };
53533
- }
53534
-
53535
- // src/claude/cli.ts
53536
- var log11 = createLogger("claude");
53537
- function cleanupBrowserBridgeSockets() {
53538
- try {
53539
- const tempDir = tmpdir2();
53540
- const files = readdirSync(tempDir);
53541
- for (const file2 of files) {
53542
- if (file2.startsWith("claude-mcp-browser-bridge-")) {
53543
- const filePath = join6(tempDir, file2);
53544
- try {
53545
- const stats = statSync(filePath);
53546
- if (stats.isSocket()) {
53547
- unlinkSync(filePath);
53548
- log11.debug(`Removed stale browser bridge socket: ${file2}`);
53549
- }
53550
- } catch {}
53551
- }
53552
- }
53553
- } catch (err) {
53554
- log11.debug(`Browser bridge cleanup failed: ${err}`);
53555
- }
53556
- }
53557
- function buildClaudeChildEnv(parentEnv, account, opts) {
53558
- const env = { ...parentEnv };
53559
- if (env.MCP_CONNECTION_NONBLOCKING === undefined) {
53560
- env.MCP_CONNECTION_NONBLOCKING = "true";
53561
- }
53562
- if (env.ENABLE_PROMPT_CACHING_1H === undefined) {
53563
- env.ENABLE_PROMPT_CACHING_1H = "true";
53564
- }
53565
- if (opts?.disableAutoMemory) {
53566
- env.CLAUDE_CODE_DISABLE_AUTO_MEMORY = "1";
53567
- }
53568
- if (opts?.decisionBridge && env.MCP_TOOL_TIMEOUT === undefined) {
53569
- env.MCP_TOOL_TIMEOUT = "3600000";
53570
- }
53571
- if (account?.home) {
53572
- env.HOME = account.home;
53573
- env.USERPROFILE = account.home;
53574
- delete env.ANTHROPIC_API_KEY;
53575
- delete env.CLAUDE_CODE_OAUTH_TOKEN;
53576
- delete env.ANTHROPIC_AUTH_TOKEN;
53577
- delete env.CLAUDE_CONFIG_DIR;
53578
- delete env.CLAUDE_SECURESTORAGE_CONFIG_DIR;
53579
- } else if (account?.apiKey) {
53580
- env.ANTHROPIC_API_KEY = account.apiKey;
53581
- delete env.CLAUDE_CODE_OAUTH_TOKEN;
53582
- delete env.ANTHROPIC_AUTH_TOKEN;
53583
- }
53584
- return env;
53585
- }
53586
- function buildInlineSettings(statusLineCommand, memory) {
53587
- const settings = {};
53588
- if (statusLineCommand) {
53589
- settings.statusLine = {
53590
- type: "command",
53591
- command: statusLineCommand,
53592
- padding: 0
53593
- };
53594
- }
53595
- if (memory) {
53596
- settings.autoMemoryEnabled = true;
53597
- settings.autoMemoryDirectory = memory.autoMemoryDir;
53598
- }
53599
- return Object.keys(settings).length > 0 ? settings : null;
53600
- }
53601
- function runtimeForScriptPath(scriptPath) {
53602
- return scriptPath.endsWith(".ts") ? process.execPath : "node";
53603
- }
53604
- function isErrorResultEvent(event) {
53605
- const ev = event;
53606
- if (typeof ev.subtype === "string" && ev.subtype.startsWith("error"))
53607
- return true;
53608
- if (ev.is_error === true)
53609
- return true;
53610
- return false;
53611
- }
53612
- function materializeMcpConfig(config2, sessionId, opts = {}) {
53613
- if (opts.inline) {
53614
- return { mode: "inline", value: JSON.stringify(config2) };
53615
- }
53616
- const dir = opts.tmpDirOverride ?? tmpdir2();
53617
- const path = join6(dir, `claude-threads-mcp-${sessionId ?? process.pid}-${Date.now()}.json`);
53618
- writeFileSync2(path, JSON.stringify(config2), { mode: 384 });
53619
- return { mode: "file", path };
53620
- }
53621
- function buildPermissionArgs(opts) {
53622
- const args = [];
53623
- if (opts.permissionMode === "bypass" && !opts.platformConfig) {
53624
- args.push("--dangerously-skip-permissions");
53625
- return { args, tempFile: null };
53626
- }
53627
- if (!opts.platformConfig) {
53628
- throw new Error(`platformConfig is required when permissionMode is '${opts.permissionMode}'`);
53629
- }
53630
- const mcpEnv = {
53631
- PLATFORM_TYPE: opts.platformConfig.type,
53632
- PLATFORM_URL: opts.platformConfig.url,
53633
- PLATFORM_TOKEN: opts.platformConfig.token,
53634
- PLATFORM_CHANNEL_ID: opts.platformConfig.channelId,
53635
- PLATFORM_THREAD_ID: opts.threadId || "",
53636
- ALLOWED_USERS: opts.platformConfig.allowedUsers.join(","),
53637
- DEBUG: opts.debug ? "1" : "",
53638
- PERMISSION_TIMEOUT_MS: String(opts.permissionTimeoutMs),
53639
- SESSION_OWNER_USERNAME: opts.sessionOwnerUsername || ""
53640
- };
53641
- if (opts.decisionBridgePath) {
53642
- mcpEnv.DECISION_BRIDGE_PATH = opts.decisionBridgePath;
53643
- if (process.env.DECISION_BRIDGE_TIMEOUT_MS) {
53644
- mcpEnv.DECISION_BRIDGE_TIMEOUT_MS = process.env.DECISION_BRIDGE_TIMEOUT_MS;
53645
- }
53646
- const features = opts.agentFeatures;
53647
- if (features) {
53648
- if (features.memoryChannel)
53649
- mcpEnv[AGENT_FEATURES_ENV.MEMORY_CHANNEL_ENABLED] = "1";
53650
- if (features.routines)
53651
- mcpEnv[AGENT_FEATURES_ENV.ROUTINES_ENABLED] = "1";
53652
- if (features.watches)
53653
- mcpEnv[AGENT_FEATURES_ENV.WATCHES_ENABLED] = "1";
53654
- if (features.unattended)
53655
- mcpEnv[AGENT_FEATURES_ENV.UNATTENDED] = "1";
53656
- if (features.dcm)
53657
- mcpEnv[AGENT_FEATURES_ENV.DCM] = "1";
53658
- }
53659
- }
53660
- if (opts.platformConfig.appToken) {
53661
- mcpEnv.PLATFORM_APP_TOKEN = opts.platformConfig.appToken;
53662
- }
53663
- if (opts.workingDir) {
53664
- mcpEnv[OUTBOUND_ENV.SESSION_WORKING_DIR] = opts.workingDir;
53665
- }
53666
- if (opts.uploadDir) {
53667
- mcpEnv[OUTBOUND_ENV.SESSION_UPLOAD_DIR] = opts.uploadDir;
53668
- }
53669
- if (opts.outboundFiles?.enabled === false) {
53670
- mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_ENABLED] = "0";
53671
- }
53672
- if (typeof opts.outboundFiles?.maxBytes === "number" && Number.isFinite(opts.outboundFiles.maxBytes) && opts.outboundFiles.maxBytes > 0) {
53673
- mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_MAX_BYTES] = String(opts.outboundFiles.maxBytes);
53674
- }
53675
- const mcpConfig = {
53676
- mcpServers: {
53677
- "claude-threads-mcp": {
53678
- type: "stdio",
53679
- command: runtimeForScriptPath(opts.mcpServerPath),
53680
- args: [opts.mcpServerPath],
53681
- env: mcpEnv
53682
- }
53683
- }
53684
- };
53685
- const materialized = materializeMcpConfig(mcpConfig, opts.sessionId, { inline: opts.inline });
53686
- let tempFile = null;
53687
- if (materialized.mode === "file") {
53688
- tempFile = materialized.path;
53689
- args.push("--mcp-config", materialized.path);
53690
- } else {
53691
- args.push("--mcp-config", materialized.value);
53692
- }
53693
- if (opts.permissionMode === "bypass") {
53694
- args.push("--dangerously-skip-permissions");
53695
- } else {
53696
- args.push("--permission-prompt-tool", "mcp__claude-threads-mcp__permission_prompt");
53697
- if (opts.permissionMode === "auto") {
53698
- args.push("--permission-mode", "auto");
53699
- }
53700
- }
53701
- return { args, tempFile };
53702
- }
53703
- var STDERR_PER_INSTANCE_CAP = 10240;
53704
- var STDERR_AGGREGATE_SOFT_CAP = 10 * 1024 * 1024;
53705
- var totalStderrBytes = 0;
53706
-
53707
- class ClaudeCli extends EventEmitter2 {
53708
- process = null;
53709
- options;
53710
- buffer = "";
53711
- debug = process.env.DEBUG === "1" || process.argv.includes("--debug");
53712
- statusFilePath = null;
53713
- lastStatusData = null;
53714
- stderrBuffer = "";
53715
- mcpConfigTempFile = null;
53716
- lastEmittedRateLimitDeadline = 0;
53717
- lastEmittedHitHadExplicitReset = false;
53718
- log;
53719
- constructor(options) {
53720
- super();
53721
- this.options = options;
53722
- this.log = options.logSessionId ? createLogger("claude").forSession(options.logSessionId) : createLogger("claude");
53723
- }
53724
- getStatusFilePath() {
53725
- return this.statusFilePath;
53726
- }
53727
- getStatusData() {
53728
- if (!this.statusFilePath)
53729
- return null;
53730
- try {
53731
- if (existsSync4(this.statusFilePath)) {
53732
- const data = readFileSync3(this.statusFilePath, "utf8");
53733
- this.lastStatusData = JSON.parse(data);
53734
- }
53735
- } catch (err) {
53736
- this.log.debug(`Failed to read status file: ${err}`);
53737
- }
53738
- return this.lastStatusData;
53739
- }
53740
- startStatusWatch() {
53741
- if (!this.statusFilePath) {
53742
- this.log.debug("No status file path, skipping status watch");
53743
- return;
53744
- }
53745
- this.log.debug(`Starting status watch: ${this.statusFilePath}`);
53746
- const checkStatus = () => {
53747
- const data = this.getStatusData();
53748
- if (data && data.timestamp !== this.lastStatusData?.timestamp) {
53749
- this.lastStatusData = data;
53750
- this.emit("status", data);
53751
- }
53752
- };
53753
- watchFile(this.statusFilePath, { interval: 1000 }, checkStatus);
53754
- }
53755
- stopStatusWatch() {
53756
- if (this.statusFilePath) {
53757
- unwatchFile(this.statusFilePath);
53758
- try {
53759
- if (existsSync4(this.statusFilePath)) {
53760
- unlinkSync(this.statusFilePath);
53761
- }
53762
- } catch {}
53763
- }
53764
- }
53765
- start() {
53766
- if (this.process)
53767
- throw new Error("Already running");
53768
- totalStderrBytes -= this.stderrBuffer.length;
53769
- this.stderrBuffer = "";
53770
- this.lastEmittedRateLimitDeadline = 0;
53771
- this.lastEmittedHitHadExplicitReset = false;
53772
- cleanupBrowserBridgeSockets();
53773
- const claudePath = getClaudePath();
53774
- const args = [
53775
- "--input-format",
53776
- "stream-json",
53777
- "--output-format",
53778
- "stream-json",
53779
- "--verbose"
53780
- ];
53781
- if (this.options.sessionId) {
53782
- if (this.options.resume) {
53783
- args.push("--resume", this.options.sessionId);
53784
- } else {
53785
- args.push("--session-id", this.options.sessionId);
53786
- }
53787
- }
53788
- const permissionMode = this.options.permissionMode ?? "default";
53789
- const permResult = buildPermissionArgs({
53790
- permissionMode,
53791
- mcpServerPath: this.getMcpServerPath(),
53792
- platformConfig: this.options.platformConfig,
53793
- threadId: this.options.threadId,
53794
- sessionId: this.options.sessionId,
53795
- permissionTimeoutMs: this.options.permissionTimeoutMs ?? 120000,
53796
- debug: this.debug,
53797
- workingDir: this.options.workingDir,
53798
- uploadDir: this.options.uploadDir,
53799
- outboundFiles: this.options.outboundFiles,
53800
- sessionOwnerUsername: this.options.sessionOwnerUsername,
53801
- decisionBridgePath: this.options.decisionBridgePath,
53802
- agentFeatures: this.options.agentFeatures
53803
- });
53804
- args.push(...permResult.args);
53805
- this.mcpConfigTempFile = permResult.tempFile;
53806
- if (this.options.chrome) {
53807
- args.push("--chrome");
53808
- }
53809
- if (this.options.appendSystemPrompt) {
53810
- args.push("--append-system-prompt", this.options.appendSystemPrompt);
53811
- }
53812
- let statusLineCommand;
53813
- if (this.options.sessionId) {
53814
- this.statusFilePath = join6(tmpdir2(), `claude-threads-status-${this.options.sessionId}.json`);
53815
- const statusLineWriterPath = this.getStatusLineWriterPath();
53816
- const runtime = runtimeForScriptPath(statusLineWriterPath);
53817
- statusLineCommand = `${runtime} ${statusLineWriterPath} ${this.options.sessionId}`;
53818
- }
53819
- const settings = buildInlineSettings(statusLineCommand, this.options.memory);
53820
- if (settings) {
53821
- args.push("--settings", JSON.stringify(settings));
53822
- }
53823
- this.log.debug(`Starting: ${claudePath} ${args.slice(0, 5).join(" ")}...`);
53824
- const childEnv = this.buildChildEnv();
53825
- if (this.options.account) {
53826
- this.log.debug(`Spawning under Claude account "${this.options.account.id}"`);
53827
- }
53828
- this.process = crossSpawn(claudePath, args, {
53829
- cwd: this.options.workingDir,
53830
- env: childEnv,
53831
- stdio: ["pipe", "pipe", "pipe"]
53832
- });
53833
- this.log.debug(`Claude process spawned: pid=${this.process.pid}`);
53834
- this.process.stdout?.on("data", (chunk) => {
53835
- this.parseOutput(chunk.toString());
53836
- });
53837
- this.process.stderr?.on("data", (chunk) => {
53838
- const text = chunk.toString();
53839
- const before = this.stderrBuffer.length;
53840
- this.stderrBuffer += text;
53841
- const cap = totalStderrBytes > STDERR_AGGREGATE_SOFT_CAP ? 1024 : STDERR_PER_INSTANCE_CAP;
53842
- if (this.stderrBuffer.length > cap) {
53843
- this.stderrBuffer = this.stderrBuffer.slice(-cap);
53844
- }
53845
- totalStderrBytes += this.stderrBuffer.length - before;
53846
- this.log.debug(`stderr: ${text.trim()}`);
53847
- if (process.env.INTEGRATION_TEST === "1") {
53848
- process.stderr.write(text);
53849
- }
53850
- this.maybeEmitRateLimit(text);
53851
- });
53852
- this.process.on("error", (err) => {
53853
- this.log.error(`Claude error: ${err}`);
53854
- this.emit("error", err);
53855
- });
53856
- this.process.on("exit", (code) => {
53857
- this.log.debug(`Exited ${code}`);
53858
- this.process = null;
53859
- this.buffer = "";
53860
- totalStderrBytes -= this.stderrBuffer.length;
53861
- if (this.mcpConfigTempFile) {
53862
- const path = this.mcpConfigTempFile;
53863
- this.mcpConfigTempFile = null;
53864
- try {
53865
- unlinkSync(path);
53866
- } catch {}
53867
- }
53868
- this.emit("exit", code);
53869
- });
53870
- }
53871
- sendMessage(content) {
53872
- if (!this.process?.stdin)
53873
- throw new Error("Not running");
53874
- const msg = JSON.stringify({
53875
- type: "user",
53876
- message: { role: "user", content }
53877
- }) + `
53878
- `;
53879
- const preview = content.substring(0, 50);
53880
- this.log.debug(`Sending: ${preview}...`);
53881
- if (process.env.INTEGRATION_TEST === "1") {
53882
- const stack = new Error().stack?.split(`
53883
- `).slice(2, 6).join(" > ").replace(/\s+at\s+/g, " < ") ?? "?";
53884
- process.stderr.write(`[claude-cli sendMessage pid=${this.process.pid}] ${preview} | ${stack}
53885
- `);
53886
- }
53887
- this.process.stdin.write(msg);
53888
- }
53889
- sendToolResult(toolUseId, content) {
53890
- if (!this.process?.stdin)
53891
- throw new Error("Not running");
53892
- const msg = JSON.stringify({
53893
- type: "user",
53894
- message: {
53895
- role: "user",
53896
- content: [{
53897
- type: "tool_result",
53898
- tool_use_id: toolUseId,
53899
- content: typeof content === "string" ? content : JSON.stringify(content)
53900
- }]
53901
- }
53902
- }) + `
53903
- `;
53904
- this.log.debug(`Sending tool_result for ${toolUseId}`);
53905
- this.process.stdin.write(msg);
53906
- }
53907
- parseOutput(data) {
53908
- this.buffer += data;
53909
- const lines = this.buffer.split(`
53910
- `);
53911
- this.buffer = lines.pop() || "";
53912
- for (const line of lines) {
53913
- const trimmed = line.trim();
53914
- if (!trimmed)
53915
- continue;
53916
- let event;
53917
- try {
53918
- event = JSON.parse(trimmed);
53919
- } catch {
53920
- continue;
53921
- }
53922
- try {
53923
- this.emit("event", event);
53924
- } catch (err) {
53925
- this.log.error(`'event' listener threw while handling a ${event.type} event: ${err}`);
53926
- }
53927
- if (event.type === "result" && isErrorResultEvent(event)) {
53928
- this.maybeEmitRateLimit(trimmed);
53929
- }
53930
- if (event.type === "rate_limit_event") {
53931
- this.maybeEmitRateLimitHit(parseRateLimitEvent(event));
53932
- }
53933
- }
53934
- }
53935
- maybeEmitRateLimit(text) {
53936
- this.maybeEmitRateLimitHit(detectRateLimit(text));
53937
- }
53938
- maybeEmitRateLimitHit(hit) {
53939
- if (!hit.detected)
53940
- return;
53941
- if (!hit.resetAtEpochMs && this.lastEmittedHitHadExplicitReset && this.lastEmittedRateLimitDeadline > Date.now()) {
53942
- return;
53943
- }
53944
- const newDeadline = cooldownDeadline(hit);
53945
- const MIN_ADVANCE_MS = 60000;
53946
- if (newDeadline - this.lastEmittedRateLimitDeadline < MIN_ADVANCE_MS) {
53947
- if (hit.resetAtEpochMs !== undefined) {
53948
- this.lastEmittedHitHadExplicitReset = true;
53949
- }
53950
- return;
53951
- }
53952
- this.lastEmittedRateLimitDeadline = newDeadline;
53953
- this.lastEmittedHitHadExplicitReset = hit.resetAtEpochMs !== undefined;
53954
- this.log.warn(`Rate limit detected: ${hit.matched ?? "(no match text)"}`);
53955
- this.emit("rate-limit", hit);
53956
- }
53957
- isRunning() {
53958
- return this.process !== null;
53959
- }
53960
- getLastStderr() {
53961
- return this.stderrBuffer;
53962
- }
53963
- isPermanentFailure() {
53964
- const stderr = this.stderrBuffer;
53965
- if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
53966
- return true;
53967
- }
53968
- if (stderr.includes("No conversation found with session ID")) {
53969
- return true;
53970
- }
53971
- return false;
53972
- }
53973
- getPermanentFailureReason() {
53974
- const stderr = this.stderrBuffer;
53975
- if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
53976
- return "Claude browser bridge state from a previous session is no longer accessible. This typically happens when a session with Chrome integration is resumed after a restart.";
53977
- }
53978
- if (stderr.includes("No conversation found with session ID")) {
53979
- return "The conversation history for this session no longer exists. This can happen if Claude's history was cleared or if the session was created on a different machine.";
53980
- }
53981
- return null;
53982
- }
53983
- kill() {
53984
- this.stopStatusWatch();
53985
- if (!this.process) {
53986
- this.log.debug("Kill called but process not running");
53987
- return Promise.resolve();
53988
- }
53989
- const proc = this.process;
53990
- const pid = proc.pid;
53991
- this.process = null;
53992
- this.log.debug(`Killing Claude process (pid=${pid})`);
53993
- if (process.env.INTEGRATION_TEST === "1") {
53994
- const stack = new Error().stack?.split(`
53995
- `).slice(2, 7).join(" > ").replace(/\s+at\s+/g, " < ") ?? "?";
53996
- process.stderr.write(`[claude-cli kill pid=${pid}] | ${stack}
53997
- `);
53998
- }
53999
- return new Promise((resolve5) => {
54000
- this.log.debug("Sending first SIGINT");
54001
- proc.kill("SIGINT");
54002
- const secondSigint = setTimeout(() => {
54003
- try {
54004
- this.log.debug("Sending second SIGINT");
54005
- proc.kill("SIGINT");
54006
- } catch {}
54007
- }, 100);
54008
- const forceKillTimeout = setTimeout(() => {
54009
- try {
54010
- this.log.debug("Sending SIGTERM (force kill)");
54011
- proc.kill("SIGTERM");
54012
- } catch {}
54013
- }, 2000);
54014
- const settle = (reason) => {
54015
- this.log.debug(`Claude process gone (${reason})`);
54016
- clearTimeout(secondSigint);
54017
- clearTimeout(forceKillTimeout);
54018
- clearTimeout(lastResort);
54019
- resolve5();
54020
- };
54021
- const lastResort = setTimeout(() => {
54022
- try {
54023
- this.log.warn("Claude process did not exit after SIGTERM — sending SIGKILL");
54024
- proc.kill("SIGKILL");
54025
- } catch {}
54026
- settle("kill timeout");
54027
- }, 5000);
54028
- proc.once("close", (code) => settle(`closed, code=${code}`));
54029
- proc.once("error", () => settle("spawn error"));
54030
- });
54031
- }
54032
- interrupt() {
54033
- if (!this.process) {
54034
- this.log.debug("Interrupt called but process not running");
54035
- return false;
54036
- }
54037
- this.log.debug(`Interrupting Claude process (pid=${this.process.pid})`);
54038
- this.process.kill("SIGINT");
54039
- return true;
54040
- }
54041
- buildChildEnv() {
54042
- return buildClaudeChildEnv(process.env, this.options.account, {
54043
- decisionBridge: this.options.decisionBridgePath !== undefined,
54044
- disableAutoMemory: this.options.memory === null
54045
- });
54046
- }
54047
- getMcpServerPath() {
54048
- const __filename2 = fileURLToPath3(import.meta.url);
54049
- const __dirname4 = dirname5(__filename2);
54050
- const bundledPath = resolve4(__dirname4, "mcp", "mcp-server.js");
54051
- if (existsSync4(bundledPath)) {
54052
- return bundledPath;
54053
- }
54054
- const sourceLayoutPath = resolve4(__dirname4, "..", "mcp", "mcp-server.js");
54055
- if (existsSync4(sourceLayoutPath)) {
54056
- return sourceLayoutPath;
54057
- }
54058
- const tsPath = resolve4(__dirname4, "..", "mcp", "mcp-server.ts");
54059
- if (existsSync4(tsPath)) {
54060
- return tsPath;
54061
- }
54062
- return sourceLayoutPath;
54063
- }
54064
- getStatusLineWriterPath() {
54065
- const __filename2 = fileURLToPath3(import.meta.url);
54066
- const __dirname4 = dirname5(__filename2);
54067
- const bundledPath = resolve4(__dirname4, "statusline", "writer.js");
54068
- if (existsSync4(bundledPath)) {
54069
- return bundledPath;
54070
- }
54071
- const sourceLayoutPath = resolve4(__dirname4, "..", "statusline", "writer.js");
54072
- if (existsSync4(sourceLayoutPath)) {
54073
- return sourceLayoutPath;
54074
- }
54075
- const tsPath = resolve4(__dirname4, "..", "statusline", "writer.ts");
54076
- if (existsSync4(tsPath)) {
54077
- return tsPath;
54078
- }
54079
- return sourceLayoutPath;
54080
- }
54081
- }
54128
+ // src/session/lifecycle.ts
54129
+ init_cli();
54130
+ init_rate_limit_detector();
54082
54131
 
54083
54132
  // src/commands/registry.ts
54084
54133
  var COMMAND_REGISTRY = [
@@ -55474,6 +55523,9 @@ var sessionLog7 = createSessionLog(log30);
55474
55523
  var _inFlightSessionStarts = new Map;
55475
55524
  var CHAT_PLATFORM_PROMPT = generateChatPlatformPrompt();
55476
55525
 
55526
+ // src/operations/commands/handler.ts
55527
+ init_cli();
55528
+
55477
55529
  // src/update-notifier.ts
55478
55530
  var import_semver2 = __toESM(require_semver2(), 1);
55479
55531
 
@@ -55506,6 +55558,7 @@ var execAsync2 = promisify2(exec2);
55506
55558
  var log34 = createLogger("branch");
55507
55559
 
55508
55560
  // src/operations/worktree/handler.ts
55561
+ init_cli();
55509
55562
  init_logger();
55510
55563
  var log35 = createLogger("worktree");
55511
55564
  var sessionLog10 = createSessionLog(log35);
@@ -56692,6 +56745,10 @@ async function validateOutboundPath(inputPath, opts) {
56692
56745
  };
56693
56746
  }
56694
56747
 
56748
+ // src/mcp/mcp-server.ts
56749
+ init_agent_features_env();
56750
+ init_outbound_env();
56751
+
56695
56752
  // src/platform/permalink-shared.ts
56696
56753
  var DEFAULT_THREAD_LIMIT = 20;
56697
56754
  var MAX_THREAD_LIMIT = 50;