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.
- package/CHANGELOG.md +5 -0
- package/dist/index.js +1296 -971
- package/dist/mcp/mcp-server.js +712 -655
- package/docs/CONFIGURATION.md +52 -0
- package/package.json +1 -1
package/dist/mcp/mcp-server.js
CHANGED
|
@@ -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/
|
|
53428
|
-
|
|
53429
|
-
|
|
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;
|