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/index.js
CHANGED
|
@@ -2196,6 +2196,231 @@ var init_logger = __esm(() => {
|
|
|
2196
2196
|
wsLogger = createLogger("ws", false);
|
|
2197
2197
|
});
|
|
2198
2198
|
|
|
2199
|
+
// src/config/types.ts
|
|
2200
|
+
function isOverheadVisibility(value) {
|
|
2201
|
+
return typeof value === "string" && OVERHEAD_VISIBILITY_VALUES.includes(value);
|
|
2202
|
+
}
|
|
2203
|
+
function resolveOverheadVisibility(value, fieldPath) {
|
|
2204
|
+
if (value === undefined || value === null)
|
|
2205
|
+
return DEFAULT_OVERHEAD_VISIBILITY;
|
|
2206
|
+
if (isOverheadVisibility(value))
|
|
2207
|
+
return value;
|
|
2208
|
+
throw new Error(`Invalid ${fieldPath}: expected one of ${OVERHEAD_VISIBILITY_VALUES.join(", ")}, got ${JSON.stringify(value)}`);
|
|
2209
|
+
}
|
|
2210
|
+
function resolveMemoryConfig(value, fieldPath) {
|
|
2211
|
+
if (value === undefined || value === null || value === true)
|
|
2212
|
+
return DEFAULT_MEMORY_CONFIG;
|
|
2213
|
+
if (value === false)
|
|
2214
|
+
return MEMORY_DISABLED;
|
|
2215
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
2216
|
+
const obj = value;
|
|
2217
|
+
const bool2 = (v, name, dflt) => resolveBooleanFeature(v, `${fieldPath ?? "memory"}.${name}`, { default: dflt, verb: `using default (${dflt})` });
|
|
2218
|
+
const enabled = bool2(obj.enabled, "enabled", true);
|
|
2219
|
+
if (!enabled)
|
|
2220
|
+
return MEMORY_DISABLED;
|
|
2221
|
+
return {
|
|
2222
|
+
enabled: true,
|
|
2223
|
+
repoLayer: bool2(obj.repoLayer, "repoLayer", true),
|
|
2224
|
+
channelLayer: bool2(obj.channelLayer, "channelLayer", true),
|
|
2225
|
+
distillation: bool2(obj.distillation, "distillation", true)
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
console.warn(`Invalid ${fieldPath ?? "memory"} config: expected boolean or {enabled, repoLayer, channelLayer, distillation}, got ${JSON.stringify(value)} — using defaults`);
|
|
2229
|
+
return DEFAULT_MEMORY_CONFIG;
|
|
2230
|
+
}
|
|
2231
|
+
function resolveRoutinesEnabled(value, fieldPath) {
|
|
2232
|
+
return resolveBooleanFeature(value, fieldPath ?? "routines", { default: true, verb: "routines stay enabled" });
|
|
2233
|
+
}
|
|
2234
|
+
function resolveTranscriptionEnabled(value, fieldPath) {
|
|
2235
|
+
if (value === undefined || value === null)
|
|
2236
|
+
return true;
|
|
2237
|
+
if (typeof value === "boolean")
|
|
2238
|
+
return value;
|
|
2239
|
+
console.warn(`Invalid ${fieldPath ?? "transcription"}: ${JSON.stringify(value)} — expected true or false. ` + `Transcription is DISABLED for this platform: a value we cannot read is not consent to upload its audio.`);
|
|
2240
|
+
return false;
|
|
2241
|
+
}
|
|
2242
|
+
function resolveBooleanFeature(value, fieldPath, opts) {
|
|
2243
|
+
if (value === true || value === false)
|
|
2244
|
+
return value;
|
|
2245
|
+
if (value === undefined || value === null)
|
|
2246
|
+
return opts.default;
|
|
2247
|
+
console.warn(`Invalid ${fieldPath} config: expected boolean, got ${JSON.stringify(value)} — ${opts.verb}`);
|
|
2248
|
+
return opts.default;
|
|
2249
|
+
}
|
|
2250
|
+
function resolveWatchesEnabled(value, fieldPath) {
|
|
2251
|
+
return resolveBooleanFeature(value, fieldPath ?? "watches", { default: true, verb: "watches stay enabled" });
|
|
2252
|
+
}
|
|
2253
|
+
function resolveAuditLogEnabled(value, fieldPath) {
|
|
2254
|
+
return resolveBooleanFeature(value, fieldPath ?? "auditLog", { default: false, verb: "audit log stays off" });
|
|
2255
|
+
}
|
|
2256
|
+
function isRemoteMcpServer(server) {
|
|
2257
|
+
return server.type === "http" || server.type === "sse";
|
|
2258
|
+
}
|
|
2259
|
+
function resolveStrictMcpConfig(value, fieldPath) {
|
|
2260
|
+
return resolveBooleanFeature(value, fieldPath ?? "strictMcpConfig", {
|
|
2261
|
+
default: false,
|
|
2262
|
+
verb: "the operator's MCP sources stay available"
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
function resolveClaudeAiConnectors(value, fieldPath) {
|
|
2266
|
+
return resolveBooleanFeature(value, fieldPath ?? "claudeAiConnectors", {
|
|
2267
|
+
default: false,
|
|
2268
|
+
verb: "claude.ai connectors stay disabled"
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
function isStringRecord(value) {
|
|
2272
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((v) => typeof v === "string");
|
|
2273
|
+
}
|
|
2274
|
+
function validateMcpServers(value, fieldPath) {
|
|
2275
|
+
if (value === undefined || value === null)
|
|
2276
|
+
return {};
|
|
2277
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
2278
|
+
throw new Error(`Invalid ${fieldPath}: expected a map of server name → {command, args?, env?} or {type: http|sse, url, headers?}`);
|
|
2279
|
+
}
|
|
2280
|
+
const out = {};
|
|
2281
|
+
for (const [name, raw] of Object.entries(value)) {
|
|
2282
|
+
const path = `${fieldPath}.${name}`;
|
|
2283
|
+
if (name === BOT_MCP_SERVER_NAME) {
|
|
2284
|
+
throw new Error(`Invalid ${path}: "${BOT_MCP_SERVER_NAME}" is the bot's own server and cannot be redefined`);
|
|
2285
|
+
}
|
|
2286
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(name)) {
|
|
2287
|
+
throw new Error(`Invalid ${path}: server names may contain letters, digits, "_" and "-" only`);
|
|
2288
|
+
}
|
|
2289
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2290
|
+
throw new Error(`Invalid ${path}: expected an object`);
|
|
2291
|
+
}
|
|
2292
|
+
const s = Object.fromEntries(Object.entries(raw).filter(([, v]) => v !== null && v !== undefined));
|
|
2293
|
+
if (typeof s.command === "string" && typeof s.url === "string") {
|
|
2294
|
+
throw new Error(`Invalid ${path}: has both command (stdio) and url (http/sse); keep one`);
|
|
2295
|
+
}
|
|
2296
|
+
const type2 = s.type ?? (typeof s.url === "string" ? "http" : "stdio");
|
|
2297
|
+
if (type2 === "http" || type2 === "sse") {
|
|
2298
|
+
const unknown = Object.keys(s).filter((k) => !REMOTE_KEYS.has(k));
|
|
2299
|
+
if (unknown.length > 0) {
|
|
2300
|
+
throw new Error(`Invalid ${path}: unknown key(s) ${unknown.join(", ")}; a ${type2} server takes type, url, headers`);
|
|
2301
|
+
}
|
|
2302
|
+
if (typeof s.url !== "string" || s.url.length === 0) {
|
|
2303
|
+
throw new Error(`Invalid ${path}: a ${type2} server needs a url`);
|
|
2304
|
+
}
|
|
2305
|
+
if (s.headers !== undefined && !isStringRecord(s.headers)) {
|
|
2306
|
+
throw new Error(`Invalid ${path}.headers: expected a map of strings`);
|
|
2307
|
+
}
|
|
2308
|
+
out[name] = { type: type2, url: s.url, ...s.headers ? { headers: s.headers } : {} };
|
|
2309
|
+
} else if (type2 === "stdio") {
|
|
2310
|
+
const unknown = Object.keys(s).filter((k) => !STDIO_KEYS.has(k));
|
|
2311
|
+
if (unknown.length > 0) {
|
|
2312
|
+
throw new Error(`Invalid ${path}: unknown key(s) ${unknown.join(", ")}; a stdio server takes type, command, args, env`);
|
|
2313
|
+
}
|
|
2314
|
+
if (typeof s.command !== "string" || s.command.length === 0) {
|
|
2315
|
+
throw new Error(`Invalid ${path}: a stdio server needs a command (or set type: http|sse with a url)`);
|
|
2316
|
+
}
|
|
2317
|
+
if (s.args !== undefined && !(Array.isArray(s.args) && s.args.every((a) => typeof a === "string"))) {
|
|
2318
|
+
throw new Error(`Invalid ${path}.args: expected a list of strings`);
|
|
2319
|
+
}
|
|
2320
|
+
if (s.env !== undefined && !isStringRecord(s.env)) {
|
|
2321
|
+
throw new Error(`Invalid ${path}.env: expected a map of strings`);
|
|
2322
|
+
}
|
|
2323
|
+
out[name] = { type: "stdio", command: s.command, args: s.args ?? [], env: s.env ?? {} };
|
|
2324
|
+
} else {
|
|
2325
|
+
throw new Error(`Invalid ${path}.type: expected stdio, http or sse, got ${JSON.stringify(s.type)}`);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
return out;
|
|
2329
|
+
}
|
|
2330
|
+
function resolveMcpServers(global2, platform, fieldPath) {
|
|
2331
|
+
return { ...validateMcpServers(global2, "mcpServers"), ...validateMcpServers(platform, fieldPath) };
|
|
2332
|
+
}
|
|
2333
|
+
function resolveLimits(limits) {
|
|
2334
|
+
const envMaxSessions = process.env.MAX_SESSIONS ? parseInt(process.env.MAX_SESSIONS, 10) : undefined;
|
|
2335
|
+
const envSessionTimeout = process.env.SESSION_TIMEOUT_MS ? Math.round(parseInt(process.env.SESSION_TIMEOUT_MS, 10) / 60000) : undefined;
|
|
2336
|
+
return {
|
|
2337
|
+
maxSessions: limits?.maxSessions ?? envMaxSessions ?? LIMITS_DEFAULTS.maxSessions,
|
|
2338
|
+
sessionTimeoutMinutes: limits?.sessionTimeoutMinutes ?? envSessionTimeout ?? LIMITS_DEFAULTS.sessionTimeoutMinutes,
|
|
2339
|
+
sessionWarningMinutes: limits?.sessionWarningMinutes ?? LIMITS_DEFAULTS.sessionWarningMinutes,
|
|
2340
|
+
cleanupIntervalMinutes: limits?.cleanupIntervalMinutes ?? LIMITS_DEFAULTS.cleanupIntervalMinutes,
|
|
2341
|
+
maxWorktreeAgeHours: limits?.maxWorktreeAgeHours ?? LIMITS_DEFAULTS.maxWorktreeAgeHours,
|
|
2342
|
+
cleanupWorktrees: limits?.cleanupWorktrees ?? LIMITS_DEFAULTS.cleanupWorktrees,
|
|
2343
|
+
permissionTimeoutSeconds: limits?.permissionTimeoutSeconds ?? LIMITS_DEFAULTS.permissionTimeoutSeconds,
|
|
2344
|
+
flushDelayMs: limits?.flushDelayMs ?? LIMITS_DEFAULTS.flushDelayMs,
|
|
2345
|
+
maxRoutines: limits?.maxRoutines ?? LIMITS_DEFAULTS.maxRoutines,
|
|
2346
|
+
maxWatches: limits?.maxWatches ?? LIMITS_DEFAULTS.maxWatches,
|
|
2347
|
+
watchCooldownMinutes: limits?.watchCooldownMinutes ?? LIMITS_DEFAULTS.watchCooldownMinutes,
|
|
2348
|
+
watchDailyCap: limits?.watchDailyCap ?? LIMITS_DEFAULTS.watchDailyCap
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
function resolvePermissionMode(opts) {
|
|
2352
|
+
if (opts.permissionMode)
|
|
2353
|
+
return opts.permissionMode;
|
|
2354
|
+
if (opts.skipPermissions === true)
|
|
2355
|
+
return "bypass";
|
|
2356
|
+
if (opts.skipPermissions === false)
|
|
2357
|
+
return "default";
|
|
2358
|
+
return "default";
|
|
2359
|
+
}
|
|
2360
|
+
function permissionModeDisplay(mode) {
|
|
2361
|
+
const info = MODE_INFO[mode];
|
|
2362
|
+
return { icon: info.icon, label: info.label, chip: `${info.icon} ${info.label}` };
|
|
2363
|
+
}
|
|
2364
|
+
function permissionModeDescription(mode) {
|
|
2365
|
+
return MODE_INFO[mode].description;
|
|
2366
|
+
}
|
|
2367
|
+
function effectivePermissionMode(input) {
|
|
2368
|
+
if (input.override)
|
|
2369
|
+
return input.override;
|
|
2370
|
+
if (input.sessionHasInteractiveOverride)
|
|
2371
|
+
return "default";
|
|
2372
|
+
return input.botWideMode;
|
|
2373
|
+
}
|
|
2374
|
+
var OVERHEAD_VISIBILITY_VALUES, DEFAULT_OVERHEAD_VISIBILITY = "full", DEFAULT_MEMORY_CONFIG, MEMORY_DISABLED, BOT_MCP_SERVER_NAME = "claude-threads-mcp", STDIO_KEYS, REMOTE_KEYS, LIMITS_DEFAULTS, MODE_INFO;
|
|
2375
|
+
var init_types = __esm(() => {
|
|
2376
|
+
OVERHEAD_VISIBILITY_VALUES = ["full", "minimal", "hidden"];
|
|
2377
|
+
DEFAULT_MEMORY_CONFIG = {
|
|
2378
|
+
enabled: true,
|
|
2379
|
+
repoLayer: true,
|
|
2380
|
+
channelLayer: true,
|
|
2381
|
+
distillation: true
|
|
2382
|
+
};
|
|
2383
|
+
MEMORY_DISABLED = {
|
|
2384
|
+
enabled: false,
|
|
2385
|
+
repoLayer: false,
|
|
2386
|
+
channelLayer: false,
|
|
2387
|
+
distillation: false
|
|
2388
|
+
};
|
|
2389
|
+
STDIO_KEYS = new Set(["type", "command", "args", "env"]);
|
|
2390
|
+
REMOTE_KEYS = new Set(["type", "url", "headers"]);
|
|
2391
|
+
LIMITS_DEFAULTS = {
|
|
2392
|
+
maxSessions: 5,
|
|
2393
|
+
sessionTimeoutMinutes: 30,
|
|
2394
|
+
sessionWarningMinutes: 5,
|
|
2395
|
+
cleanupIntervalMinutes: 60,
|
|
2396
|
+
maxWorktreeAgeHours: 24,
|
|
2397
|
+
cleanupWorktrees: true,
|
|
2398
|
+
permissionTimeoutSeconds: 120,
|
|
2399
|
+
flushDelayMs: 500,
|
|
2400
|
+
maxRoutines: 10,
|
|
2401
|
+
maxWatches: 10,
|
|
2402
|
+
watchCooldownMinutes: 5,
|
|
2403
|
+
watchDailyCap: 20
|
|
2404
|
+
};
|
|
2405
|
+
MODE_INFO = {
|
|
2406
|
+
default: {
|
|
2407
|
+
icon: "\uD83D\uDD10",
|
|
2408
|
+
label: "Default",
|
|
2409
|
+
description: "Every tool-use prompts for approval."
|
|
2410
|
+
},
|
|
2411
|
+
auto: {
|
|
2412
|
+
icon: "⚡",
|
|
2413
|
+
label: "Auto",
|
|
2414
|
+
description: "Claude classifier auto-approves low-risk tools; high-risk still prompts."
|
|
2415
|
+
},
|
|
2416
|
+
bypass: {
|
|
2417
|
+
icon: "⚠️",
|
|
2418
|
+
label: "Bypass",
|
|
2419
|
+
description: "No prompts — every tool-use is allowed."
|
|
2420
|
+
}
|
|
2421
|
+
};
|
|
2422
|
+
});
|
|
2423
|
+
|
|
2199
2424
|
// src/utils/spawn.ts
|
|
2200
2425
|
import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "child_process";
|
|
2201
2426
|
function addWindowsShell(options) {
|
|
@@ -4058,7 +4283,7 @@ var require_semver2 = __commonJS((exports, module) => {
|
|
|
4058
4283
|
|
|
4059
4284
|
// src/claude/version-check.ts
|
|
4060
4285
|
import { execSync } from "child_process";
|
|
4061
|
-
import { existsSync as
|
|
4286
|
+
import { existsSync as existsSync3 } from "fs";
|
|
4062
4287
|
import { join as join3 } from "path";
|
|
4063
4288
|
function tryClaudeVersion(claudePath) {
|
|
4064
4289
|
try {
|
|
@@ -4117,7 +4342,7 @@ function getClaudeCliVersion() {
|
|
|
4117
4342
|
return pathResult;
|
|
4118
4343
|
}
|
|
4119
4344
|
for (const path of COMMON_CLAUDE_PATHS) {
|
|
4120
|
-
if (
|
|
4345
|
+
if (existsSync3(path)) {
|
|
4121
4346
|
const result = tryClaudeVersion(path);
|
|
4122
4347
|
if (!result.error) {
|
|
4123
4348
|
return result;
|
|
@@ -4156,7 +4381,7 @@ function getClaudePath() {
|
|
|
4156
4381
|
return whichResult;
|
|
4157
4382
|
}
|
|
4158
4383
|
for (const path of COMMON_CLAUDE_PATHS) {
|
|
4159
|
-
if (
|
|
4384
|
+
if (existsSync3(path)) {
|
|
4160
4385
|
const result = tryClaudeVersion(path);
|
|
4161
4386
|
if (!result.error) {
|
|
4162
4387
|
discoveredClaudePath = path;
|
|
@@ -4239,6 +4464,690 @@ var init_version_check = __esm(() => {
|
|
|
4239
4464
|
];
|
|
4240
4465
|
});
|
|
4241
4466
|
|
|
4467
|
+
// src/mcp/outbound-env.ts
|
|
4468
|
+
var OUTBOUND_ENV;
|
|
4469
|
+
var init_outbound_env = __esm(() => {
|
|
4470
|
+
OUTBOUND_ENV = {
|
|
4471
|
+
SESSION_WORKING_DIR: "SESSION_WORKING_DIR",
|
|
4472
|
+
SESSION_UPLOAD_DIR: "SESSION_UPLOAD_DIR",
|
|
4473
|
+
OUTBOUND_FILES_ENABLED: "OUTBOUND_FILES_ENABLED",
|
|
4474
|
+
OUTBOUND_FILES_MAX_BYTES: "OUTBOUND_FILES_MAX_BYTES"
|
|
4475
|
+
};
|
|
4476
|
+
});
|
|
4477
|
+
|
|
4478
|
+
// src/mcp/agent-features-env.ts
|
|
4479
|
+
var AGENT_FEATURES_ENV;
|
|
4480
|
+
var init_agent_features_env = __esm(() => {
|
|
4481
|
+
AGENT_FEATURES_ENV = {
|
|
4482
|
+
MEMORY_CHANNEL_ENABLED: "CT_MEMORY_CHANNEL_ENABLED",
|
|
4483
|
+
ROUTINES_ENABLED: "CT_ROUTINES_ENABLED",
|
|
4484
|
+
WATCHES_ENABLED: "CT_WATCHES_ENABLED",
|
|
4485
|
+
UNATTENDED: "CT_UNATTENDED",
|
|
4486
|
+
DCM: "CT_DCM"
|
|
4487
|
+
};
|
|
4488
|
+
});
|
|
4489
|
+
|
|
4490
|
+
// src/claude/rate-limit-detector.ts
|
|
4491
|
+
function detectRateLimit(text, now = Date.now()) {
|
|
4492
|
+
if (!text)
|
|
4493
|
+
return { detected: false };
|
|
4494
|
+
let matched;
|
|
4495
|
+
for (const phrase of RATE_LIMIT_PHRASES) {
|
|
4496
|
+
const m = text.match(phrase);
|
|
4497
|
+
if (m) {
|
|
4498
|
+
matched = m[0];
|
|
4499
|
+
break;
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
if (!matched)
|
|
4503
|
+
return { detected: false };
|
|
4504
|
+
const resetAtEpochMs = extractResetAt(text, now);
|
|
4505
|
+
return { detected: true, matched, resetAtEpochMs };
|
|
4506
|
+
}
|
|
4507
|
+
function cooldownDeadline(hit, now = Date.now()) {
|
|
4508
|
+
if (!hit.detected)
|
|
4509
|
+
return now;
|
|
4510
|
+
return hit.resetAtEpochMs ?? now + DEFAULT_COOLDOWN_MS;
|
|
4511
|
+
}
|
|
4512
|
+
function extractResetAt(text, now) {
|
|
4513
|
+
const relative = text.match(/(?:retry[_\s-]?after|resets?\s+in)\s+(\d+)\s*(second|minute|hour|day)s?/i);
|
|
4514
|
+
if (relative) {
|
|
4515
|
+
const value = parseInt(relative[1], 10);
|
|
4516
|
+
const unit = relative[2].toLowerCase();
|
|
4517
|
+
const unitMs = {
|
|
4518
|
+
second: 1000,
|
|
4519
|
+
minute: 60000,
|
|
4520
|
+
hour: 3600000,
|
|
4521
|
+
day: 86400000
|
|
4522
|
+
};
|
|
4523
|
+
return now + value * unitMs[unit];
|
|
4524
|
+
}
|
|
4525
|
+
const unix = text.match(/\breset(?:_at)?\b\s*["']?\s*[:=]\s*(\d{10,13})/);
|
|
4526
|
+
if (unix) {
|
|
4527
|
+
const raw = parseInt(unix[1], 10);
|
|
4528
|
+
return unix[1].length === 13 ? raw : raw * 1000;
|
|
4529
|
+
}
|
|
4530
|
+
const clock = text.match(/resets?\s+at\s+(\d{1,2}):(\d{2})\s*(utc|gmt)?/i);
|
|
4531
|
+
if (clock) {
|
|
4532
|
+
const hh = parseInt(clock[1], 10);
|
|
4533
|
+
const mm = parseInt(clock[2], 10);
|
|
4534
|
+
if (hh < 24 && mm < 60) {
|
|
4535
|
+
const reference = new Date(now);
|
|
4536
|
+
const target = new Date(Date.UTC(reference.getUTCFullYear(), reference.getUTCMonth(), reference.getUTCDate(), hh, mm)).getTime();
|
|
4537
|
+
return target > now ? target : target + 86400000;
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
return;
|
|
4541
|
+
}
|
|
4542
|
+
function parseRateLimitEvent(event, now = Date.now()) {
|
|
4543
|
+
const info = event?.rate_limit_info;
|
|
4544
|
+
if (!info || typeof info !== "object")
|
|
4545
|
+
return { detected: false };
|
|
4546
|
+
const { status, resetsAt } = info;
|
|
4547
|
+
if (status !== "rejected")
|
|
4548
|
+
return { detected: false };
|
|
4549
|
+
const PAST_SKEW_TOLERANCE_MS = 2 * 60000;
|
|
4550
|
+
let resetAtEpochMs;
|
|
4551
|
+
if (typeof resetsAt === "number") {
|
|
4552
|
+
const ms = resetsAt * 1000;
|
|
4553
|
+
if (ms > now && ms - now < 8 * 86400000) {
|
|
4554
|
+
resetAtEpochMs = ms;
|
|
4555
|
+
} else if (ms <= now && now - ms < PAST_SKEW_TOLERANCE_MS) {
|
|
4556
|
+
resetAtEpochMs = now + 60000;
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
return { detected: true, matched: `rate_limit_event status=${status}`, resetAtEpochMs };
|
|
4560
|
+
}
|
|
4561
|
+
var RATE_LIMIT_PHRASES, DEFAULT_COOLDOWN_MS;
|
|
4562
|
+
var init_rate_limit_detector = __esm(() => {
|
|
4563
|
+
RATE_LIMIT_PHRASES = [
|
|
4564
|
+
/usage limit reached/i,
|
|
4565
|
+
/rate[_\s-]?limit[_\s-]?error/i,
|
|
4566
|
+
/you have hit the rate limit/i,
|
|
4567
|
+
/quota (has been )?exceeded/i,
|
|
4568
|
+
/\b429\b.*(rate|limit|quota)/i
|
|
4569
|
+
];
|
|
4570
|
+
DEFAULT_COOLDOWN_MS = 60 * 60 * 1000;
|
|
4571
|
+
});
|
|
4572
|
+
|
|
4573
|
+
// src/claude/cli.ts
|
|
4574
|
+
import { EventEmitter } from "events";
|
|
4575
|
+
import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
4576
|
+
import { fileURLToPath } from "url";
|
|
4577
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, watchFile, unwatchFile, unlinkSync, statSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
4578
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
4579
|
+
import { join as join4 } from "path";
|
|
4580
|
+
function cleanupBrowserBridgeSockets() {
|
|
4581
|
+
try {
|
|
4582
|
+
const tempDir = tmpdir2();
|
|
4583
|
+
const files = readdirSync(tempDir);
|
|
4584
|
+
for (const file of files) {
|
|
4585
|
+
if (file.startsWith("claude-mcp-browser-bridge-")) {
|
|
4586
|
+
const filePath = join4(tempDir, file);
|
|
4587
|
+
try {
|
|
4588
|
+
const stats = statSync(filePath);
|
|
4589
|
+
if (stats.isSocket()) {
|
|
4590
|
+
unlinkSync(filePath);
|
|
4591
|
+
log3.debug(`Removed stale browser bridge socket: ${file}`);
|
|
4592
|
+
}
|
|
4593
|
+
} catch {}
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
} catch (err) {
|
|
4597
|
+
log3.debug(`Browser bridge cleanup failed: ${err}`);
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
function buildClaudeChildEnv(parentEnv, account, opts) {
|
|
4601
|
+
const env = { ...parentEnv };
|
|
4602
|
+
if (opts?.claudeAiConnectors !== true) {
|
|
4603
|
+
env.ENABLE_CLAUDEAI_MCP_SERVERS = "false";
|
|
4604
|
+
}
|
|
4605
|
+
if (env.MCP_CONNECTION_NONBLOCKING === undefined) {
|
|
4606
|
+
env.MCP_CONNECTION_NONBLOCKING = "true";
|
|
4607
|
+
}
|
|
4608
|
+
if (env.ENABLE_PROMPT_CACHING_1H === undefined) {
|
|
4609
|
+
env.ENABLE_PROMPT_CACHING_1H = "true";
|
|
4610
|
+
}
|
|
4611
|
+
if (opts?.disableAutoMemory) {
|
|
4612
|
+
env.CLAUDE_CODE_DISABLE_AUTO_MEMORY = "1";
|
|
4613
|
+
}
|
|
4614
|
+
if (opts?.decisionBridge && env.MCP_TOOL_TIMEOUT === undefined) {
|
|
4615
|
+
env.MCP_TOOL_TIMEOUT = "3600000";
|
|
4616
|
+
}
|
|
4617
|
+
if (account?.home) {
|
|
4618
|
+
env.HOME = account.home;
|
|
4619
|
+
env.USERPROFILE = account.home;
|
|
4620
|
+
delete env.ANTHROPIC_API_KEY;
|
|
4621
|
+
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
4622
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
4623
|
+
delete env.CLAUDE_CONFIG_DIR;
|
|
4624
|
+
delete env.CLAUDE_SECURESTORAGE_CONFIG_DIR;
|
|
4625
|
+
} else if (account?.apiKey) {
|
|
4626
|
+
env.ANTHROPIC_API_KEY = account.apiKey;
|
|
4627
|
+
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
4628
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
4629
|
+
}
|
|
4630
|
+
return env;
|
|
4631
|
+
}
|
|
4632
|
+
function buildInlineSettings(statusLineCommand, memory, mcp = {}) {
|
|
4633
|
+
const settings = {};
|
|
4634
|
+
if (mcp.claudeAiConnectors !== true) {
|
|
4635
|
+
settings.disableClaudeAiConnectors = true;
|
|
4636
|
+
}
|
|
4637
|
+
if (statusLineCommand) {
|
|
4638
|
+
settings.statusLine = {
|
|
4639
|
+
type: "command",
|
|
4640
|
+
command: statusLineCommand,
|
|
4641
|
+
padding: 0
|
|
4642
|
+
};
|
|
4643
|
+
}
|
|
4644
|
+
if (memory) {
|
|
4645
|
+
settings.autoMemoryEnabled = true;
|
|
4646
|
+
settings.autoMemoryDirectory = memory.autoMemoryDir;
|
|
4647
|
+
}
|
|
4648
|
+
return Object.keys(settings).length > 0 ? settings : null;
|
|
4649
|
+
}
|
|
4650
|
+
function runtimeForScriptPath(scriptPath) {
|
|
4651
|
+
return scriptPath.endsWith(".ts") ? process.execPath : "node";
|
|
4652
|
+
}
|
|
4653
|
+
function isErrorResultEvent(event) {
|
|
4654
|
+
const ev = event;
|
|
4655
|
+
if (typeof ev.subtype === "string" && ev.subtype.startsWith("error"))
|
|
4656
|
+
return true;
|
|
4657
|
+
if (ev.is_error === true)
|
|
4658
|
+
return true;
|
|
4659
|
+
return false;
|
|
4660
|
+
}
|
|
4661
|
+
function materializeMcpConfig(config, sessionId, opts = {}) {
|
|
4662
|
+
if (opts.inline) {
|
|
4663
|
+
return { mode: "inline", value: JSON.stringify(config) };
|
|
4664
|
+
}
|
|
4665
|
+
const dir = opts.tmpDirOverride ?? tmpdir2();
|
|
4666
|
+
const path = join4(dir, `claude-threads-mcp-${sessionId ?? process.pid}-${Date.now()}.json`);
|
|
4667
|
+
writeFileSync2(path, JSON.stringify(config), { mode: 384 });
|
|
4668
|
+
return { mode: "file", path };
|
|
4669
|
+
}
|
|
4670
|
+
function buildPermissionArgs(opts) {
|
|
4671
|
+
const args = [];
|
|
4672
|
+
if (opts.permissionMode === "bypass" && !opts.platformConfig) {
|
|
4673
|
+
args.push("--dangerously-skip-permissions");
|
|
4674
|
+
return { args, tempFile: null };
|
|
4675
|
+
}
|
|
4676
|
+
if (!opts.platformConfig) {
|
|
4677
|
+
throw new Error(`platformConfig is required when permissionMode is '${opts.permissionMode}'`);
|
|
4678
|
+
}
|
|
4679
|
+
const mcpEnv = {
|
|
4680
|
+
PLATFORM_TYPE: opts.platformConfig.type,
|
|
4681
|
+
PLATFORM_URL: opts.platformConfig.url,
|
|
4682
|
+
PLATFORM_TOKEN: opts.platformConfig.token,
|
|
4683
|
+
PLATFORM_CHANNEL_ID: opts.platformConfig.channelId,
|
|
4684
|
+
PLATFORM_THREAD_ID: opts.threadId || "",
|
|
4685
|
+
ALLOWED_USERS: opts.platformConfig.allowedUsers.join(","),
|
|
4686
|
+
DEBUG: opts.debug ? "1" : "",
|
|
4687
|
+
PERMISSION_TIMEOUT_MS: String(opts.permissionTimeoutMs),
|
|
4688
|
+
SESSION_OWNER_USERNAME: opts.sessionOwnerUsername || ""
|
|
4689
|
+
};
|
|
4690
|
+
if (opts.decisionBridgePath) {
|
|
4691
|
+
mcpEnv.DECISION_BRIDGE_PATH = opts.decisionBridgePath;
|
|
4692
|
+
if (process.env.DECISION_BRIDGE_TIMEOUT_MS) {
|
|
4693
|
+
mcpEnv.DECISION_BRIDGE_TIMEOUT_MS = process.env.DECISION_BRIDGE_TIMEOUT_MS;
|
|
4694
|
+
}
|
|
4695
|
+
const features = opts.agentFeatures;
|
|
4696
|
+
if (features) {
|
|
4697
|
+
if (features.memoryChannel)
|
|
4698
|
+
mcpEnv[AGENT_FEATURES_ENV.MEMORY_CHANNEL_ENABLED] = "1";
|
|
4699
|
+
if (features.routines)
|
|
4700
|
+
mcpEnv[AGENT_FEATURES_ENV.ROUTINES_ENABLED] = "1";
|
|
4701
|
+
if (features.watches)
|
|
4702
|
+
mcpEnv[AGENT_FEATURES_ENV.WATCHES_ENABLED] = "1";
|
|
4703
|
+
if (features.unattended)
|
|
4704
|
+
mcpEnv[AGENT_FEATURES_ENV.UNATTENDED] = "1";
|
|
4705
|
+
if (features.dcm)
|
|
4706
|
+
mcpEnv[AGENT_FEATURES_ENV.DCM] = "1";
|
|
4707
|
+
}
|
|
4708
|
+
}
|
|
4709
|
+
if (opts.platformConfig.appToken) {
|
|
4710
|
+
mcpEnv.PLATFORM_APP_TOKEN = opts.platformConfig.appToken;
|
|
4711
|
+
}
|
|
4712
|
+
if (opts.workingDir) {
|
|
4713
|
+
mcpEnv[OUTBOUND_ENV.SESSION_WORKING_DIR] = opts.workingDir;
|
|
4714
|
+
}
|
|
4715
|
+
if (opts.uploadDir) {
|
|
4716
|
+
mcpEnv[OUTBOUND_ENV.SESSION_UPLOAD_DIR] = opts.uploadDir;
|
|
4717
|
+
}
|
|
4718
|
+
if (opts.outboundFiles?.enabled === false) {
|
|
4719
|
+
mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_ENABLED] = "0";
|
|
4720
|
+
}
|
|
4721
|
+
if (typeof opts.outboundFiles?.maxBytes === "number" && Number.isFinite(opts.outboundFiles.maxBytes) && opts.outboundFiles.maxBytes > 0) {
|
|
4722
|
+
mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_MAX_BYTES] = String(opts.outboundFiles.maxBytes);
|
|
4723
|
+
}
|
|
4724
|
+
const mcpConfig = {
|
|
4725
|
+
mcpServers: {
|
|
4726
|
+
"claude-threads-mcp": {
|
|
4727
|
+
type: "stdio",
|
|
4728
|
+
command: runtimeForScriptPath(opts.mcpServerPath),
|
|
4729
|
+
args: [opts.mcpServerPath],
|
|
4730
|
+
env: mcpEnv
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
};
|
|
4734
|
+
for (const [name, server] of Object.entries(opts.platformConfig.mcpServers ?? {})) {
|
|
4735
|
+
if (name === BOT_MCP_SERVER_NAME)
|
|
4736
|
+
continue;
|
|
4737
|
+
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 ?? {} };
|
|
4738
|
+
}
|
|
4739
|
+
const materialized = materializeMcpConfig(mcpConfig, opts.sessionId, { inline: opts.inline });
|
|
4740
|
+
let tempFile = null;
|
|
4741
|
+
if (materialized.mode === "file") {
|
|
4742
|
+
tempFile = materialized.path;
|
|
4743
|
+
args.push("--mcp-config", materialized.path);
|
|
4744
|
+
} else {
|
|
4745
|
+
args.push("--mcp-config", materialized.value);
|
|
4746
|
+
}
|
|
4747
|
+
if (opts.platformConfig.strictMcpConfig === true) {
|
|
4748
|
+
args.push("--strict-mcp-config");
|
|
4749
|
+
}
|
|
4750
|
+
if (opts.permissionMode === "bypass") {
|
|
4751
|
+
args.push("--dangerously-skip-permissions");
|
|
4752
|
+
} else {
|
|
4753
|
+
args.push("--permission-prompt-tool", "mcp__claude-threads-mcp__permission_prompt");
|
|
4754
|
+
if (opts.permissionMode === "auto") {
|
|
4755
|
+
args.push("--permission-mode", "auto");
|
|
4756
|
+
}
|
|
4757
|
+
}
|
|
4758
|
+
return { args, tempFile };
|
|
4759
|
+
}
|
|
4760
|
+
var log3, STDERR_PER_INSTANCE_CAP = 10240, STDERR_AGGREGATE_SOFT_CAP, totalStderrBytes = 0, ClaudeCli;
|
|
4761
|
+
var init_cli = __esm(() => {
|
|
4762
|
+
init_types();
|
|
4763
|
+
init_spawn();
|
|
4764
|
+
init_logger();
|
|
4765
|
+
init_version_check();
|
|
4766
|
+
init_outbound_env();
|
|
4767
|
+
init_agent_features_env();
|
|
4768
|
+
init_rate_limit_detector();
|
|
4769
|
+
log3 = createLogger("claude");
|
|
4770
|
+
STDERR_AGGREGATE_SOFT_CAP = 10 * 1024 * 1024;
|
|
4771
|
+
ClaudeCli = class ClaudeCli extends EventEmitter {
|
|
4772
|
+
process = null;
|
|
4773
|
+
options;
|
|
4774
|
+
buffer = "";
|
|
4775
|
+
debug = process.env.DEBUG === "1" || process.argv.includes("--debug");
|
|
4776
|
+
statusFilePath = null;
|
|
4777
|
+
lastStatusData = null;
|
|
4778
|
+
stderrBuffer = "";
|
|
4779
|
+
mcpConfigTempFile = null;
|
|
4780
|
+
lastEmittedRateLimitDeadline = 0;
|
|
4781
|
+
lastEmittedHitHadExplicitReset = false;
|
|
4782
|
+
log;
|
|
4783
|
+
constructor(options) {
|
|
4784
|
+
super();
|
|
4785
|
+
this.options = options;
|
|
4786
|
+
this.log = options.logSessionId ? createLogger("claude").forSession(options.logSessionId) : createLogger("claude");
|
|
4787
|
+
}
|
|
4788
|
+
getStatusFilePath() {
|
|
4789
|
+
return this.statusFilePath;
|
|
4790
|
+
}
|
|
4791
|
+
getStatusData() {
|
|
4792
|
+
if (!this.statusFilePath)
|
|
4793
|
+
return null;
|
|
4794
|
+
try {
|
|
4795
|
+
if (existsSync4(this.statusFilePath)) {
|
|
4796
|
+
const data = readFileSync2(this.statusFilePath, "utf8");
|
|
4797
|
+
this.lastStatusData = JSON.parse(data);
|
|
4798
|
+
}
|
|
4799
|
+
} catch (err) {
|
|
4800
|
+
this.log.debug(`Failed to read status file: ${err}`);
|
|
4801
|
+
}
|
|
4802
|
+
return this.lastStatusData;
|
|
4803
|
+
}
|
|
4804
|
+
startStatusWatch() {
|
|
4805
|
+
if (!this.statusFilePath) {
|
|
4806
|
+
this.log.debug("No status file path, skipping status watch");
|
|
4807
|
+
return;
|
|
4808
|
+
}
|
|
4809
|
+
this.log.debug(`Starting status watch: ${this.statusFilePath}`);
|
|
4810
|
+
const checkStatus = () => {
|
|
4811
|
+
const data = this.getStatusData();
|
|
4812
|
+
if (data && data.timestamp !== this.lastStatusData?.timestamp) {
|
|
4813
|
+
this.lastStatusData = data;
|
|
4814
|
+
this.emit("status", data);
|
|
4815
|
+
}
|
|
4816
|
+
};
|
|
4817
|
+
watchFile(this.statusFilePath, { interval: 1000 }, checkStatus);
|
|
4818
|
+
}
|
|
4819
|
+
stopStatusWatch() {
|
|
4820
|
+
if (this.statusFilePath) {
|
|
4821
|
+
unwatchFile(this.statusFilePath);
|
|
4822
|
+
try {
|
|
4823
|
+
if (existsSync4(this.statusFilePath)) {
|
|
4824
|
+
unlinkSync(this.statusFilePath);
|
|
4825
|
+
}
|
|
4826
|
+
} catch {}
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
start() {
|
|
4830
|
+
if (this.process)
|
|
4831
|
+
throw new Error("Already running");
|
|
4832
|
+
totalStderrBytes -= this.stderrBuffer.length;
|
|
4833
|
+
this.stderrBuffer = "";
|
|
4834
|
+
this.lastEmittedRateLimitDeadline = 0;
|
|
4835
|
+
this.lastEmittedHitHadExplicitReset = false;
|
|
4836
|
+
cleanupBrowserBridgeSockets();
|
|
4837
|
+
const claudePath = getClaudePath();
|
|
4838
|
+
const args = [
|
|
4839
|
+
"--input-format",
|
|
4840
|
+
"stream-json",
|
|
4841
|
+
"--output-format",
|
|
4842
|
+
"stream-json",
|
|
4843
|
+
"--verbose"
|
|
4844
|
+
];
|
|
4845
|
+
if (this.options.sessionId) {
|
|
4846
|
+
if (this.options.resume) {
|
|
4847
|
+
args.push("--resume", this.options.sessionId);
|
|
4848
|
+
} else {
|
|
4849
|
+
args.push("--session-id", this.options.sessionId);
|
|
4850
|
+
}
|
|
4851
|
+
}
|
|
4852
|
+
const permissionMode = this.options.permissionMode ?? "default";
|
|
4853
|
+
const permResult = buildPermissionArgs({
|
|
4854
|
+
permissionMode,
|
|
4855
|
+
mcpServerPath: this.getMcpServerPath(),
|
|
4856
|
+
platformConfig: this.options.platformConfig,
|
|
4857
|
+
threadId: this.options.threadId,
|
|
4858
|
+
sessionId: this.options.sessionId,
|
|
4859
|
+
permissionTimeoutMs: this.options.permissionTimeoutMs ?? 120000,
|
|
4860
|
+
debug: this.debug,
|
|
4861
|
+
workingDir: this.options.workingDir,
|
|
4862
|
+
uploadDir: this.options.uploadDir,
|
|
4863
|
+
outboundFiles: this.options.outboundFiles,
|
|
4864
|
+
sessionOwnerUsername: this.options.sessionOwnerUsername,
|
|
4865
|
+
decisionBridgePath: this.options.decisionBridgePath,
|
|
4866
|
+
agentFeatures: this.options.agentFeatures
|
|
4867
|
+
});
|
|
4868
|
+
args.push(...permResult.args);
|
|
4869
|
+
this.mcpConfigTempFile = permResult.tempFile;
|
|
4870
|
+
if (this.options.chrome) {
|
|
4871
|
+
args.push("--chrome");
|
|
4872
|
+
}
|
|
4873
|
+
if (this.options.appendSystemPrompt) {
|
|
4874
|
+
args.push("--append-system-prompt", this.options.appendSystemPrompt);
|
|
4875
|
+
}
|
|
4876
|
+
let statusLineCommand;
|
|
4877
|
+
if (this.options.sessionId) {
|
|
4878
|
+
this.statusFilePath = join4(tmpdir2(), `claude-threads-status-${this.options.sessionId}.json`);
|
|
4879
|
+
const statusLineWriterPath = this.getStatusLineWriterPath();
|
|
4880
|
+
const runtime = runtimeForScriptPath(statusLineWriterPath);
|
|
4881
|
+
statusLineCommand = `${runtime} ${statusLineWriterPath} ${this.options.sessionId}`;
|
|
4882
|
+
}
|
|
4883
|
+
const settings = buildInlineSettings(statusLineCommand, this.options.memory, {
|
|
4884
|
+
claudeAiConnectors: this.options.platformConfig?.claudeAiConnectors
|
|
4885
|
+
});
|
|
4886
|
+
if (settings) {
|
|
4887
|
+
args.push("--settings", JSON.stringify(settings));
|
|
4888
|
+
}
|
|
4889
|
+
this.log.debug(`Starting: ${claudePath} ${args.slice(0, 5).join(" ")}...`);
|
|
4890
|
+
const childEnv = this.buildChildEnv();
|
|
4891
|
+
if (this.options.account) {
|
|
4892
|
+
this.log.debug(`Spawning under Claude account "${this.options.account.id}"`);
|
|
4893
|
+
}
|
|
4894
|
+
this.process = crossSpawn(claudePath, args, {
|
|
4895
|
+
cwd: this.options.workingDir,
|
|
4896
|
+
env: childEnv,
|
|
4897
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4898
|
+
});
|
|
4899
|
+
this.log.debug(`Claude process spawned: pid=${this.process.pid}`);
|
|
4900
|
+
this.process.stdout?.on("data", (chunk) => {
|
|
4901
|
+
this.parseOutput(chunk.toString());
|
|
4902
|
+
});
|
|
4903
|
+
this.process.stderr?.on("data", (chunk) => {
|
|
4904
|
+
const text = chunk.toString();
|
|
4905
|
+
const before = this.stderrBuffer.length;
|
|
4906
|
+
this.stderrBuffer += text;
|
|
4907
|
+
const cap = totalStderrBytes > STDERR_AGGREGATE_SOFT_CAP ? 1024 : STDERR_PER_INSTANCE_CAP;
|
|
4908
|
+
if (this.stderrBuffer.length > cap) {
|
|
4909
|
+
this.stderrBuffer = this.stderrBuffer.slice(-cap);
|
|
4910
|
+
}
|
|
4911
|
+
totalStderrBytes += this.stderrBuffer.length - before;
|
|
4912
|
+
this.log.debug(`stderr: ${text.trim()}`);
|
|
4913
|
+
if (process.env.INTEGRATION_TEST === "1") {
|
|
4914
|
+
process.stderr.write(text);
|
|
4915
|
+
}
|
|
4916
|
+
this.maybeEmitRateLimit(text);
|
|
4917
|
+
});
|
|
4918
|
+
this.process.on("error", (err) => {
|
|
4919
|
+
this.log.error(`Claude error: ${err}`);
|
|
4920
|
+
this.emit("error", err);
|
|
4921
|
+
});
|
|
4922
|
+
this.process.on("exit", (code) => {
|
|
4923
|
+
this.log.debug(`Exited ${code}`);
|
|
4924
|
+
this.process = null;
|
|
4925
|
+
this.buffer = "";
|
|
4926
|
+
totalStderrBytes -= this.stderrBuffer.length;
|
|
4927
|
+
if (this.mcpConfigTempFile) {
|
|
4928
|
+
const path = this.mcpConfigTempFile;
|
|
4929
|
+
this.mcpConfigTempFile = null;
|
|
4930
|
+
try {
|
|
4931
|
+
unlinkSync(path);
|
|
4932
|
+
} catch {}
|
|
4933
|
+
}
|
|
4934
|
+
this.emit("exit", code);
|
|
4935
|
+
});
|
|
4936
|
+
}
|
|
4937
|
+
sendMessage(content) {
|
|
4938
|
+
if (!this.process?.stdin)
|
|
4939
|
+
throw new Error("Not running");
|
|
4940
|
+
const msg = JSON.stringify({
|
|
4941
|
+
type: "user",
|
|
4942
|
+
message: { role: "user", content }
|
|
4943
|
+
}) + `
|
|
4944
|
+
`;
|
|
4945
|
+
const preview = content.substring(0, 50);
|
|
4946
|
+
this.log.debug(`Sending: ${preview}...`);
|
|
4947
|
+
if (process.env.INTEGRATION_TEST === "1") {
|
|
4948
|
+
const stack = new Error().stack?.split(`
|
|
4949
|
+
`).slice(2, 6).join(" > ").replace(/\s+at\s+/g, " < ") ?? "?";
|
|
4950
|
+
process.stderr.write(`[claude-cli sendMessage pid=${this.process.pid}] ${preview} | ${stack}
|
|
4951
|
+
`);
|
|
4952
|
+
}
|
|
4953
|
+
this.process.stdin.write(msg);
|
|
4954
|
+
}
|
|
4955
|
+
sendToolResult(toolUseId, content) {
|
|
4956
|
+
if (!this.process?.stdin)
|
|
4957
|
+
throw new Error("Not running");
|
|
4958
|
+
const msg = JSON.stringify({
|
|
4959
|
+
type: "user",
|
|
4960
|
+
message: {
|
|
4961
|
+
role: "user",
|
|
4962
|
+
content: [{
|
|
4963
|
+
type: "tool_result",
|
|
4964
|
+
tool_use_id: toolUseId,
|
|
4965
|
+
content: typeof content === "string" ? content : JSON.stringify(content)
|
|
4966
|
+
}]
|
|
4967
|
+
}
|
|
4968
|
+
}) + `
|
|
4969
|
+
`;
|
|
4970
|
+
this.log.debug(`Sending tool_result for ${toolUseId}`);
|
|
4971
|
+
this.process.stdin.write(msg);
|
|
4972
|
+
}
|
|
4973
|
+
parseOutput(data) {
|
|
4974
|
+
this.buffer += data;
|
|
4975
|
+
const lines = this.buffer.split(`
|
|
4976
|
+
`);
|
|
4977
|
+
this.buffer = lines.pop() || "";
|
|
4978
|
+
for (const line of lines) {
|
|
4979
|
+
const trimmed = line.trim();
|
|
4980
|
+
if (!trimmed)
|
|
4981
|
+
continue;
|
|
4982
|
+
let event;
|
|
4983
|
+
try {
|
|
4984
|
+
event = JSON.parse(trimmed);
|
|
4985
|
+
} catch {
|
|
4986
|
+
continue;
|
|
4987
|
+
}
|
|
4988
|
+
try {
|
|
4989
|
+
this.emit("event", event);
|
|
4990
|
+
} catch (err) {
|
|
4991
|
+
this.log.error(`'event' listener threw while handling a ${event.type} event: ${err}`);
|
|
4992
|
+
}
|
|
4993
|
+
if (event.type === "result" && isErrorResultEvent(event)) {
|
|
4994
|
+
this.maybeEmitRateLimit(trimmed);
|
|
4995
|
+
}
|
|
4996
|
+
if (event.type === "rate_limit_event") {
|
|
4997
|
+
this.maybeEmitRateLimitHit(parseRateLimitEvent(event));
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
maybeEmitRateLimit(text) {
|
|
5002
|
+
this.maybeEmitRateLimitHit(detectRateLimit(text));
|
|
5003
|
+
}
|
|
5004
|
+
maybeEmitRateLimitHit(hit) {
|
|
5005
|
+
if (!hit.detected)
|
|
5006
|
+
return;
|
|
5007
|
+
if (!hit.resetAtEpochMs && this.lastEmittedHitHadExplicitReset && this.lastEmittedRateLimitDeadline > Date.now()) {
|
|
5008
|
+
return;
|
|
5009
|
+
}
|
|
5010
|
+
const newDeadline = cooldownDeadline(hit);
|
|
5011
|
+
const MIN_ADVANCE_MS = 60000;
|
|
5012
|
+
if (newDeadline - this.lastEmittedRateLimitDeadline < MIN_ADVANCE_MS) {
|
|
5013
|
+
if (hit.resetAtEpochMs !== undefined) {
|
|
5014
|
+
this.lastEmittedHitHadExplicitReset = true;
|
|
5015
|
+
}
|
|
5016
|
+
return;
|
|
5017
|
+
}
|
|
5018
|
+
this.lastEmittedRateLimitDeadline = newDeadline;
|
|
5019
|
+
this.lastEmittedHitHadExplicitReset = hit.resetAtEpochMs !== undefined;
|
|
5020
|
+
this.log.warn(`Rate limit detected: ${hit.matched ?? "(no match text)"}`);
|
|
5021
|
+
this.emit("rate-limit", hit);
|
|
5022
|
+
}
|
|
5023
|
+
isRunning() {
|
|
5024
|
+
return this.process !== null;
|
|
5025
|
+
}
|
|
5026
|
+
getLastStderr() {
|
|
5027
|
+
return this.stderrBuffer;
|
|
5028
|
+
}
|
|
5029
|
+
isPermanentFailure() {
|
|
5030
|
+
const stderr = this.stderrBuffer;
|
|
5031
|
+
if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
|
|
5032
|
+
return true;
|
|
5033
|
+
}
|
|
5034
|
+
if (stderr.includes("No conversation found with session ID")) {
|
|
5035
|
+
return true;
|
|
5036
|
+
}
|
|
5037
|
+
return false;
|
|
5038
|
+
}
|
|
5039
|
+
getPermanentFailureReason() {
|
|
5040
|
+
const stderr = this.stderrBuffer;
|
|
5041
|
+
if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
|
|
5042
|
+
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.";
|
|
5043
|
+
}
|
|
5044
|
+
if (stderr.includes("No conversation found with session ID")) {
|
|
5045
|
+
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.";
|
|
5046
|
+
}
|
|
5047
|
+
return null;
|
|
5048
|
+
}
|
|
5049
|
+
kill() {
|
|
5050
|
+
this.stopStatusWatch();
|
|
5051
|
+
if (!this.process) {
|
|
5052
|
+
this.log.debug("Kill called but process not running");
|
|
5053
|
+
return Promise.resolve();
|
|
5054
|
+
}
|
|
5055
|
+
const proc = this.process;
|
|
5056
|
+
const pid = proc.pid;
|
|
5057
|
+
this.process = null;
|
|
5058
|
+
this.log.debug(`Killing Claude process (pid=${pid})`);
|
|
5059
|
+
if (process.env.INTEGRATION_TEST === "1") {
|
|
5060
|
+
const stack = new Error().stack?.split(`
|
|
5061
|
+
`).slice(2, 7).join(" > ").replace(/\s+at\s+/g, " < ") ?? "?";
|
|
5062
|
+
process.stderr.write(`[claude-cli kill pid=${pid}] | ${stack}
|
|
5063
|
+
`);
|
|
5064
|
+
}
|
|
5065
|
+
return new Promise((resolve3) => {
|
|
5066
|
+
this.log.debug("Sending first SIGINT");
|
|
5067
|
+
proc.kill("SIGINT");
|
|
5068
|
+
const secondSigint = setTimeout(() => {
|
|
5069
|
+
try {
|
|
5070
|
+
this.log.debug("Sending second SIGINT");
|
|
5071
|
+
proc.kill("SIGINT");
|
|
5072
|
+
} catch {}
|
|
5073
|
+
}, 100);
|
|
5074
|
+
const forceKillTimeout = setTimeout(() => {
|
|
5075
|
+
try {
|
|
5076
|
+
this.log.debug("Sending SIGTERM (force kill)");
|
|
5077
|
+
proc.kill("SIGTERM");
|
|
5078
|
+
} catch {}
|
|
5079
|
+
}, 2000);
|
|
5080
|
+
const settle = (reason) => {
|
|
5081
|
+
this.log.debug(`Claude process gone (${reason})`);
|
|
5082
|
+
clearTimeout(secondSigint);
|
|
5083
|
+
clearTimeout(forceKillTimeout);
|
|
5084
|
+
clearTimeout(lastResort);
|
|
5085
|
+
resolve3();
|
|
5086
|
+
};
|
|
5087
|
+
const lastResort = setTimeout(() => {
|
|
5088
|
+
try {
|
|
5089
|
+
this.log.warn("Claude process did not exit after SIGTERM — sending SIGKILL");
|
|
5090
|
+
proc.kill("SIGKILL");
|
|
5091
|
+
} catch {}
|
|
5092
|
+
settle("kill timeout");
|
|
5093
|
+
}, 5000);
|
|
5094
|
+
proc.once("close", (code) => settle(`closed, code=${code}`));
|
|
5095
|
+
proc.once("error", () => settle("spawn error"));
|
|
5096
|
+
});
|
|
5097
|
+
}
|
|
5098
|
+
interrupt() {
|
|
5099
|
+
if (!this.process) {
|
|
5100
|
+
this.log.debug("Interrupt called but process not running");
|
|
5101
|
+
return false;
|
|
5102
|
+
}
|
|
5103
|
+
this.log.debug(`Interrupting Claude process (pid=${this.process.pid})`);
|
|
5104
|
+
this.process.kill("SIGINT");
|
|
5105
|
+
return true;
|
|
5106
|
+
}
|
|
5107
|
+
buildChildEnv() {
|
|
5108
|
+
return buildClaudeChildEnv(process.env, this.options.account, {
|
|
5109
|
+
claudeAiConnectors: this.options.platformConfig?.claudeAiConnectors,
|
|
5110
|
+
decisionBridge: this.options.decisionBridgePath !== undefined,
|
|
5111
|
+
disableAutoMemory: this.options.memory === null
|
|
5112
|
+
});
|
|
5113
|
+
}
|
|
5114
|
+
getMcpServerPath() {
|
|
5115
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
5116
|
+
const __dirname2 = dirname2(__filename2);
|
|
5117
|
+
const bundledPath = resolve2(__dirname2, "mcp", "mcp-server.js");
|
|
5118
|
+
if (existsSync4(bundledPath)) {
|
|
5119
|
+
return bundledPath;
|
|
5120
|
+
}
|
|
5121
|
+
const sourceLayoutPath = resolve2(__dirname2, "..", "mcp", "mcp-server.js");
|
|
5122
|
+
if (existsSync4(sourceLayoutPath)) {
|
|
5123
|
+
return sourceLayoutPath;
|
|
5124
|
+
}
|
|
5125
|
+
const tsPath = resolve2(__dirname2, "..", "mcp", "mcp-server.ts");
|
|
5126
|
+
if (existsSync4(tsPath)) {
|
|
5127
|
+
return tsPath;
|
|
5128
|
+
}
|
|
5129
|
+
return sourceLayoutPath;
|
|
5130
|
+
}
|
|
5131
|
+
getStatusLineWriterPath() {
|
|
5132
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
5133
|
+
const __dirname2 = dirname2(__filename2);
|
|
5134
|
+
const bundledPath = resolve2(__dirname2, "statusline", "writer.js");
|
|
5135
|
+
if (existsSync4(bundledPath)) {
|
|
5136
|
+
return bundledPath;
|
|
5137
|
+
}
|
|
5138
|
+
const sourceLayoutPath = resolve2(__dirname2, "..", "statusline", "writer.js");
|
|
5139
|
+
if (existsSync4(sourceLayoutPath)) {
|
|
5140
|
+
return sourceLayoutPath;
|
|
5141
|
+
}
|
|
5142
|
+
const tsPath = resolve2(__dirname2, "..", "statusline", "writer.ts");
|
|
5143
|
+
if (existsSync4(tsPath)) {
|
|
5144
|
+
return tsPath;
|
|
5145
|
+
}
|
|
5146
|
+
return sourceLayoutPath;
|
|
5147
|
+
}
|
|
5148
|
+
};
|
|
5149
|
+
});
|
|
5150
|
+
|
|
4242
5151
|
// src/utils/emoji.ts
|
|
4243
5152
|
var exports_emoji = {};
|
|
4244
5153
|
__export(exports_emoji, {
|
|
@@ -8699,7 +9608,7 @@ async function quickQuery(options) {
|
|
|
8699
9608
|
let resolved = false;
|
|
8700
9609
|
const proc = crossSpawn(claudePath, args, {
|
|
8701
9610
|
cwd: workingDir || process.cwd(),
|
|
8702
|
-
env: process.env,
|
|
9611
|
+
env: buildClaudeChildEnv(process.env),
|
|
8703
9612
|
stdio: ["pipe", "pipe", "pipe"]
|
|
8704
9613
|
});
|
|
8705
9614
|
const timeoutId = setTimeout(() => {
|
|
@@ -8763,6 +9672,7 @@ async function quickQuery(options) {
|
|
|
8763
9672
|
var log20;
|
|
8764
9673
|
var init_quick_query = __esm(() => {
|
|
8765
9674
|
init_spawn();
|
|
9675
|
+
init_cli();
|
|
8766
9676
|
init_version_check();
|
|
8767
9677
|
init_logger();
|
|
8768
9678
|
log20 = createLogger("query");
|
|
@@ -18445,7 +19355,7 @@ var require_react_reconciler_development = __commonJS((exports, module) => {
|
|
|
18445
19355
|
return hook.checkDCE ? true : false;
|
|
18446
19356
|
}
|
|
18447
19357
|
function setIsStrictModeForDevtools(newIsStrictMode) {
|
|
18448
|
-
typeof
|
|
19358
|
+
typeof log55 === "function" && unstable_setDisableYieldValue2(newIsStrictMode);
|
|
18449
19359
|
if (injectedHook && typeof injectedHook.setStrictMode === "function")
|
|
18450
19360
|
try {
|
|
18451
19361
|
injectedHook.setStrictMode(rendererID, newIsStrictMode);
|
|
@@ -26529,7 +27439,7 @@ Check the render method of %s.`, getComponentNameFromFiber(current) || "Unknown"
|
|
|
26529
27439
|
var fiberStack = [];
|
|
26530
27440
|
var index$jscomp$0 = -1, emptyContextObject = {};
|
|
26531
27441
|
Object.freeze(emptyContextObject);
|
|
26532
|
-
var clz32 = Math.clz32 ? Math.clz32 : clz32Fallback, log$1 = Math.log, LN2 = Math.LN2, nextTransitionUpdateLane = 256, nextTransitionDeferredLane = 262144, nextRetryLane = 4194304, scheduleCallback$3 = Scheduler.unstable_scheduleCallback, cancelCallback$1 = Scheduler.unstable_cancelCallback, shouldYield = Scheduler.unstable_shouldYield, requestPaint = Scheduler.unstable_requestPaint, now$1 = Scheduler.unstable_now, ImmediatePriority = Scheduler.unstable_ImmediatePriority, UserBlockingPriority = Scheduler.unstable_UserBlockingPriority, NormalPriority$1 = Scheduler.unstable_NormalPriority, IdlePriority = Scheduler.unstable_IdlePriority,
|
|
27442
|
+
var clz32 = Math.clz32 ? Math.clz32 : clz32Fallback, log$1 = Math.log, LN2 = Math.LN2, nextTransitionUpdateLane = 256, nextTransitionDeferredLane = 262144, nextRetryLane = 4194304, scheduleCallback$3 = Scheduler.unstable_scheduleCallback, cancelCallback$1 = Scheduler.unstable_cancelCallback, shouldYield = Scheduler.unstable_shouldYield, requestPaint = Scheduler.unstable_requestPaint, now$1 = Scheduler.unstable_now, ImmediatePriority = Scheduler.unstable_ImmediatePriority, UserBlockingPriority = Scheduler.unstable_UserBlockingPriority, NormalPriority$1 = Scheduler.unstable_NormalPriority, IdlePriority = Scheduler.unstable_IdlePriority, log55 = Scheduler.log, unstable_setDisableYieldValue2 = Scheduler.unstable_setDisableYieldValue, rendererID = null, injectedHook = null, hasLoggedError = false, isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined", lastResetTime = 0;
|
|
26533
27443
|
if (typeof performance === "object" && typeof performance.now === "function") {
|
|
26534
27444
|
var localPerformance = performance;
|
|
26535
27445
|
var getCurrentTime = function() {
|
|
@@ -46489,7 +47399,7 @@ function getSessionStatus(session) {
|
|
|
46489
47399
|
}
|
|
46490
47400
|
|
|
46491
47401
|
// src/config/index.ts
|
|
46492
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync as mkdirSync2, chmodSync as chmodSync2 } from "fs";
|
|
47402
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync as mkdirSync2, chmodSync as chmodSync2 } from "fs";
|
|
46493
47403
|
import { resolve, dirname } from "path";
|
|
46494
47404
|
import { homedir as homedir2 } from "os";
|
|
46495
47405
|
|
|
@@ -49610,154 +50520,43 @@ function requireJsYaml() {
|
|
|
49610
50520
|
var jsYamlExports = requireJsYaml();
|
|
49611
50521
|
var yaml = /* @__PURE__ */ getDefaultExportFromCjs(jsYamlExports);
|
|
49612
50522
|
|
|
49613
|
-
// src/config/
|
|
49614
|
-
|
|
49615
|
-
|
|
49616
|
-
|
|
49617
|
-
|
|
49618
|
-
|
|
49619
|
-
|
|
49620
|
-
|
|
49621
|
-
|
|
49622
|
-
if (isOverheadVisibility(value))
|
|
49623
|
-
return value;
|
|
49624
|
-
throw new Error(`Invalid ${fieldPath}: expected one of ${OVERHEAD_VISIBILITY_VALUES.join(", ")}, got ${JSON.stringify(value)}`);
|
|
49625
|
-
}
|
|
49626
|
-
var DEFAULT_MEMORY_CONFIG = {
|
|
49627
|
-
enabled: true,
|
|
49628
|
-
repoLayer: true,
|
|
49629
|
-
channelLayer: true,
|
|
49630
|
-
distillation: true
|
|
49631
|
-
};
|
|
49632
|
-
var MEMORY_DISABLED = {
|
|
49633
|
-
enabled: false,
|
|
49634
|
-
repoLayer: false,
|
|
49635
|
-
channelLayer: false,
|
|
49636
|
-
distillation: false
|
|
49637
|
-
};
|
|
49638
|
-
function resolveMemoryConfig(value, fieldPath) {
|
|
49639
|
-
if (value === undefined || value === null || value === true)
|
|
49640
|
-
return DEFAULT_MEMORY_CONFIG;
|
|
49641
|
-
if (value === false)
|
|
49642
|
-
return MEMORY_DISABLED;
|
|
49643
|
-
if (typeof value === "object" && !Array.isArray(value)) {
|
|
49644
|
-
const obj = value;
|
|
49645
|
-
const bool2 = (v, name, dflt) => resolveBooleanFeature(v, `${fieldPath ?? "memory"}.${name}`, { default: dflt, verb: `using default (${dflt})` });
|
|
49646
|
-
const enabled = bool2(obj.enabled, "enabled", true);
|
|
49647
|
-
if (!enabled)
|
|
49648
|
-
return MEMORY_DISABLED;
|
|
49649
|
-
return {
|
|
49650
|
-
enabled: true,
|
|
49651
|
-
repoLayer: bool2(obj.repoLayer, "repoLayer", true),
|
|
49652
|
-
channelLayer: bool2(obj.channelLayer, "channelLayer", true),
|
|
49653
|
-
distillation: bool2(obj.distillation, "distillation", true)
|
|
49654
|
-
};
|
|
49655
|
-
}
|
|
49656
|
-
console.warn(`Invalid ${fieldPath ?? "memory"} config: expected boolean or {enabled, repoLayer, channelLayer, distillation}, got ${JSON.stringify(value)} — using defaults`);
|
|
49657
|
-
return DEFAULT_MEMORY_CONFIG;
|
|
49658
|
-
}
|
|
49659
|
-
function resolveRoutinesEnabled(value, fieldPath) {
|
|
49660
|
-
return resolveBooleanFeature(value, fieldPath ?? "routines", { default: true, verb: "routines stay enabled" });
|
|
49661
|
-
}
|
|
49662
|
-
function resolveTranscriptionEnabled(value, fieldPath) {
|
|
49663
|
-
if (value === undefined || value === null)
|
|
49664
|
-
return true;
|
|
49665
|
-
if (typeof value === "boolean")
|
|
49666
|
-
return value;
|
|
49667
|
-
console.warn(`Invalid ${fieldPath ?? "transcription"}: ${JSON.stringify(value)} — expected true or false. ` + `Transcription is DISABLED for this platform: a value we cannot read is not consent to upload its audio.`);
|
|
49668
|
-
return false;
|
|
49669
|
-
}
|
|
49670
|
-
function resolveBooleanFeature(value, fieldPath, opts) {
|
|
49671
|
-
if (value === true || value === false)
|
|
49672
|
-
return value;
|
|
49673
|
-
if (value === undefined || value === null)
|
|
49674
|
-
return opts.default;
|
|
49675
|
-
console.warn(`Invalid ${fieldPath} config: expected boolean, got ${JSON.stringify(value)} — ${opts.verb}`);
|
|
49676
|
-
return opts.default;
|
|
49677
|
-
}
|
|
49678
|
-
function resolveWatchesEnabled(value, fieldPath) {
|
|
49679
|
-
return resolveBooleanFeature(value, fieldPath ?? "watches", { default: true, verb: "watches stay enabled" });
|
|
49680
|
-
}
|
|
49681
|
-
function resolveAuditLogEnabled(value, fieldPath) {
|
|
49682
|
-
return resolveBooleanFeature(value, fieldPath ?? "auditLog", { default: false, verb: "audit log stays off" });
|
|
49683
|
-
}
|
|
49684
|
-
var LIMITS_DEFAULTS = {
|
|
49685
|
-
maxSessions: 5,
|
|
49686
|
-
sessionTimeoutMinutes: 30,
|
|
49687
|
-
sessionWarningMinutes: 5,
|
|
49688
|
-
cleanupIntervalMinutes: 60,
|
|
49689
|
-
maxWorktreeAgeHours: 24,
|
|
49690
|
-
cleanupWorktrees: true,
|
|
49691
|
-
permissionTimeoutSeconds: 120,
|
|
49692
|
-
flushDelayMs: 500,
|
|
49693
|
-
maxRoutines: 10,
|
|
49694
|
-
maxWatches: 10,
|
|
49695
|
-
watchCooldownMinutes: 5,
|
|
49696
|
-
watchDailyCap: 20
|
|
50523
|
+
// src/config/index.ts
|
|
50524
|
+
init_types();
|
|
50525
|
+
|
|
50526
|
+
// src/config/managed-mcp.ts
|
|
50527
|
+
import { existsSync } from "fs";
|
|
50528
|
+
var MANAGED_MCP_CONFIG_PATHS = {
|
|
50529
|
+
darwin: "/Library/Application Support/ClaudeCode/managed-mcp.json",
|
|
50530
|
+
linux: "/etc/claude-code/managed-mcp.json",
|
|
50531
|
+
win32: "C:\\Program Files\\ClaudeCode\\managed-mcp.json"
|
|
49697
50532
|
};
|
|
49698
|
-
function
|
|
49699
|
-
|
|
49700
|
-
const envSessionTimeout = process.env.SESSION_TIMEOUT_MS ? Math.round(parseInt(process.env.SESSION_TIMEOUT_MS, 10) / 60000) : undefined;
|
|
49701
|
-
return {
|
|
49702
|
-
maxSessions: limits?.maxSessions ?? envMaxSessions ?? LIMITS_DEFAULTS.maxSessions,
|
|
49703
|
-
sessionTimeoutMinutes: limits?.sessionTimeoutMinutes ?? envSessionTimeout ?? LIMITS_DEFAULTS.sessionTimeoutMinutes,
|
|
49704
|
-
sessionWarningMinutes: limits?.sessionWarningMinutes ?? LIMITS_DEFAULTS.sessionWarningMinutes,
|
|
49705
|
-
cleanupIntervalMinutes: limits?.cleanupIntervalMinutes ?? LIMITS_DEFAULTS.cleanupIntervalMinutes,
|
|
49706
|
-
maxWorktreeAgeHours: limits?.maxWorktreeAgeHours ?? LIMITS_DEFAULTS.maxWorktreeAgeHours,
|
|
49707
|
-
cleanupWorktrees: limits?.cleanupWorktrees ?? LIMITS_DEFAULTS.cleanupWorktrees,
|
|
49708
|
-
permissionTimeoutSeconds: limits?.permissionTimeoutSeconds ?? LIMITS_DEFAULTS.permissionTimeoutSeconds,
|
|
49709
|
-
flushDelayMs: limits?.flushDelayMs ?? LIMITS_DEFAULTS.flushDelayMs,
|
|
49710
|
-
maxRoutines: limits?.maxRoutines ?? LIMITS_DEFAULTS.maxRoutines,
|
|
49711
|
-
maxWatches: limits?.maxWatches ?? LIMITS_DEFAULTS.maxWatches,
|
|
49712
|
-
watchCooldownMinutes: limits?.watchCooldownMinutes ?? LIMITS_DEFAULTS.watchCooldownMinutes,
|
|
49713
|
-
watchDailyCap: limits?.watchDailyCap ?? LIMITS_DEFAULTS.watchDailyCap
|
|
49714
|
-
};
|
|
50533
|
+
function managedMcpConfigPath(platform = process.platform) {
|
|
50534
|
+
return MANAGED_MCP_CONFIG_PATHS[platform] ?? null;
|
|
49715
50535
|
}
|
|
49716
|
-
function
|
|
49717
|
-
|
|
49718
|
-
|
|
49719
|
-
if (opts.skipPermissions === true)
|
|
49720
|
-
return "bypass";
|
|
49721
|
-
if (opts.skipPermissions === false)
|
|
49722
|
-
return "default";
|
|
49723
|
-
return "default";
|
|
50536
|
+
function managedMcpConfigPresent(platform = process.platform, exists = existsSync) {
|
|
50537
|
+
const path = managedMcpConfigPath(platform);
|
|
50538
|
+
return path !== null && exists(path);
|
|
49724
50539
|
}
|
|
49725
|
-
|
|
49726
|
-
|
|
49727
|
-
|
|
49728
|
-
|
|
49729
|
-
|
|
49730
|
-
|
|
49731
|
-
|
|
49732
|
-
|
|
49733
|
-
|
|
49734
|
-
|
|
49735
|
-
|
|
49736
|
-
|
|
49737
|
-
icon: "⚠️",
|
|
49738
|
-
label: "Bypass",
|
|
49739
|
-
description: "No prompts — every tool-use is allowed."
|
|
50540
|
+
// src/config/mcp-posture.ts
|
|
50541
|
+
init_types();
|
|
50542
|
+
function resolvePlatformMcpPosture(platforms, globalMcpServers, managedPresent = () => managedMcpConfigPresent()) {
|
|
50543
|
+
const warnings = [];
|
|
50544
|
+
for (const p of platforms) {
|
|
50545
|
+
p.mcpServers = resolveMcpServers(globalMcpServers, p.mcpServers, `platforms[${p.id}].mcpServers`);
|
|
50546
|
+
p.strictMcpConfig = resolveStrictMcpConfig(p.strictMcpConfig, `platforms[${p.id}].strictMcpConfig`);
|
|
50547
|
+
p.claudeAiConnectors = resolveClaudeAiConnectors(p.claudeAiConnectors, `platforms[${p.id}].claudeAiConnectors`);
|
|
50548
|
+
if (p.strictMcpConfig && managedPresent()) {
|
|
50549
|
+
warnings.push(`platforms[${p.id}].strictMcpConfig ignored: an enterprise managed MCP config is present ` + `(${managedMcpConfigPath() ?? "managed-mcp.json"}) and the Claude CLI refuses --strict-mcp-config alongside it.`);
|
|
50550
|
+
p.strictMcpConfig = false;
|
|
50551
|
+
}
|
|
49740
50552
|
}
|
|
49741
|
-
};
|
|
49742
|
-
function permissionModeDisplay(mode) {
|
|
49743
|
-
const info = MODE_INFO[mode];
|
|
49744
|
-
return { icon: info.icon, label: info.label, chip: `${info.icon} ${info.label}` };
|
|
49745
|
-
}
|
|
49746
|
-
function permissionModeDescription(mode) {
|
|
49747
|
-
return MODE_INFO[mode].description;
|
|
49748
|
-
}
|
|
49749
|
-
function effectivePermissionMode(input) {
|
|
49750
|
-
if (input.override)
|
|
49751
|
-
return input.override;
|
|
49752
|
-
if (input.sessionHasInteractiveOverride)
|
|
49753
|
-
return "default";
|
|
49754
|
-
return input.botWideMode;
|
|
50553
|
+
return { warnings };
|
|
49755
50554
|
}
|
|
49756
50555
|
|
|
49757
50556
|
// src/config/index.ts
|
|
49758
50557
|
var CONFIG_PATH = resolve(homedir2(), ".config", "claude-threads", "config.yaml");
|
|
49759
50558
|
function loadConfigWithMigration() {
|
|
49760
|
-
if (
|
|
50559
|
+
if (existsSync2(CONFIG_PATH)) {
|
|
49761
50560
|
const content = readFileSync(CONFIG_PATH, "utf-8");
|
|
49762
50561
|
return yaml.load(content);
|
|
49763
50562
|
}
|
|
@@ -49765,7 +50564,7 @@ function loadConfigWithMigration() {
|
|
|
49765
50564
|
}
|
|
49766
50565
|
function saveConfig(config, path = CONFIG_PATH) {
|
|
49767
50566
|
const configDir = dirname(path);
|
|
49768
|
-
if (!
|
|
50567
|
+
if (!existsSync2(configDir)) {
|
|
49769
50568
|
mkdirSync2(configDir, { recursive: true, mode: 448 });
|
|
49770
50569
|
}
|
|
49771
50570
|
const yamlContent = yaml.dump(config, {
|
|
@@ -49781,7 +50580,7 @@ function saveConfig(config, path = CONFIG_PATH) {
|
|
|
49781
50580
|
} catch {}
|
|
49782
50581
|
}
|
|
49783
50582
|
function configExists() {
|
|
49784
|
-
return
|
|
50583
|
+
return existsSync2(CONFIG_PATH);
|
|
49785
50584
|
}
|
|
49786
50585
|
|
|
49787
50586
|
// src/session/authorization.ts
|
|
@@ -49909,664 +50708,12 @@ class DecisionBridgeServer {
|
|
|
49909
50708
|
}
|
|
49910
50709
|
}
|
|
49911
50710
|
|
|
49912
|
-
// src/
|
|
49913
|
-
|
|
49914
|
-
|
|
49915
|
-
init_version_check();
|
|
49916
|
-
import { EventEmitter } from "events";
|
|
49917
|
-
import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
49918
|
-
import { fileURLToPath } from "url";
|
|
49919
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2, watchFile, unwatchFile, unlinkSync, statSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
49920
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
49921
|
-
import { join as join4 } from "path";
|
|
49922
|
-
|
|
49923
|
-
// src/mcp/outbound-env.ts
|
|
49924
|
-
var OUTBOUND_ENV = {
|
|
49925
|
-
SESSION_WORKING_DIR: "SESSION_WORKING_DIR",
|
|
49926
|
-
SESSION_UPLOAD_DIR: "SESSION_UPLOAD_DIR",
|
|
49927
|
-
OUTBOUND_FILES_ENABLED: "OUTBOUND_FILES_ENABLED",
|
|
49928
|
-
OUTBOUND_FILES_MAX_BYTES: "OUTBOUND_FILES_MAX_BYTES"
|
|
49929
|
-
};
|
|
49930
|
-
|
|
49931
|
-
// src/mcp/agent-features-env.ts
|
|
49932
|
-
var AGENT_FEATURES_ENV = {
|
|
49933
|
-
MEMORY_CHANNEL_ENABLED: "CT_MEMORY_CHANNEL_ENABLED",
|
|
49934
|
-
ROUTINES_ENABLED: "CT_ROUTINES_ENABLED",
|
|
49935
|
-
WATCHES_ENABLED: "CT_WATCHES_ENABLED",
|
|
49936
|
-
UNATTENDED: "CT_UNATTENDED",
|
|
49937
|
-
DCM: "CT_DCM"
|
|
49938
|
-
};
|
|
49939
|
-
|
|
49940
|
-
// src/claude/rate-limit-detector.ts
|
|
49941
|
-
var RATE_LIMIT_PHRASES = [
|
|
49942
|
-
/usage limit reached/i,
|
|
49943
|
-
/rate[_\s-]?limit[_\s-]?error/i,
|
|
49944
|
-
/you have hit the rate limit/i,
|
|
49945
|
-
/quota (has been )?exceeded/i,
|
|
49946
|
-
/\b429\b.*(rate|limit|quota)/i
|
|
49947
|
-
];
|
|
49948
|
-
var DEFAULT_COOLDOWN_MS = 60 * 60 * 1000;
|
|
49949
|
-
function detectRateLimit(text, now = Date.now()) {
|
|
49950
|
-
if (!text)
|
|
49951
|
-
return { detected: false };
|
|
49952
|
-
let matched;
|
|
49953
|
-
for (const phrase of RATE_LIMIT_PHRASES) {
|
|
49954
|
-
const m = text.match(phrase);
|
|
49955
|
-
if (m) {
|
|
49956
|
-
matched = m[0];
|
|
49957
|
-
break;
|
|
49958
|
-
}
|
|
49959
|
-
}
|
|
49960
|
-
if (!matched)
|
|
49961
|
-
return { detected: false };
|
|
49962
|
-
const resetAtEpochMs = extractResetAt(text, now);
|
|
49963
|
-
return { detected: true, matched, resetAtEpochMs };
|
|
49964
|
-
}
|
|
49965
|
-
function cooldownDeadline(hit, now = Date.now()) {
|
|
49966
|
-
if (!hit.detected)
|
|
49967
|
-
return now;
|
|
49968
|
-
return hit.resetAtEpochMs ?? now + DEFAULT_COOLDOWN_MS;
|
|
49969
|
-
}
|
|
49970
|
-
function extractResetAt(text, now) {
|
|
49971
|
-
const relative = text.match(/(?:retry[_\s-]?after|resets?\s+in)\s+(\d+)\s*(second|minute|hour|day)s?/i);
|
|
49972
|
-
if (relative) {
|
|
49973
|
-
const value = parseInt(relative[1], 10);
|
|
49974
|
-
const unit = relative[2].toLowerCase();
|
|
49975
|
-
const unitMs = {
|
|
49976
|
-
second: 1000,
|
|
49977
|
-
minute: 60000,
|
|
49978
|
-
hour: 3600000,
|
|
49979
|
-
day: 86400000
|
|
49980
|
-
};
|
|
49981
|
-
return now + value * unitMs[unit];
|
|
49982
|
-
}
|
|
49983
|
-
const unix = text.match(/\breset(?:_at)?\b\s*["']?\s*[:=]\s*(\d{10,13})/);
|
|
49984
|
-
if (unix) {
|
|
49985
|
-
const raw = parseInt(unix[1], 10);
|
|
49986
|
-
return unix[1].length === 13 ? raw : raw * 1000;
|
|
49987
|
-
}
|
|
49988
|
-
const clock = text.match(/resets?\s+at\s+(\d{1,2}):(\d{2})\s*(utc|gmt)?/i);
|
|
49989
|
-
if (clock) {
|
|
49990
|
-
const hh = parseInt(clock[1], 10);
|
|
49991
|
-
const mm = parseInt(clock[2], 10);
|
|
49992
|
-
if (hh < 24 && mm < 60) {
|
|
49993
|
-
const reference = new Date(now);
|
|
49994
|
-
const target = new Date(Date.UTC(reference.getUTCFullYear(), reference.getUTCMonth(), reference.getUTCDate(), hh, mm)).getTime();
|
|
49995
|
-
return target > now ? target : target + 86400000;
|
|
49996
|
-
}
|
|
49997
|
-
}
|
|
49998
|
-
return;
|
|
49999
|
-
}
|
|
50000
|
-
function parseRateLimitEvent(event, now = Date.now()) {
|
|
50001
|
-
const info = event?.rate_limit_info;
|
|
50002
|
-
if (!info || typeof info !== "object")
|
|
50003
|
-
return { detected: false };
|
|
50004
|
-
const { status, resetsAt } = info;
|
|
50005
|
-
if (status !== "rejected")
|
|
50006
|
-
return { detected: false };
|
|
50007
|
-
const PAST_SKEW_TOLERANCE_MS = 2 * 60000;
|
|
50008
|
-
let resetAtEpochMs;
|
|
50009
|
-
if (typeof resetsAt === "number") {
|
|
50010
|
-
const ms = resetsAt * 1000;
|
|
50011
|
-
if (ms > now && ms - now < 8 * 86400000) {
|
|
50012
|
-
resetAtEpochMs = ms;
|
|
50013
|
-
} else if (ms <= now && now - ms < PAST_SKEW_TOLERANCE_MS) {
|
|
50014
|
-
resetAtEpochMs = now + 60000;
|
|
50015
|
-
}
|
|
50016
|
-
}
|
|
50017
|
-
return { detected: true, matched: `rate_limit_event status=${status}`, resetAtEpochMs };
|
|
50018
|
-
}
|
|
50019
|
-
|
|
50020
|
-
// src/claude/cli.ts
|
|
50021
|
-
var log3 = createLogger("claude");
|
|
50022
|
-
function cleanupBrowserBridgeSockets() {
|
|
50023
|
-
try {
|
|
50024
|
-
const tempDir = tmpdir2();
|
|
50025
|
-
const files = readdirSync(tempDir);
|
|
50026
|
-
for (const file of files) {
|
|
50027
|
-
if (file.startsWith("claude-mcp-browser-bridge-")) {
|
|
50028
|
-
const filePath = join4(tempDir, file);
|
|
50029
|
-
try {
|
|
50030
|
-
const stats = statSync(filePath);
|
|
50031
|
-
if (stats.isSocket()) {
|
|
50032
|
-
unlinkSync(filePath);
|
|
50033
|
-
log3.debug(`Removed stale browser bridge socket: ${file}`);
|
|
50034
|
-
}
|
|
50035
|
-
} catch {}
|
|
50036
|
-
}
|
|
50037
|
-
}
|
|
50038
|
-
} catch (err) {
|
|
50039
|
-
log3.debug(`Browser bridge cleanup failed: ${err}`);
|
|
50040
|
-
}
|
|
50041
|
-
}
|
|
50042
|
-
function buildClaudeChildEnv(parentEnv, account, opts) {
|
|
50043
|
-
const env = { ...parentEnv };
|
|
50044
|
-
if (env.MCP_CONNECTION_NONBLOCKING === undefined) {
|
|
50045
|
-
env.MCP_CONNECTION_NONBLOCKING = "true";
|
|
50046
|
-
}
|
|
50047
|
-
if (env.ENABLE_PROMPT_CACHING_1H === undefined) {
|
|
50048
|
-
env.ENABLE_PROMPT_CACHING_1H = "true";
|
|
50049
|
-
}
|
|
50050
|
-
if (opts?.disableAutoMemory) {
|
|
50051
|
-
env.CLAUDE_CODE_DISABLE_AUTO_MEMORY = "1";
|
|
50052
|
-
}
|
|
50053
|
-
if (opts?.decisionBridge && env.MCP_TOOL_TIMEOUT === undefined) {
|
|
50054
|
-
env.MCP_TOOL_TIMEOUT = "3600000";
|
|
50055
|
-
}
|
|
50056
|
-
if (account?.home) {
|
|
50057
|
-
env.HOME = account.home;
|
|
50058
|
-
env.USERPROFILE = account.home;
|
|
50059
|
-
delete env.ANTHROPIC_API_KEY;
|
|
50060
|
-
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
50061
|
-
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
50062
|
-
delete env.CLAUDE_CONFIG_DIR;
|
|
50063
|
-
delete env.CLAUDE_SECURESTORAGE_CONFIG_DIR;
|
|
50064
|
-
} else if (account?.apiKey) {
|
|
50065
|
-
env.ANTHROPIC_API_KEY = account.apiKey;
|
|
50066
|
-
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
50067
|
-
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
50068
|
-
}
|
|
50069
|
-
return env;
|
|
50070
|
-
}
|
|
50071
|
-
function buildInlineSettings(statusLineCommand, memory) {
|
|
50072
|
-
const settings = {};
|
|
50073
|
-
if (statusLineCommand) {
|
|
50074
|
-
settings.statusLine = {
|
|
50075
|
-
type: "command",
|
|
50076
|
-
command: statusLineCommand,
|
|
50077
|
-
padding: 0
|
|
50078
|
-
};
|
|
50079
|
-
}
|
|
50080
|
-
if (memory) {
|
|
50081
|
-
settings.autoMemoryEnabled = true;
|
|
50082
|
-
settings.autoMemoryDirectory = memory.autoMemoryDir;
|
|
50083
|
-
}
|
|
50084
|
-
return Object.keys(settings).length > 0 ? settings : null;
|
|
50085
|
-
}
|
|
50086
|
-
function runtimeForScriptPath(scriptPath) {
|
|
50087
|
-
return scriptPath.endsWith(".ts") ? process.execPath : "node";
|
|
50088
|
-
}
|
|
50089
|
-
function isErrorResultEvent(event) {
|
|
50090
|
-
const ev = event;
|
|
50091
|
-
if (typeof ev.subtype === "string" && ev.subtype.startsWith("error"))
|
|
50092
|
-
return true;
|
|
50093
|
-
if (ev.is_error === true)
|
|
50094
|
-
return true;
|
|
50095
|
-
return false;
|
|
50096
|
-
}
|
|
50097
|
-
function materializeMcpConfig(config, sessionId, opts = {}) {
|
|
50098
|
-
if (opts.inline) {
|
|
50099
|
-
return { mode: "inline", value: JSON.stringify(config) };
|
|
50100
|
-
}
|
|
50101
|
-
const dir = opts.tmpDirOverride ?? tmpdir2();
|
|
50102
|
-
const path = join4(dir, `claude-threads-mcp-${sessionId ?? process.pid}-${Date.now()}.json`);
|
|
50103
|
-
writeFileSync2(path, JSON.stringify(config), { mode: 384 });
|
|
50104
|
-
return { mode: "file", path };
|
|
50105
|
-
}
|
|
50106
|
-
function buildPermissionArgs(opts) {
|
|
50107
|
-
const args = [];
|
|
50108
|
-
if (opts.permissionMode === "bypass" && !opts.platformConfig) {
|
|
50109
|
-
args.push("--dangerously-skip-permissions");
|
|
50110
|
-
return { args, tempFile: null };
|
|
50111
|
-
}
|
|
50112
|
-
if (!opts.platformConfig) {
|
|
50113
|
-
throw new Error(`platformConfig is required when permissionMode is '${opts.permissionMode}'`);
|
|
50114
|
-
}
|
|
50115
|
-
const mcpEnv = {
|
|
50116
|
-
PLATFORM_TYPE: opts.platformConfig.type,
|
|
50117
|
-
PLATFORM_URL: opts.platformConfig.url,
|
|
50118
|
-
PLATFORM_TOKEN: opts.platformConfig.token,
|
|
50119
|
-
PLATFORM_CHANNEL_ID: opts.platformConfig.channelId,
|
|
50120
|
-
PLATFORM_THREAD_ID: opts.threadId || "",
|
|
50121
|
-
ALLOWED_USERS: opts.platformConfig.allowedUsers.join(","),
|
|
50122
|
-
DEBUG: opts.debug ? "1" : "",
|
|
50123
|
-
PERMISSION_TIMEOUT_MS: String(opts.permissionTimeoutMs),
|
|
50124
|
-
SESSION_OWNER_USERNAME: opts.sessionOwnerUsername || ""
|
|
50125
|
-
};
|
|
50126
|
-
if (opts.decisionBridgePath) {
|
|
50127
|
-
mcpEnv.DECISION_BRIDGE_PATH = opts.decisionBridgePath;
|
|
50128
|
-
if (process.env.DECISION_BRIDGE_TIMEOUT_MS) {
|
|
50129
|
-
mcpEnv.DECISION_BRIDGE_TIMEOUT_MS = process.env.DECISION_BRIDGE_TIMEOUT_MS;
|
|
50130
|
-
}
|
|
50131
|
-
const features = opts.agentFeatures;
|
|
50132
|
-
if (features) {
|
|
50133
|
-
if (features.memoryChannel)
|
|
50134
|
-
mcpEnv[AGENT_FEATURES_ENV.MEMORY_CHANNEL_ENABLED] = "1";
|
|
50135
|
-
if (features.routines)
|
|
50136
|
-
mcpEnv[AGENT_FEATURES_ENV.ROUTINES_ENABLED] = "1";
|
|
50137
|
-
if (features.watches)
|
|
50138
|
-
mcpEnv[AGENT_FEATURES_ENV.WATCHES_ENABLED] = "1";
|
|
50139
|
-
if (features.unattended)
|
|
50140
|
-
mcpEnv[AGENT_FEATURES_ENV.UNATTENDED] = "1";
|
|
50141
|
-
if (features.dcm)
|
|
50142
|
-
mcpEnv[AGENT_FEATURES_ENV.DCM] = "1";
|
|
50143
|
-
}
|
|
50144
|
-
}
|
|
50145
|
-
if (opts.platformConfig.appToken) {
|
|
50146
|
-
mcpEnv.PLATFORM_APP_TOKEN = opts.platformConfig.appToken;
|
|
50147
|
-
}
|
|
50148
|
-
if (opts.workingDir) {
|
|
50149
|
-
mcpEnv[OUTBOUND_ENV.SESSION_WORKING_DIR] = opts.workingDir;
|
|
50150
|
-
}
|
|
50151
|
-
if (opts.uploadDir) {
|
|
50152
|
-
mcpEnv[OUTBOUND_ENV.SESSION_UPLOAD_DIR] = opts.uploadDir;
|
|
50153
|
-
}
|
|
50154
|
-
if (opts.outboundFiles?.enabled === false) {
|
|
50155
|
-
mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_ENABLED] = "0";
|
|
50156
|
-
}
|
|
50157
|
-
if (typeof opts.outboundFiles?.maxBytes === "number" && Number.isFinite(opts.outboundFiles.maxBytes) && opts.outboundFiles.maxBytes > 0) {
|
|
50158
|
-
mcpEnv[OUTBOUND_ENV.OUTBOUND_FILES_MAX_BYTES] = String(opts.outboundFiles.maxBytes);
|
|
50159
|
-
}
|
|
50160
|
-
const mcpConfig = {
|
|
50161
|
-
mcpServers: {
|
|
50162
|
-
"claude-threads-mcp": {
|
|
50163
|
-
type: "stdio",
|
|
50164
|
-
command: runtimeForScriptPath(opts.mcpServerPath),
|
|
50165
|
-
args: [opts.mcpServerPath],
|
|
50166
|
-
env: mcpEnv
|
|
50167
|
-
}
|
|
50168
|
-
}
|
|
50169
|
-
};
|
|
50170
|
-
const materialized = materializeMcpConfig(mcpConfig, opts.sessionId, { inline: opts.inline });
|
|
50171
|
-
let tempFile = null;
|
|
50172
|
-
if (materialized.mode === "file") {
|
|
50173
|
-
tempFile = materialized.path;
|
|
50174
|
-
args.push("--mcp-config", materialized.path);
|
|
50175
|
-
} else {
|
|
50176
|
-
args.push("--mcp-config", materialized.value);
|
|
50177
|
-
}
|
|
50178
|
-
if (opts.permissionMode === "bypass") {
|
|
50179
|
-
args.push("--dangerously-skip-permissions");
|
|
50180
|
-
} else {
|
|
50181
|
-
args.push("--permission-prompt-tool", "mcp__claude-threads-mcp__permission_prompt");
|
|
50182
|
-
if (opts.permissionMode === "auto") {
|
|
50183
|
-
args.push("--permission-mode", "auto");
|
|
50184
|
-
}
|
|
50185
|
-
}
|
|
50186
|
-
return { args, tempFile };
|
|
50187
|
-
}
|
|
50188
|
-
var STDERR_PER_INSTANCE_CAP = 10240;
|
|
50189
|
-
var STDERR_AGGREGATE_SOFT_CAP = 10 * 1024 * 1024;
|
|
50190
|
-
var totalStderrBytes = 0;
|
|
50191
|
-
|
|
50192
|
-
class ClaudeCli extends EventEmitter {
|
|
50193
|
-
process = null;
|
|
50194
|
-
options;
|
|
50195
|
-
buffer = "";
|
|
50196
|
-
debug = process.env.DEBUG === "1" || process.argv.includes("--debug");
|
|
50197
|
-
statusFilePath = null;
|
|
50198
|
-
lastStatusData = null;
|
|
50199
|
-
stderrBuffer = "";
|
|
50200
|
-
mcpConfigTempFile = null;
|
|
50201
|
-
lastEmittedRateLimitDeadline = 0;
|
|
50202
|
-
lastEmittedHitHadExplicitReset = false;
|
|
50203
|
-
log;
|
|
50204
|
-
constructor(options) {
|
|
50205
|
-
super();
|
|
50206
|
-
this.options = options;
|
|
50207
|
-
this.log = options.logSessionId ? createLogger("claude").forSession(options.logSessionId) : createLogger("claude");
|
|
50208
|
-
}
|
|
50209
|
-
getStatusFilePath() {
|
|
50210
|
-
return this.statusFilePath;
|
|
50211
|
-
}
|
|
50212
|
-
getStatusData() {
|
|
50213
|
-
if (!this.statusFilePath)
|
|
50214
|
-
return null;
|
|
50215
|
-
try {
|
|
50216
|
-
if (existsSync3(this.statusFilePath)) {
|
|
50217
|
-
const data = readFileSync2(this.statusFilePath, "utf8");
|
|
50218
|
-
this.lastStatusData = JSON.parse(data);
|
|
50219
|
-
}
|
|
50220
|
-
} catch (err) {
|
|
50221
|
-
this.log.debug(`Failed to read status file: ${err}`);
|
|
50222
|
-
}
|
|
50223
|
-
return this.lastStatusData;
|
|
50224
|
-
}
|
|
50225
|
-
startStatusWatch() {
|
|
50226
|
-
if (!this.statusFilePath) {
|
|
50227
|
-
this.log.debug("No status file path, skipping status watch");
|
|
50228
|
-
return;
|
|
50229
|
-
}
|
|
50230
|
-
this.log.debug(`Starting status watch: ${this.statusFilePath}`);
|
|
50231
|
-
const checkStatus = () => {
|
|
50232
|
-
const data = this.getStatusData();
|
|
50233
|
-
if (data && data.timestamp !== this.lastStatusData?.timestamp) {
|
|
50234
|
-
this.lastStatusData = data;
|
|
50235
|
-
this.emit("status", data);
|
|
50236
|
-
}
|
|
50237
|
-
};
|
|
50238
|
-
watchFile(this.statusFilePath, { interval: 1000 }, checkStatus);
|
|
50239
|
-
}
|
|
50240
|
-
stopStatusWatch() {
|
|
50241
|
-
if (this.statusFilePath) {
|
|
50242
|
-
unwatchFile(this.statusFilePath);
|
|
50243
|
-
try {
|
|
50244
|
-
if (existsSync3(this.statusFilePath)) {
|
|
50245
|
-
unlinkSync(this.statusFilePath);
|
|
50246
|
-
}
|
|
50247
|
-
} catch {}
|
|
50248
|
-
}
|
|
50249
|
-
}
|
|
50250
|
-
start() {
|
|
50251
|
-
if (this.process)
|
|
50252
|
-
throw new Error("Already running");
|
|
50253
|
-
totalStderrBytes -= this.stderrBuffer.length;
|
|
50254
|
-
this.stderrBuffer = "";
|
|
50255
|
-
this.lastEmittedRateLimitDeadline = 0;
|
|
50256
|
-
this.lastEmittedHitHadExplicitReset = false;
|
|
50257
|
-
cleanupBrowserBridgeSockets();
|
|
50258
|
-
const claudePath = getClaudePath();
|
|
50259
|
-
const args = [
|
|
50260
|
-
"--input-format",
|
|
50261
|
-
"stream-json",
|
|
50262
|
-
"--output-format",
|
|
50263
|
-
"stream-json",
|
|
50264
|
-
"--verbose"
|
|
50265
|
-
];
|
|
50266
|
-
if (this.options.sessionId) {
|
|
50267
|
-
if (this.options.resume) {
|
|
50268
|
-
args.push("--resume", this.options.sessionId);
|
|
50269
|
-
} else {
|
|
50270
|
-
args.push("--session-id", this.options.sessionId);
|
|
50271
|
-
}
|
|
50272
|
-
}
|
|
50273
|
-
const permissionMode = this.options.permissionMode ?? "default";
|
|
50274
|
-
const permResult = buildPermissionArgs({
|
|
50275
|
-
permissionMode,
|
|
50276
|
-
mcpServerPath: this.getMcpServerPath(),
|
|
50277
|
-
platformConfig: this.options.platformConfig,
|
|
50278
|
-
threadId: this.options.threadId,
|
|
50279
|
-
sessionId: this.options.sessionId,
|
|
50280
|
-
permissionTimeoutMs: this.options.permissionTimeoutMs ?? 120000,
|
|
50281
|
-
debug: this.debug,
|
|
50282
|
-
workingDir: this.options.workingDir,
|
|
50283
|
-
uploadDir: this.options.uploadDir,
|
|
50284
|
-
outboundFiles: this.options.outboundFiles,
|
|
50285
|
-
sessionOwnerUsername: this.options.sessionOwnerUsername,
|
|
50286
|
-
decisionBridgePath: this.options.decisionBridgePath,
|
|
50287
|
-
agentFeatures: this.options.agentFeatures
|
|
50288
|
-
});
|
|
50289
|
-
args.push(...permResult.args);
|
|
50290
|
-
this.mcpConfigTempFile = permResult.tempFile;
|
|
50291
|
-
if (this.options.chrome) {
|
|
50292
|
-
args.push("--chrome");
|
|
50293
|
-
}
|
|
50294
|
-
if (this.options.appendSystemPrompt) {
|
|
50295
|
-
args.push("--append-system-prompt", this.options.appendSystemPrompt);
|
|
50296
|
-
}
|
|
50297
|
-
let statusLineCommand;
|
|
50298
|
-
if (this.options.sessionId) {
|
|
50299
|
-
this.statusFilePath = join4(tmpdir2(), `claude-threads-status-${this.options.sessionId}.json`);
|
|
50300
|
-
const statusLineWriterPath = this.getStatusLineWriterPath();
|
|
50301
|
-
const runtime = runtimeForScriptPath(statusLineWriterPath);
|
|
50302
|
-
statusLineCommand = `${runtime} ${statusLineWriterPath} ${this.options.sessionId}`;
|
|
50303
|
-
}
|
|
50304
|
-
const settings = buildInlineSettings(statusLineCommand, this.options.memory);
|
|
50305
|
-
if (settings) {
|
|
50306
|
-
args.push("--settings", JSON.stringify(settings));
|
|
50307
|
-
}
|
|
50308
|
-
this.log.debug(`Starting: ${claudePath} ${args.slice(0, 5).join(" ")}...`);
|
|
50309
|
-
const childEnv = this.buildChildEnv();
|
|
50310
|
-
if (this.options.account) {
|
|
50311
|
-
this.log.debug(`Spawning under Claude account "${this.options.account.id}"`);
|
|
50312
|
-
}
|
|
50313
|
-
this.process = crossSpawn(claudePath, args, {
|
|
50314
|
-
cwd: this.options.workingDir,
|
|
50315
|
-
env: childEnv,
|
|
50316
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
50317
|
-
});
|
|
50318
|
-
this.log.debug(`Claude process spawned: pid=${this.process.pid}`);
|
|
50319
|
-
this.process.stdout?.on("data", (chunk) => {
|
|
50320
|
-
this.parseOutput(chunk.toString());
|
|
50321
|
-
});
|
|
50322
|
-
this.process.stderr?.on("data", (chunk) => {
|
|
50323
|
-
const text = chunk.toString();
|
|
50324
|
-
const before = this.stderrBuffer.length;
|
|
50325
|
-
this.stderrBuffer += text;
|
|
50326
|
-
const cap = totalStderrBytes > STDERR_AGGREGATE_SOFT_CAP ? 1024 : STDERR_PER_INSTANCE_CAP;
|
|
50327
|
-
if (this.stderrBuffer.length > cap) {
|
|
50328
|
-
this.stderrBuffer = this.stderrBuffer.slice(-cap);
|
|
50329
|
-
}
|
|
50330
|
-
totalStderrBytes += this.stderrBuffer.length - before;
|
|
50331
|
-
this.log.debug(`stderr: ${text.trim()}`);
|
|
50332
|
-
if (process.env.INTEGRATION_TEST === "1") {
|
|
50333
|
-
process.stderr.write(text);
|
|
50334
|
-
}
|
|
50335
|
-
this.maybeEmitRateLimit(text);
|
|
50336
|
-
});
|
|
50337
|
-
this.process.on("error", (err) => {
|
|
50338
|
-
this.log.error(`Claude error: ${err}`);
|
|
50339
|
-
this.emit("error", err);
|
|
50340
|
-
});
|
|
50341
|
-
this.process.on("exit", (code) => {
|
|
50342
|
-
this.log.debug(`Exited ${code}`);
|
|
50343
|
-
this.process = null;
|
|
50344
|
-
this.buffer = "";
|
|
50345
|
-
totalStderrBytes -= this.stderrBuffer.length;
|
|
50346
|
-
if (this.mcpConfigTempFile) {
|
|
50347
|
-
const path = this.mcpConfigTempFile;
|
|
50348
|
-
this.mcpConfigTempFile = null;
|
|
50349
|
-
try {
|
|
50350
|
-
unlinkSync(path);
|
|
50351
|
-
} catch {}
|
|
50352
|
-
}
|
|
50353
|
-
this.emit("exit", code);
|
|
50354
|
-
});
|
|
50355
|
-
}
|
|
50356
|
-
sendMessage(content) {
|
|
50357
|
-
if (!this.process?.stdin)
|
|
50358
|
-
throw new Error("Not running");
|
|
50359
|
-
const msg = JSON.stringify({
|
|
50360
|
-
type: "user",
|
|
50361
|
-
message: { role: "user", content }
|
|
50362
|
-
}) + `
|
|
50363
|
-
`;
|
|
50364
|
-
const preview = content.substring(0, 50);
|
|
50365
|
-
this.log.debug(`Sending: ${preview}...`);
|
|
50366
|
-
if (process.env.INTEGRATION_TEST === "1") {
|
|
50367
|
-
const stack = new Error().stack?.split(`
|
|
50368
|
-
`).slice(2, 6).join(" > ").replace(/\s+at\s+/g, " < ") ?? "?";
|
|
50369
|
-
process.stderr.write(`[claude-cli sendMessage pid=${this.process.pid}] ${preview} | ${stack}
|
|
50370
|
-
`);
|
|
50371
|
-
}
|
|
50372
|
-
this.process.stdin.write(msg);
|
|
50373
|
-
}
|
|
50374
|
-
sendToolResult(toolUseId, content) {
|
|
50375
|
-
if (!this.process?.stdin)
|
|
50376
|
-
throw new Error("Not running");
|
|
50377
|
-
const msg = JSON.stringify({
|
|
50378
|
-
type: "user",
|
|
50379
|
-
message: {
|
|
50380
|
-
role: "user",
|
|
50381
|
-
content: [{
|
|
50382
|
-
type: "tool_result",
|
|
50383
|
-
tool_use_id: toolUseId,
|
|
50384
|
-
content: typeof content === "string" ? content : JSON.stringify(content)
|
|
50385
|
-
}]
|
|
50386
|
-
}
|
|
50387
|
-
}) + `
|
|
50388
|
-
`;
|
|
50389
|
-
this.log.debug(`Sending tool_result for ${toolUseId}`);
|
|
50390
|
-
this.process.stdin.write(msg);
|
|
50391
|
-
}
|
|
50392
|
-
parseOutput(data) {
|
|
50393
|
-
this.buffer += data;
|
|
50394
|
-
const lines = this.buffer.split(`
|
|
50395
|
-
`);
|
|
50396
|
-
this.buffer = lines.pop() || "";
|
|
50397
|
-
for (const line of lines) {
|
|
50398
|
-
const trimmed = line.trim();
|
|
50399
|
-
if (!trimmed)
|
|
50400
|
-
continue;
|
|
50401
|
-
let event;
|
|
50402
|
-
try {
|
|
50403
|
-
event = JSON.parse(trimmed);
|
|
50404
|
-
} catch {
|
|
50405
|
-
continue;
|
|
50406
|
-
}
|
|
50407
|
-
try {
|
|
50408
|
-
this.emit("event", event);
|
|
50409
|
-
} catch (err) {
|
|
50410
|
-
this.log.error(`'event' listener threw while handling a ${event.type} event: ${err}`);
|
|
50411
|
-
}
|
|
50412
|
-
if (event.type === "result" && isErrorResultEvent(event)) {
|
|
50413
|
-
this.maybeEmitRateLimit(trimmed);
|
|
50414
|
-
}
|
|
50415
|
-
if (event.type === "rate_limit_event") {
|
|
50416
|
-
this.maybeEmitRateLimitHit(parseRateLimitEvent(event));
|
|
50417
|
-
}
|
|
50418
|
-
}
|
|
50419
|
-
}
|
|
50420
|
-
maybeEmitRateLimit(text) {
|
|
50421
|
-
this.maybeEmitRateLimitHit(detectRateLimit(text));
|
|
50422
|
-
}
|
|
50423
|
-
maybeEmitRateLimitHit(hit) {
|
|
50424
|
-
if (!hit.detected)
|
|
50425
|
-
return;
|
|
50426
|
-
if (!hit.resetAtEpochMs && this.lastEmittedHitHadExplicitReset && this.lastEmittedRateLimitDeadline > Date.now()) {
|
|
50427
|
-
return;
|
|
50428
|
-
}
|
|
50429
|
-
const newDeadline = cooldownDeadline(hit);
|
|
50430
|
-
const MIN_ADVANCE_MS = 60000;
|
|
50431
|
-
if (newDeadline - this.lastEmittedRateLimitDeadline < MIN_ADVANCE_MS) {
|
|
50432
|
-
if (hit.resetAtEpochMs !== undefined) {
|
|
50433
|
-
this.lastEmittedHitHadExplicitReset = true;
|
|
50434
|
-
}
|
|
50435
|
-
return;
|
|
50436
|
-
}
|
|
50437
|
-
this.lastEmittedRateLimitDeadline = newDeadline;
|
|
50438
|
-
this.lastEmittedHitHadExplicitReset = hit.resetAtEpochMs !== undefined;
|
|
50439
|
-
this.log.warn(`Rate limit detected: ${hit.matched ?? "(no match text)"}`);
|
|
50440
|
-
this.emit("rate-limit", hit);
|
|
50441
|
-
}
|
|
50442
|
-
isRunning() {
|
|
50443
|
-
return this.process !== null;
|
|
50444
|
-
}
|
|
50445
|
-
getLastStderr() {
|
|
50446
|
-
return this.stderrBuffer;
|
|
50447
|
-
}
|
|
50448
|
-
isPermanentFailure() {
|
|
50449
|
-
const stderr = this.stderrBuffer;
|
|
50450
|
-
if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
|
|
50451
|
-
return true;
|
|
50452
|
-
}
|
|
50453
|
-
if (stderr.includes("No conversation found with session ID")) {
|
|
50454
|
-
return true;
|
|
50455
|
-
}
|
|
50456
|
-
return false;
|
|
50457
|
-
}
|
|
50458
|
-
getPermanentFailureReason() {
|
|
50459
|
-
const stderr = this.stderrBuffer;
|
|
50460
|
-
if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
|
|
50461
|
-
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.";
|
|
50462
|
-
}
|
|
50463
|
-
if (stderr.includes("No conversation found with session ID")) {
|
|
50464
|
-
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.";
|
|
50465
|
-
}
|
|
50466
|
-
return null;
|
|
50467
|
-
}
|
|
50468
|
-
kill() {
|
|
50469
|
-
this.stopStatusWatch();
|
|
50470
|
-
if (!this.process) {
|
|
50471
|
-
this.log.debug("Kill called but process not running");
|
|
50472
|
-
return Promise.resolve();
|
|
50473
|
-
}
|
|
50474
|
-
const proc = this.process;
|
|
50475
|
-
const pid = proc.pid;
|
|
50476
|
-
this.process = null;
|
|
50477
|
-
this.log.debug(`Killing Claude process (pid=${pid})`);
|
|
50478
|
-
if (process.env.INTEGRATION_TEST === "1") {
|
|
50479
|
-
const stack = new Error().stack?.split(`
|
|
50480
|
-
`).slice(2, 7).join(" > ").replace(/\s+at\s+/g, " < ") ?? "?";
|
|
50481
|
-
process.stderr.write(`[claude-cli kill pid=${pid}] | ${stack}
|
|
50482
|
-
`);
|
|
50483
|
-
}
|
|
50484
|
-
return new Promise((resolve3) => {
|
|
50485
|
-
this.log.debug("Sending first SIGINT");
|
|
50486
|
-
proc.kill("SIGINT");
|
|
50487
|
-
const secondSigint = setTimeout(() => {
|
|
50488
|
-
try {
|
|
50489
|
-
this.log.debug("Sending second SIGINT");
|
|
50490
|
-
proc.kill("SIGINT");
|
|
50491
|
-
} catch {}
|
|
50492
|
-
}, 100);
|
|
50493
|
-
const forceKillTimeout = setTimeout(() => {
|
|
50494
|
-
try {
|
|
50495
|
-
this.log.debug("Sending SIGTERM (force kill)");
|
|
50496
|
-
proc.kill("SIGTERM");
|
|
50497
|
-
} catch {}
|
|
50498
|
-
}, 2000);
|
|
50499
|
-
const settle = (reason) => {
|
|
50500
|
-
this.log.debug(`Claude process gone (${reason})`);
|
|
50501
|
-
clearTimeout(secondSigint);
|
|
50502
|
-
clearTimeout(forceKillTimeout);
|
|
50503
|
-
clearTimeout(lastResort);
|
|
50504
|
-
resolve3();
|
|
50505
|
-
};
|
|
50506
|
-
const lastResort = setTimeout(() => {
|
|
50507
|
-
try {
|
|
50508
|
-
this.log.warn("Claude process did not exit after SIGTERM — sending SIGKILL");
|
|
50509
|
-
proc.kill("SIGKILL");
|
|
50510
|
-
} catch {}
|
|
50511
|
-
settle("kill timeout");
|
|
50512
|
-
}, 5000);
|
|
50513
|
-
proc.once("close", (code) => settle(`closed, code=${code}`));
|
|
50514
|
-
proc.once("error", () => settle("spawn error"));
|
|
50515
|
-
});
|
|
50516
|
-
}
|
|
50517
|
-
interrupt() {
|
|
50518
|
-
if (!this.process) {
|
|
50519
|
-
this.log.debug("Interrupt called but process not running");
|
|
50520
|
-
return false;
|
|
50521
|
-
}
|
|
50522
|
-
this.log.debug(`Interrupting Claude process (pid=${this.process.pid})`);
|
|
50523
|
-
this.process.kill("SIGINT");
|
|
50524
|
-
return true;
|
|
50525
|
-
}
|
|
50526
|
-
buildChildEnv() {
|
|
50527
|
-
return buildClaudeChildEnv(process.env, this.options.account, {
|
|
50528
|
-
decisionBridge: this.options.decisionBridgePath !== undefined,
|
|
50529
|
-
disableAutoMemory: this.options.memory === null
|
|
50530
|
-
});
|
|
50531
|
-
}
|
|
50532
|
-
getMcpServerPath() {
|
|
50533
|
-
const __filename2 = fileURLToPath(import.meta.url);
|
|
50534
|
-
const __dirname2 = dirname2(__filename2);
|
|
50535
|
-
const bundledPath = resolve2(__dirname2, "mcp", "mcp-server.js");
|
|
50536
|
-
if (existsSync3(bundledPath)) {
|
|
50537
|
-
return bundledPath;
|
|
50538
|
-
}
|
|
50539
|
-
const sourceLayoutPath = resolve2(__dirname2, "..", "mcp", "mcp-server.js");
|
|
50540
|
-
if (existsSync3(sourceLayoutPath)) {
|
|
50541
|
-
return sourceLayoutPath;
|
|
50542
|
-
}
|
|
50543
|
-
const tsPath = resolve2(__dirname2, "..", "mcp", "mcp-server.ts");
|
|
50544
|
-
if (existsSync3(tsPath)) {
|
|
50545
|
-
return tsPath;
|
|
50546
|
-
}
|
|
50547
|
-
return sourceLayoutPath;
|
|
50548
|
-
}
|
|
50549
|
-
getStatusLineWriterPath() {
|
|
50550
|
-
const __filename2 = fileURLToPath(import.meta.url);
|
|
50551
|
-
const __dirname2 = dirname2(__filename2);
|
|
50552
|
-
const bundledPath = resolve2(__dirname2, "statusline", "writer.js");
|
|
50553
|
-
if (existsSync3(bundledPath)) {
|
|
50554
|
-
return bundledPath;
|
|
50555
|
-
}
|
|
50556
|
-
const sourceLayoutPath = resolve2(__dirname2, "..", "statusline", "writer.js");
|
|
50557
|
-
if (existsSync3(sourceLayoutPath)) {
|
|
50558
|
-
return sourceLayoutPath;
|
|
50559
|
-
}
|
|
50560
|
-
const tsPath = resolve2(__dirname2, "..", "statusline", "writer.ts");
|
|
50561
|
-
if (existsSync3(tsPath)) {
|
|
50562
|
-
return tsPath;
|
|
50563
|
-
}
|
|
50564
|
-
return sourceLayoutPath;
|
|
50565
|
-
}
|
|
50566
|
-
}
|
|
50711
|
+
// src/session/lifecycle.ts
|
|
50712
|
+
init_cli();
|
|
50713
|
+
init_rate_limit_detector();
|
|
50567
50714
|
|
|
50568
50715
|
// src/persistence/session-store.ts
|
|
50569
|
-
import { existsSync as
|
|
50716
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3 } from "fs";
|
|
50570
50717
|
|
|
50571
50718
|
// src/persistence/atomic-file.ts
|
|
50572
50719
|
import { chmodSync as chmodSync3, renameSync, writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -50641,13 +50788,13 @@ class SessionStore {
|
|
|
50641
50788
|
this.sessionsFile = DEFAULT_SESSIONS_FILE;
|
|
50642
50789
|
this.configDir = DEFAULT_CONFIG_DIR;
|
|
50643
50790
|
}
|
|
50644
|
-
if (!
|
|
50791
|
+
if (!existsSync5(this.configDir)) {
|
|
50645
50792
|
mkdirSync3(this.configDir, { recursive: true });
|
|
50646
50793
|
}
|
|
50647
50794
|
}
|
|
50648
50795
|
load() {
|
|
50649
50796
|
const sessions = new Map;
|
|
50650
|
-
if (!
|
|
50797
|
+
if (!existsSync5(this.sessionsFile)) {
|
|
50651
50798
|
log4.debug("No sessions file found");
|
|
50652
50799
|
return sessions;
|
|
50653
50800
|
}
|
|
@@ -50858,7 +51005,7 @@ class SessionStore {
|
|
|
50858
51005
|
}
|
|
50859
51006
|
lastReadDegraded = false;
|
|
50860
51007
|
loadRaw() {
|
|
50861
|
-
if (!
|
|
51008
|
+
if (!existsSync5(this.sessionsFile)) {
|
|
50862
51009
|
this.lastReadDegraded = false;
|
|
50863
51010
|
return { version: STORE_VERSION, sessions: {} };
|
|
50864
51011
|
}
|
|
@@ -50903,7 +51050,7 @@ class SessionStore {
|
|
|
50903
51050
|
|
|
50904
51051
|
// src/persistence/thread-logger.ts
|
|
50905
51052
|
init_logger();
|
|
50906
|
-
import { existsSync as
|
|
51053
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, appendFileSync, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync2, rmdirSync, readFileSync as readFileSync4, chmodSync as chmodSync4 } from "fs";
|
|
50907
51054
|
import { homedir as homedir4 } from "os";
|
|
50908
51055
|
import { join as join6, dirname as dirname3 } from "path";
|
|
50909
51056
|
var log5 = createLogger("thread-log");
|
|
@@ -50930,7 +51077,7 @@ class ThreadLoggerImpl {
|
|
|
50930
51077
|
this.logPath = join6(LOGS_BASE_DIR, platformId, `${claudeSessionId}.jsonl`);
|
|
50931
51078
|
if (this.enabled) {
|
|
50932
51079
|
const dir = dirname3(this.logPath);
|
|
50933
|
-
if (!
|
|
51080
|
+
if (!existsSync6(dir)) {
|
|
50934
51081
|
mkdirSync4(dir, { recursive: true });
|
|
50935
51082
|
}
|
|
50936
51083
|
this.flushTimer = setInterval(() => {
|
|
@@ -51065,7 +51212,7 @@ class ThreadLoggerImpl {
|
|
|
51065
51212
|
const lines = this.buffer.map((entry) => JSON.stringify(entry)).join(`
|
|
51066
51213
|
`) + `
|
|
51067
51214
|
`;
|
|
51068
|
-
const isNewFile = !
|
|
51215
|
+
const isNewFile = !existsSync6(this.logPath);
|
|
51069
51216
|
appendFileSync(this.logPath, lines, { encoding: "utf8", mode: 384 });
|
|
51070
51217
|
if (isNewFile) {
|
|
51071
51218
|
chmodSync4(this.logPath, 384);
|
|
@@ -51103,7 +51250,7 @@ function createThreadLogger(platformId, threadId, claudeSessionId, options) {
|
|
|
51103
51250
|
function cleanupOldLogs(retentionDays = 30) {
|
|
51104
51251
|
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
|
51105
51252
|
let deletedCount = 0;
|
|
51106
|
-
if (!
|
|
51253
|
+
if (!existsSync6(LOGS_BASE_DIR)) {
|
|
51107
51254
|
return 0;
|
|
51108
51255
|
}
|
|
51109
51256
|
try {
|
|
@@ -51151,7 +51298,7 @@ function getLogFilePath(platformId, sessionId) {
|
|
|
51151
51298
|
function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
|
|
51152
51299
|
const logPath = getLogFilePath(platformId, sessionId);
|
|
51153
51300
|
log5.debug(`Reading log entries from: ${logPath}`);
|
|
51154
|
-
if (!
|
|
51301
|
+
if (!existsSync6(logPath)) {
|
|
51155
51302
|
log5.debug(`Log file does not exist: ${logPath}`);
|
|
51156
51303
|
return [];
|
|
51157
51304
|
}
|
|
@@ -51178,7 +51325,7 @@ function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
|
|
|
51178
51325
|
}
|
|
51179
51326
|
|
|
51180
51327
|
// src/version.ts
|
|
51181
|
-
import { readFileSync as readFileSync5, existsSync as
|
|
51328
|
+
import { readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
|
|
51182
51329
|
import { dirname as dirname4, resolve as resolve3 } from "path";
|
|
51183
51330
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
51184
51331
|
var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
|
|
@@ -51189,7 +51336,7 @@ function loadPackageJson() {
|
|
|
51189
51336
|
resolve3(process.cwd(), "package.json")
|
|
51190
51337
|
];
|
|
51191
51338
|
for (const candidate of candidates) {
|
|
51192
|
-
if (
|
|
51339
|
+
if (existsSync7(candidate)) {
|
|
51193
51340
|
try {
|
|
51194
51341
|
const pkg = JSON.parse(readFileSync5(candidate, "utf-8"));
|
|
51195
51342
|
if (pkg.name === "claude-threads") {
|
|
@@ -51639,7 +51786,7 @@ ${formatter.formatBold("Reactions:")}
|
|
|
51639
51786
|
}
|
|
51640
51787
|
|
|
51641
51788
|
// src/changelog.ts
|
|
51642
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
51789
|
+
import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
|
|
51643
51790
|
import { dirname as dirname5, resolve as resolve4 } from "path";
|
|
51644
51791
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
51645
51792
|
var __dirname3 = dirname5(fileURLToPath3(import.meta.url));
|
|
@@ -51650,7 +51797,7 @@ function getReleaseNotes(version) {
|
|
|
51650
51797
|
];
|
|
51651
51798
|
let changelogPath = null;
|
|
51652
51799
|
for (const p of possiblePaths) {
|
|
51653
|
-
if (
|
|
51800
|
+
if (existsSync8(p)) {
|
|
51654
51801
|
changelogPath = p;
|
|
51655
51802
|
break;
|
|
51656
51803
|
}
|
|
@@ -52345,7 +52492,7 @@ ${avoidCommands.map((c) => `- \`!${c.command}\` - ${c.reason}`).join(`
|
|
|
52345
52492
|
}
|
|
52346
52493
|
// src/session/lifecycle.ts
|
|
52347
52494
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
52348
|
-
import { existsSync as
|
|
52495
|
+
import { existsSync as existsSync13 } from "fs";
|
|
52349
52496
|
|
|
52350
52497
|
// src/utils/keep-alive.ts
|
|
52351
52498
|
init_logger();
|
|
@@ -52800,6 +52947,9 @@ function updateLastMessage(session, post2) {
|
|
|
52800
52947
|
}
|
|
52801
52948
|
}
|
|
52802
52949
|
|
|
52950
|
+
// src/operations/commands/handler.ts
|
|
52951
|
+
init_cli();
|
|
52952
|
+
|
|
52803
52953
|
// src/operations/streaming/handler.ts
|
|
52804
52954
|
import { lstat, mkdir as mkdir2, mkdtemp, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
52805
52955
|
import { tmpdir as tmpdir3 } from "os";
|
|
@@ -53146,7 +53296,7 @@ function buildRestartCliOptions(session, ctx) {
|
|
|
53146
53296
|
// src/operations/commands/handler.ts
|
|
53147
53297
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
53148
53298
|
import { resolve as resolve6 } from "path";
|
|
53149
|
-
import { existsSync as
|
|
53299
|
+
import { existsSync as existsSync12, statSync as statSync4 } from "fs";
|
|
53150
53300
|
|
|
53151
53301
|
// node_modules/update-notifier/update-notifier.js
|
|
53152
53302
|
import process10 from "node:process";
|
|
@@ -62129,6 +62279,10 @@ async function buildStatusBar(sessionCount, config, formatter, platformId) {
|
|
|
62129
62279
|
}
|
|
62130
62280
|
items.push(formatter.formatCode(label));
|
|
62131
62281
|
}
|
|
62282
|
+
if (config.connectorsOff && config.connectorsOff > 0) {
|
|
62283
|
+
const n = config.connectorsOff;
|
|
62284
|
+
items.push(formatter.formatCode(`\uD83D\uDD0C ${n} claude.ai connector${n === 1 ? "" : "s"} off`));
|
|
62285
|
+
}
|
|
62132
62286
|
items.push(formatter.formatCode(permissionModeDisplay(config.permissionMode).chip));
|
|
62133
62287
|
if (config.worktreeMode === "require") {
|
|
62134
62288
|
items.push(formatter.formatCode("\uD83C\uDF3F Worktree: require"));
|
|
@@ -62537,7 +62691,7 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
|
|
|
62537
62691
|
// src/memory/store.ts
|
|
62538
62692
|
import { createHash } from "crypto";
|
|
62539
62693
|
import {
|
|
62540
|
-
existsSync as
|
|
62694
|
+
existsSync as existsSync9,
|
|
62541
62695
|
mkdirSync as mkdirSync5,
|
|
62542
62696
|
readFileSync as readFileSync7,
|
|
62543
62697
|
realpathSync
|
|
@@ -62749,7 +62903,7 @@ _(older entries omitted — \`!memory\` shows all)_` : rendered;
|
|
|
62749
62903
|
}
|
|
62750
62904
|
loadLines(platformId) {
|
|
62751
62905
|
const file = this.channelMemoryPath(platformId);
|
|
62752
|
-
if (!
|
|
62906
|
+
if (!existsSync9(file))
|
|
62753
62907
|
return [];
|
|
62754
62908
|
const raw = readFileSync7(file, "utf-8");
|
|
62755
62909
|
const lines = [];
|
|
@@ -62791,7 +62945,7 @@ _(older entries omitted — \`!memory\` shows all)_` : rendered;
|
|
|
62791
62945
|
writeFileAtomic(file, content);
|
|
62792
62946
|
}
|
|
62793
62947
|
ensureDir(dir) {
|
|
62794
|
-
if (!
|
|
62948
|
+
if (!existsSync9(dir)) {
|
|
62795
62949
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
62796
62950
|
}
|
|
62797
62951
|
}
|
|
@@ -62938,7 +63092,7 @@ import { join as join12 } from "path";
|
|
|
62938
63092
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
62939
63093
|
|
|
62940
63094
|
// src/persistence/platform-list-store.ts
|
|
62941
|
-
import { existsSync as
|
|
63095
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
|
|
62942
63096
|
import { homedir as homedir7 } from "os";
|
|
62943
63097
|
import { join as join11 } from "path";
|
|
62944
63098
|
var STORES_CONFIG_DIR = join11(homedir7(), ".config", "claude-threads");
|
|
@@ -63015,7 +63169,7 @@ class PlatformListStore {
|
|
|
63015
63169
|
return this.queue.run(fn);
|
|
63016
63170
|
}
|
|
63017
63171
|
loadRaw(forWrite = false) {
|
|
63018
|
-
if (!
|
|
63172
|
+
if (!existsSync10(this.file)) {
|
|
63019
63173
|
this.cache = null;
|
|
63020
63174
|
return { version: STORE_VERSION2, items: {} };
|
|
63021
63175
|
}
|
|
@@ -63712,6 +63866,7 @@ async function suggestBranchNames(workingDir, userMessage) {
|
|
|
63712
63866
|
|
|
63713
63867
|
// src/operations/worktree/handler.ts
|
|
63714
63868
|
init_worktree();
|
|
63869
|
+
init_cli();
|
|
63715
63870
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
63716
63871
|
init_logger();
|
|
63717
63872
|
var log26 = createLogger("worktree");
|
|
@@ -64410,6 +64565,24 @@ function handleEventPreProcessing(session, event, ctx) {
|
|
|
64410
64565
|
}
|
|
64411
64566
|
if (event.type === "system") {
|
|
64412
64567
|
const e = event;
|
|
64568
|
+
if (e.subtype === "init" && Array.isArray(e.mcp_servers)) {
|
|
64569
|
+
const summary = [...e.mcp_servers].sort((a, b) => (a.name ?? "").localeCompare(b.name ?? "")).map((s) => `${s.name ?? "?"} (${s.status ?? "unknown"})`).join(", ") || "none";
|
|
64570
|
+
if (session.mcpServersSummary !== summary) {
|
|
64571
|
+
session.mcpServersSummary = summary;
|
|
64572
|
+
sessionLog6(session).info(`MCP servers: ${summary}`);
|
|
64573
|
+
const down = e.mcp_servers.filter((s) => s.status !== "connected" && s.status !== "pending");
|
|
64574
|
+
if (down.length > 0) {
|
|
64575
|
+
sessionLog6(session).warn(`MCP servers not connected: ${down.map((s) => `${s.name ?? "?"} (${s.status ?? "unknown"})`).join(", ")}`);
|
|
64576
|
+
}
|
|
64577
|
+
const connectors = e.mcp_servers.filter((s) => (s.name ?? "").startsWith("claude.ai "));
|
|
64578
|
+
if (connectors.length > 0 && session.platform.getMcpConfig().claudeAiConnectors !== true) {
|
|
64579
|
+
const names = connectors.map((s) => s.name).join(", ");
|
|
64580
|
+
sessionLog6(session).warn(`claude.ai connectors are active although claudeAiConnectors is off: ${names}. ` + `This Claude CLI ignores both disableClaudeAiConnectors and ENABLE_CLAUDEAI_MCP_SERVERS; upgrade it.`);
|
|
64581
|
+
const f = session.platform.getFormatter();
|
|
64582
|
+
withErrorHandling(() => post(session, "warning", `${f.formatBold("claude.ai connectors are active in this session")} (${names}) although ` + `${f.formatCode("claudeAiConnectors")} is off for this platform. This Claude CLI version ignores the ` + `switch; upgrade the CLI on the bot's machine.`), { action: "Post connector warning", session });
|
|
64583
|
+
}
|
|
64584
|
+
}
|
|
64585
|
+
}
|
|
64413
64586
|
if (e.subtype === "init" && typeof e.model === "string") {
|
|
64414
64587
|
session.currentModel = e.model;
|
|
64415
64588
|
}
|
|
@@ -65110,7 +65283,7 @@ async function suggestSessionMetadata(context) {
|
|
|
65110
65283
|
init_quick_query();
|
|
65111
65284
|
|
|
65112
65285
|
// src/persistence/github-emails-store.ts
|
|
65113
|
-
import { existsSync as
|
|
65286
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync9 } from "fs";
|
|
65114
65287
|
import { homedir as homedir8 } from "os";
|
|
65115
65288
|
import { join as join14 } from "path";
|
|
65116
65289
|
init_logger();
|
|
@@ -65136,7 +65309,7 @@ class GitHubEmailsStore {
|
|
|
65136
65309
|
this.file = DEFAULT_FILE3;
|
|
65137
65310
|
this.configDir = DEFAULT_CONFIG_DIR2;
|
|
65138
65311
|
}
|
|
65139
|
-
if (!
|
|
65312
|
+
if (!existsSync11(this.configDir)) {
|
|
65140
65313
|
mkdirSync7(this.configDir, { recursive: true });
|
|
65141
65314
|
}
|
|
65142
65315
|
}
|
|
@@ -65171,7 +65344,7 @@ class GitHubEmailsStore {
|
|
|
65171
65344
|
}
|
|
65172
65345
|
lastReadDegraded = false;
|
|
65173
65346
|
loadRaw() {
|
|
65174
|
-
if (!
|
|
65347
|
+
if (!existsSync11(this.file)) {
|
|
65175
65348
|
this.lastReadDegraded = false;
|
|
65176
65349
|
return { version: STORE_VERSION3, emails: {} };
|
|
65177
65350
|
}
|
|
@@ -65377,7 +65550,7 @@ async function changeDirectory(session, newDir, username, ctx) {
|
|
|
65377
65550
|
const expandedDir = newDir.startsWith("~") ? newDir.replace("~", process.env.HOME || "") : newDir;
|
|
65378
65551
|
const absoluteDir = resolve6(expandedDir);
|
|
65379
65552
|
const formatter = session.platform.getFormatter();
|
|
65380
|
-
if (!
|
|
65553
|
+
if (!existsSync12(absoluteDir)) {
|
|
65381
65554
|
await postError(session, `Directory does not exist: ${formatter.formatCode(newDir)}`);
|
|
65382
65555
|
sessionLog8(session).warn(`\uD83D\uDCC2 Directory does not exist: ${newDir}`);
|
|
65383
65556
|
return;
|
|
@@ -65727,6 +65900,16 @@ async function updateSessionHeader(session, ctx) {
|
|
|
65727
65900
|
const label = account?.displayName ?? session.claudeAccountId;
|
|
65728
65901
|
items.push(["\uD83D\uDD11", "Claude account", formatter.formatCode(label)]);
|
|
65729
65902
|
}
|
|
65903
|
+
{
|
|
65904
|
+
const mcp = session.platform.getMcpConfig();
|
|
65905
|
+
const posture = [];
|
|
65906
|
+
if (mcp.claudeAiConnectors === true)
|
|
65907
|
+
posture.push(`claude.ai connectors ${formatter.formatBold("on")}`);
|
|
65908
|
+
if (mcp.strictMcpConfig === true)
|
|
65909
|
+
posture.push("strict (declared servers only)");
|
|
65910
|
+
if (posture.length > 0)
|
|
65911
|
+
items.push(["\uD83D\uDD0C", "MCP", posture.join(", ")]);
|
|
65912
|
+
}
|
|
65730
65913
|
items.push(["\uD83C\uDD94", "Session ID", formatter.formatCode(session.claudeSessionId.substring(0, 8))]);
|
|
65731
65914
|
const logPath = getLogFilePath(session.platform.platformId, session.claudeSessionId);
|
|
65732
65915
|
const shortLogPath = logPath.replace(process.env.HOME || "", "~");
|
|
@@ -66907,7 +67090,7 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
|
|
|
66907
67090
|
const { resolve: resolve7 } = await import("path");
|
|
66908
67091
|
const requestedDir = initialOptions.workingDir.startsWith("~") ? initialOptions.workingDir.replace("~", process.env.HOME || "") : initialOptions.workingDir;
|
|
66909
67092
|
const resolvedDir = resolve7(requestedDir);
|
|
66910
|
-
if (!
|
|
67093
|
+
if (!existsSync13(resolvedDir)) {
|
|
66911
67094
|
const msg = `❌ Directory does not exist: ${formatter.formatCode(initialOptions.workingDir)}`;
|
|
66912
67095
|
if (startPost) {
|
|
66913
67096
|
await platform.updatePost(startPost.id, msg);
|
|
@@ -67147,7 +67330,7 @@ async function resumeSessionImpl(state, ctx, resumedBy) {
|
|
|
67147
67330
|
log37.warn(`Max sessions reached, skipping resume for ${shortId}...`);
|
|
67148
67331
|
return;
|
|
67149
67332
|
}
|
|
67150
|
-
if (!
|
|
67333
|
+
if (!existsSync13(state.workingDir)) {
|
|
67151
67334
|
log37.warn(`Working directory ${state.workingDir} no longer exists, skipping resume for ${shortId}...`);
|
|
67152
67335
|
ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
|
|
67153
67336
|
const resumeFormatter = platform.getFormatter();
|
|
@@ -67475,7 +67658,10 @@ async function handleExit(sessionId, code, ctx, source) {
|
|
|
67475
67658
|
auditReason: "early-exit"
|
|
67476
67659
|
});
|
|
67477
67660
|
const earlyExitFormatter = session.platform.getFormatter();
|
|
67478
|
-
|
|
67661
|
+
const lastStderr = typeof session.claude?.getLastStderr === "function" ? session.claude.getLastStderr() : "";
|
|
67662
|
+
const strictRefused = lastStderr.includes("You cannot use --strict-mcp-config");
|
|
67663
|
+
const earlyExitText = strictRefused ? `${earlyExitFormatter.formatBold("Session ended")} before Claude could respond: the Claude CLI refuses ${earlyExitFormatter.formatCode("--strict-mcp-config")} because an enterprise-managed MCP config is present. Set ${earlyExitFormatter.formatCode("strictMcpConfig: false")} on this platform and restart the bot.` : `${earlyExitFormatter.formatBold("Session ended")} before Claude could respond (exit code ${code}). Please start a new session.`;
|
|
67664
|
+
await withErrorHandling(() => post(session, "warning", earlyExitText), { action: "Post early exit notification", session });
|
|
67479
67665
|
sessionLog11(session).info(`⚠ Session ended early (exit code ${code})`);
|
|
67480
67666
|
await ctx.ops.updateStickyMessage();
|
|
67481
67667
|
return;
|
|
@@ -67841,7 +68027,7 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
67841
68027
|
|
|
67842
68028
|
// src/onboarding.ts
|
|
67843
68029
|
var import_prompts = __toESM(require_prompts3(), 1);
|
|
67844
|
-
import { existsSync as
|
|
68030
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10 } from "fs";
|
|
67845
68031
|
import { join as join15, dirname as dirname8 } from "path";
|
|
67846
68032
|
import { spawn as spawn3 } from "child_process";
|
|
67847
68033
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
@@ -68066,7 +68252,7 @@ async function runOnboarding(reconfigure = false) {
|
|
|
68066
68252
|
console.log(dim(" ─────────────────────────────────"));
|
|
68067
68253
|
console.log("");
|
|
68068
68254
|
let existingConfig = null;
|
|
68069
|
-
if (reconfigure &&
|
|
68255
|
+
if (reconfigure && existsSync14(CONFIG_PATH)) {
|
|
68070
68256
|
try {
|
|
68071
68257
|
const content = readFileSync10(CONFIG_PATH, "utf-8");
|
|
68072
68258
|
existingConfig = yaml.load(content);
|
|
@@ -69520,6 +69706,9 @@ class MattermostClient extends BasePlatformClient {
|
|
|
69520
69706
|
channelId;
|
|
69521
69707
|
directMessages;
|
|
69522
69708
|
outboundFiles;
|
|
69709
|
+
mcpServers;
|
|
69710
|
+
strictMcpConfig;
|
|
69711
|
+
claudeAiConnectors;
|
|
69523
69712
|
userCache = new Map;
|
|
69524
69713
|
botUserId = null;
|
|
69525
69714
|
formatter = new MattermostFormatter;
|
|
@@ -69535,6 +69724,9 @@ class MattermostClient extends BasePlatformClient {
|
|
|
69535
69724
|
this.botName = platformConfig.botName;
|
|
69536
69725
|
this.allowedUsers = platformConfig.allowedUsers;
|
|
69537
69726
|
this.outboundFiles = platformConfig.outboundFiles;
|
|
69727
|
+
this.mcpServers = platformConfig.mcpServers;
|
|
69728
|
+
this.strictMcpConfig = platformConfig.strictMcpConfig;
|
|
69729
|
+
this.claudeAiConnectors = platformConfig.claudeAiConnectors;
|
|
69538
69730
|
this.directChannelMode = resolveDirectChannelMode(platformConfig.directChannelMode);
|
|
69539
69731
|
this.approvals = platformConfig.approvals;
|
|
69540
69732
|
this.ackReaction = normalizeAckReaction(platformConfig.ackReaction, `platforms[${platformConfig.id}].ackReaction`);
|
|
@@ -69978,7 +70170,10 @@ class MattermostClient extends BasePlatformClient {
|
|
|
69978
70170
|
token: this.token,
|
|
69979
70171
|
channelId: this.channelId,
|
|
69980
70172
|
allowedUsers: this.allowedUsers,
|
|
69981
|
-
outboundFiles: this.outboundFiles
|
|
70173
|
+
outboundFiles: this.outboundFiles,
|
|
70174
|
+
mcpServers: this.mcpServers,
|
|
70175
|
+
strictMcpConfig: this.strictMcpConfig,
|
|
70176
|
+
claudeAiConnectors: this.claudeAiConnectors
|
|
69982
70177
|
};
|
|
69983
70178
|
}
|
|
69984
70179
|
getFormatter() {
|
|
@@ -70190,6 +70385,9 @@ class SlackClient extends BasePlatformClient {
|
|
|
70190
70385
|
rateLimitDelay = 0;
|
|
70191
70386
|
rateLimitRetryAfter = 0;
|
|
70192
70387
|
outboundFiles;
|
|
70388
|
+
mcpServers;
|
|
70389
|
+
strictMcpConfig;
|
|
70390
|
+
claudeAiConnectors;
|
|
70193
70391
|
statusSentAt = new Map;
|
|
70194
70392
|
formatter = new SlackFormatter;
|
|
70195
70393
|
channelClients = new Map;
|
|
@@ -70208,6 +70406,9 @@ class SlackClient extends BasePlatformClient {
|
|
|
70208
70406
|
this.allowedUsers = platformConfig.allowedUsers;
|
|
70209
70407
|
this.apiUrl = platformConfig.apiUrl || "https://slack.com/api";
|
|
70210
70408
|
this.outboundFiles = platformConfig.outboundFiles;
|
|
70409
|
+
this.mcpServers = platformConfig.mcpServers;
|
|
70410
|
+
this.strictMcpConfig = platformConfig.strictMcpConfig;
|
|
70411
|
+
this.claudeAiConnectors = platformConfig.claudeAiConnectors;
|
|
70211
70412
|
this.directChannelMode = resolveDirectChannelMode(platformConfig.directChannelMode);
|
|
70212
70413
|
this.approvals = platformConfig.approvals;
|
|
70213
70414
|
this.ackReaction = normalizeAckReaction(platformConfig.ackReaction, `platforms[${platformConfig.id}].ackReaction`);
|
|
@@ -70689,7 +70890,10 @@ class SlackClient extends BasePlatformClient {
|
|
|
70689
70890
|
channelId: this.channelId,
|
|
70690
70891
|
allowedUsers: this.allowedUsers,
|
|
70691
70892
|
appToken: this.appToken,
|
|
70692
|
-
outboundFiles: this.outboundFiles
|
|
70893
|
+
outboundFiles: this.outboundFiles,
|
|
70894
|
+
mcpServers: this.mcpServers,
|
|
70895
|
+
strictMcpConfig: this.strictMcpConfig,
|
|
70896
|
+
claudeAiConnectors: this.claudeAiConnectors
|
|
70693
70897
|
};
|
|
70694
70898
|
}
|
|
70695
70899
|
getFormatter() {
|
|
@@ -72221,6 +72425,7 @@ init_logger();
|
|
|
72221
72425
|
// src/claude/usage-probe.ts
|
|
72222
72426
|
init_spawn();
|
|
72223
72427
|
init_version_check();
|
|
72428
|
+
init_cli();
|
|
72224
72429
|
init_logger();
|
|
72225
72430
|
var log47 = createLogger("usage-probe");
|
|
72226
72431
|
var DEFAULT_USAGE_PROBE_TIMEOUT_MS = 30000;
|
|
@@ -72474,13 +72679,91 @@ class AccountPool {
|
|
|
72474
72679
|
}
|
|
72475
72680
|
}
|
|
72476
72681
|
|
|
72682
|
+
// src/claude/connector-probe.ts
|
|
72683
|
+
init_spawn();
|
|
72684
|
+
init_version_check();
|
|
72685
|
+
init_cli();
|
|
72686
|
+
init_logger();
|
|
72687
|
+
var log49 = createLogger("connector-probe");
|
|
72688
|
+
var CONNECTOR_PREFIX = "claude.ai ";
|
|
72689
|
+
var DEFAULT_CONNECTOR_PROBE_TIMEOUT_MS = 15000;
|
|
72690
|
+
function parseConnectorNames(stdout) {
|
|
72691
|
+
for (const line of stdout.split(`
|
|
72692
|
+
`)) {
|
|
72693
|
+
const trimmed = line.trim();
|
|
72694
|
+
if (!trimmed.startsWith("{"))
|
|
72695
|
+
continue;
|
|
72696
|
+
let event;
|
|
72697
|
+
try {
|
|
72698
|
+
event = JSON.parse(trimmed);
|
|
72699
|
+
} catch {
|
|
72700
|
+
continue;
|
|
72701
|
+
}
|
|
72702
|
+
if (event.type !== "system" || event.subtype !== "init")
|
|
72703
|
+
continue;
|
|
72704
|
+
return (event.mcp_servers ?? []).map((s) => s.name ?? "").filter((name) => name.startsWith(CONNECTOR_PREFIX)).map((name) => name.slice(CONNECTOR_PREFIX.length));
|
|
72705
|
+
}
|
|
72706
|
+
return null;
|
|
72707
|
+
}
|
|
72708
|
+
async function probeClaudeAiConnectors(account, opts = {}) {
|
|
72709
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_CONNECTOR_PROBE_TIMEOUT_MS;
|
|
72710
|
+
const label = account?.id ?? "default account";
|
|
72711
|
+
const claudePath = getClaudePath();
|
|
72712
|
+
const env4 = buildClaudeChildEnv(process.env, account, { claudeAiConnectors: true });
|
|
72713
|
+
return new Promise((resolve7) => {
|
|
72714
|
+
let settled = false;
|
|
72715
|
+
let stdout = "";
|
|
72716
|
+
const finish = (value) => {
|
|
72717
|
+
if (settled)
|
|
72718
|
+
return;
|
|
72719
|
+
settled = true;
|
|
72720
|
+
clearTimeout(timer);
|
|
72721
|
+
resolve7(value);
|
|
72722
|
+
};
|
|
72723
|
+
let child;
|
|
72724
|
+
try {
|
|
72725
|
+
child = crossSpawn(claudePath, ["-p", "/usage", "--output-format", "stream-json", "--verbose"], {
|
|
72726
|
+
env: env4,
|
|
72727
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
72728
|
+
});
|
|
72729
|
+
} catch (err) {
|
|
72730
|
+
log49.warn(`Connector probe for ${label} failed to spawn: ${err}`);
|
|
72731
|
+
resolve7(null);
|
|
72732
|
+
return;
|
|
72733
|
+
}
|
|
72734
|
+
const timer = setTimeout(() => {
|
|
72735
|
+
log49.debug(`Connector probe for ${label} timed out after ${timeoutMs}ms`);
|
|
72736
|
+
try {
|
|
72737
|
+
child.kill("SIGKILL");
|
|
72738
|
+
} catch {}
|
|
72739
|
+
finish(parseConnectorNames(stdout));
|
|
72740
|
+
}, timeoutMs);
|
|
72741
|
+
child.stdout?.on("data", (chunk) => {
|
|
72742
|
+
stdout += chunk.toString();
|
|
72743
|
+
const names = parseConnectorNames(stdout);
|
|
72744
|
+
if (names !== null) {
|
|
72745
|
+
try {
|
|
72746
|
+
child.kill("SIGTERM");
|
|
72747
|
+
} catch {}
|
|
72748
|
+
finish(names);
|
|
72749
|
+
}
|
|
72750
|
+
});
|
|
72751
|
+
child.stderr?.on("data", () => {});
|
|
72752
|
+
child.on("error", (err) => {
|
|
72753
|
+
log49.warn(`Connector probe for ${label} errored: ${err}`);
|
|
72754
|
+
finish(null);
|
|
72755
|
+
});
|
|
72756
|
+
child.on("close", () => finish(parseConnectorNames(stdout)));
|
|
72757
|
+
});
|
|
72758
|
+
}
|
|
72759
|
+
|
|
72477
72760
|
// src/cleanup/scheduler.ts
|
|
72478
72761
|
init_logger();
|
|
72479
|
-
import { existsSync as
|
|
72762
|
+
import { existsSync as existsSync15 } from "fs";
|
|
72480
72763
|
import { readdir, rm as rm3 } from "fs/promises";
|
|
72481
72764
|
import { join as join16 } from "path";
|
|
72482
72765
|
init_worktree();
|
|
72483
|
-
var
|
|
72766
|
+
var log50 = createLogger("cleanup");
|
|
72484
72767
|
var DEFAULT_CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
|
|
72485
72768
|
var MAX_WORKTREE_AGE_MS = 24 * 60 * 60 * 1000;
|
|
72486
72769
|
|
|
@@ -72503,17 +72786,17 @@ class CleanupScheduler {
|
|
|
72503
72786
|
}
|
|
72504
72787
|
start() {
|
|
72505
72788
|
if (this.isRunning) {
|
|
72506
|
-
|
|
72789
|
+
log50.debug("Cleanup scheduler already running");
|
|
72507
72790
|
return;
|
|
72508
72791
|
}
|
|
72509
72792
|
this.isRunning = true;
|
|
72510
|
-
|
|
72793
|
+
log50.info(`Cleanup scheduler started (interval: ${Math.round(this.intervalMs / 60000)}min)`);
|
|
72511
72794
|
this.runCleanup().catch((err) => {
|
|
72512
|
-
|
|
72795
|
+
log50.warn(`Initial cleanup failed: ${err}`);
|
|
72513
72796
|
});
|
|
72514
72797
|
this.timer = setInterval(() => {
|
|
72515
72798
|
this.runCleanup().catch((err) => {
|
|
72516
|
-
|
|
72799
|
+
log50.warn(`Periodic cleanup failed: ${err}`);
|
|
72517
72800
|
});
|
|
72518
72801
|
}, this.intervalMs);
|
|
72519
72802
|
}
|
|
@@ -72523,11 +72806,11 @@ class CleanupScheduler {
|
|
|
72523
72806
|
this.timer = null;
|
|
72524
72807
|
}
|
|
72525
72808
|
this.isRunning = false;
|
|
72526
|
-
|
|
72809
|
+
log50.debug("Cleanup scheduler stopped");
|
|
72527
72810
|
}
|
|
72528
72811
|
async runCleanup() {
|
|
72529
72812
|
const startTime = Date.now();
|
|
72530
|
-
|
|
72813
|
+
log50.debug("Running background cleanup...");
|
|
72531
72814
|
const stats = {
|
|
72532
72815
|
logsDeleted: 0,
|
|
72533
72816
|
worktreesCleaned: 0,
|
|
@@ -72553,9 +72836,9 @@ class CleanupScheduler {
|
|
|
72553
72836
|
const elapsed = Date.now() - startTime;
|
|
72554
72837
|
const totalCleaned = stats.logsDeleted + stats.worktreesCleaned + stats.metadataCleaned;
|
|
72555
72838
|
if (totalCleaned > 0 || stats.errors.length > 0) {
|
|
72556
|
-
|
|
72839
|
+
log50.info(`Cleanup completed in ${elapsed}ms: ` + `${stats.logsDeleted} logs, ${stats.worktreesCleaned} worktrees, ${stats.metadataCleaned} metadata` + (stats.errors.length > 0 ? ` (${stats.errors.length} errors)` : ""));
|
|
72557
72840
|
} else {
|
|
72558
|
-
|
|
72841
|
+
log50.debug(`Cleanup completed in ${elapsed}ms (nothing to clean)`);
|
|
72559
72842
|
}
|
|
72560
72843
|
return stats;
|
|
72561
72844
|
}
|
|
@@ -72568,7 +72851,7 @@ class CleanupScheduler {
|
|
|
72568
72851
|
const deleted = cleanupOldLogs(this.logRetentionDays);
|
|
72569
72852
|
resolve7(deleted);
|
|
72570
72853
|
} catch (err) {
|
|
72571
|
-
|
|
72854
|
+
log50.warn(`Log cleanup error: ${err}`);
|
|
72572
72855
|
resolve7(0);
|
|
72573
72856
|
}
|
|
72574
72857
|
});
|
|
@@ -72576,8 +72859,8 @@ class CleanupScheduler {
|
|
|
72576
72859
|
async cleanupOrphanedWorktrees() {
|
|
72577
72860
|
const worktreesDir = getWorktreesDir();
|
|
72578
72861
|
const result = { cleaned: 0, metadata: 0 };
|
|
72579
|
-
if (!
|
|
72580
|
-
|
|
72862
|
+
if (!existsSync15(worktreesDir)) {
|
|
72863
|
+
log50.debug("No worktrees directory exists, nothing to clean");
|
|
72581
72864
|
return result;
|
|
72582
72865
|
}
|
|
72583
72866
|
const persisted = this.sessionStore.load();
|
|
@@ -72595,7 +72878,7 @@ class CleanupScheduler {
|
|
|
72595
72878
|
continue;
|
|
72596
72879
|
const worktreePath = join16(worktreesDir, entry.name);
|
|
72597
72880
|
if (activeWorktrees.has(worktreePath)) {
|
|
72598
|
-
|
|
72881
|
+
log50.debug(`Worktree in use by persisted session, skipping: ${entry.name}`);
|
|
72599
72882
|
continue;
|
|
72600
72883
|
}
|
|
72601
72884
|
const meta = await readWorktreeMetadata(worktreePath);
|
|
@@ -72605,7 +72888,7 @@ class CleanupScheduler {
|
|
|
72605
72888
|
const lastActivity = new Date(meta.lastActivityAt).getTime();
|
|
72606
72889
|
const age = now - lastActivity;
|
|
72607
72890
|
if (meta.sessionId && age < this.maxWorktreeAgeMs) {
|
|
72608
|
-
|
|
72891
|
+
log50.debug(`Worktree has active session (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
|
|
72609
72892
|
continue;
|
|
72610
72893
|
}
|
|
72611
72894
|
const merged = age >= this.maxWorktreeAgeMs ? await isBranchMerged(meta.repoRoot, meta.branch).catch(() => false) : false;
|
|
@@ -72616,7 +72899,7 @@ class CleanupScheduler {
|
|
|
72616
72899
|
shouldCleanup = true;
|
|
72617
72900
|
cleanupReason = `inactive for ${Math.round(age / 3600000)}h`;
|
|
72618
72901
|
} else {
|
|
72619
|
-
|
|
72902
|
+
log50.debug(`Worktree recent (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
|
|
72620
72903
|
continue;
|
|
72621
72904
|
}
|
|
72622
72905
|
} else {
|
|
@@ -72625,7 +72908,7 @@ class CleanupScheduler {
|
|
|
72625
72908
|
}
|
|
72626
72909
|
if (!shouldCleanup)
|
|
72627
72910
|
continue;
|
|
72628
|
-
|
|
72911
|
+
log50.info(`Cleaning worktree (${cleanupReason}): ${entry.name}`);
|
|
72629
72912
|
try {
|
|
72630
72913
|
if (meta?.repoRoot) {
|
|
72631
72914
|
await removeWorktree(meta.repoRoot, worktreePath);
|
|
@@ -72636,19 +72919,19 @@ class CleanupScheduler {
|
|
|
72636
72919
|
await removeWorktreeMetadata(worktreePath);
|
|
72637
72920
|
result.metadata++;
|
|
72638
72921
|
} catch (err) {
|
|
72639
|
-
|
|
72922
|
+
log50.warn(`Failed to clean orphaned worktree ${entry.name}: ${err}`);
|
|
72640
72923
|
try {
|
|
72641
72924
|
await rm3(worktreePath, { recursive: true, force: true });
|
|
72642
72925
|
result.cleaned++;
|
|
72643
72926
|
await removeWorktreeMetadata(worktreePath);
|
|
72644
72927
|
result.metadata++;
|
|
72645
72928
|
} catch (rmErr) {
|
|
72646
|
-
|
|
72929
|
+
log50.error(`Failed to force remove worktree ${entry.name}: ${rmErr}`);
|
|
72647
72930
|
}
|
|
72648
72931
|
}
|
|
72649
72932
|
}
|
|
72650
72933
|
} catch (err) {
|
|
72651
|
-
|
|
72934
|
+
log50.warn(`Failed to scan worktrees directory: ${err}`);
|
|
72652
72935
|
}
|
|
72653
72936
|
return result;
|
|
72654
72937
|
}
|
|
@@ -72657,8 +72940,8 @@ class CleanupScheduler {
|
|
|
72657
72940
|
init_version_check();
|
|
72658
72941
|
init_spawn();
|
|
72659
72942
|
init_logger();
|
|
72660
|
-
var
|
|
72661
|
-
var sessionLog12 = createSessionLog(
|
|
72943
|
+
var log51 = createLogger("plugin");
|
|
72944
|
+
var sessionLog12 = createSessionLog(log51);
|
|
72662
72945
|
async function buildPluginRestartCliOptions(session, ctx) {
|
|
72663
72946
|
const account = session.claudeAccountId ? ctx.ops.getClaudeAccount(session.claudeAccountId) : undefined;
|
|
72664
72947
|
const memoryConfig = ctx.ops.getPlatformMemoryConfig(session.platformId);
|
|
@@ -72701,7 +72984,7 @@ async function runPluginCommand(args, cwd, timeout2 = 60000) {
|
|
|
72701
72984
|
});
|
|
72702
72985
|
proc.on("error", (err) => {
|
|
72703
72986
|
resolve7({ stdout, stderr, exitCode: 1 });
|
|
72704
|
-
|
|
72987
|
+
log51.error(`Plugin command error: ${err.message}`);
|
|
72705
72988
|
});
|
|
72706
72989
|
});
|
|
72707
72990
|
}
|
|
@@ -72742,6 +73025,9 @@ ${formatter.formatCodeBlock(errorMsg, "text")}`);
|
|
|
72742
73025
|
}
|
|
72743
73026
|
await post(session, "success", `✅ Plugin installed: ${formatter.formatCode(pluginName)}
|
|
72744
73027
|
\uD83D\uDD04 Restarting Claude to load plugin...`);
|
|
73028
|
+
if (session.platform.getMcpConfig().strictMcpConfig === true) {
|
|
73029
|
+
await post(session, "warning", `This platform runs with ${formatter.formatCode("strictMcpConfig: true")}: MCP servers bundled with the plugin will not load. ` + `Declare them under ${formatter.formatCode("mcpServers")} or set ${formatter.formatCode("strictMcpConfig: false")}.`);
|
|
73030
|
+
}
|
|
72745
73031
|
const cliOptions = await buildPluginRestartCliOptions(session, ctx);
|
|
72746
73032
|
const success = await restartClaudeSession(session, cliOptions, ctx, `Plugin installation: ${pluginName}`);
|
|
72747
73033
|
if (success) {
|
|
@@ -72806,7 +73092,7 @@ function shouldPostResumeRefusal(platformId, threadId, username, now = Date.now(
|
|
|
72806
73092
|
|
|
72807
73093
|
// src/session/reaction-router.ts
|
|
72808
73094
|
init_logger();
|
|
72809
|
-
var
|
|
73095
|
+
var log52 = createLogger("manager");
|
|
72810
73096
|
async function handleReaction(deps, platformId, postId, emojiName, username, action) {
|
|
72811
73097
|
const normalizedEmoji = normalizeEmojiName(emojiName);
|
|
72812
73098
|
if (action === "added" && isResumeEmoji(normalizedEmoji)) {
|
|
@@ -72821,7 +73107,7 @@ async function handleReaction(deps, platformId, postId, emojiName, username, act
|
|
|
72821
73107
|
return;
|
|
72822
73108
|
const ownerScoped = resolveApprovals(session.platform.approvals, isDcmThreadId(session.threadId)) === "owner";
|
|
72823
73109
|
if (!session.sessionAllowedUsers.has(username) && (ownerScoped || !session.platform.isUserAllowed(username))) {
|
|
72824
|
-
|
|
73110
|
+
log52.info(`\uD83D\uDEAB rejected reaction from unauthorized user`, {
|
|
72825
73111
|
event: "reaction.rejected",
|
|
72826
73112
|
platformId,
|
|
72827
73113
|
sessionId: session.sessionId,
|
|
@@ -72839,7 +73125,7 @@ async function tryResumeFromReaction(deps, platformId, postId, username) {
|
|
|
72839
73125
|
if (!persistedSession)
|
|
72840
73126
|
return false;
|
|
72841
73127
|
if (!isRevivable(persistedSession)) {
|
|
72842
|
-
|
|
73128
|
+
log52.debug(`Ignoring resume reaction on stopped session ${persistedSession.threadId.substring(0, 8)}...`);
|
|
72843
73129
|
return false;
|
|
72844
73130
|
}
|
|
72845
73131
|
const sessionId = `${platformId}:${persistedSession.threadId}`;
|
|
@@ -72864,7 +73150,7 @@ async function tryResumeFromReaction(deps, platformId, postId, username) {
|
|
|
72864
73150
|
return false;
|
|
72865
73151
|
}
|
|
72866
73152
|
const shortId = persistedSession.threadId.substring(0, 8);
|
|
72867
|
-
|
|
73153
|
+
log52.info(`\uD83D\uDD04 Resuming session ${shortId}... via emoji reaction by @${username}`);
|
|
72868
73154
|
await resumeSession(persistedSession, deps.getContext(), username);
|
|
72869
73155
|
return true;
|
|
72870
73156
|
}
|
|
@@ -72894,7 +73180,7 @@ async function dispatch(deps, session, postId, emojiName, username, action) {
|
|
|
72894
73180
|
}
|
|
72895
73181
|
if (session.lastError?.postId === postId && isBugReportEmoji(emojiName)) {
|
|
72896
73182
|
if (session.startedBy === username || session.platform.isUserAllowed(username) || session.sessionAllowedUsers.has(username)) {
|
|
72897
|
-
|
|
73183
|
+
log52.info(`\uD83D\uDC1B @${username} triggered bug report from error reaction`);
|
|
72898
73184
|
await reportBug(session, undefined, username, deps.getContext(), session.lastError);
|
|
72899
73185
|
return;
|
|
72900
73186
|
}
|
|
@@ -72909,7 +73195,7 @@ async function dispatch(deps, session, postId, emojiName, username, action) {
|
|
|
72909
73195
|
|
|
72910
73196
|
// src/session/manager.ts
|
|
72911
73197
|
init_logger();
|
|
72912
|
-
var
|
|
73198
|
+
var log53 = createLogger("manager");
|
|
72913
73199
|
var USAGE_PROBE_TIMEOUT_MS = 1e4;
|
|
72914
73200
|
var USAGE_REFRESH_DEADLINE_MS = 5000;
|
|
72915
73201
|
var USAGE_CACHE_TTL_MS = 15000;
|
|
@@ -72948,6 +73234,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
72948
73234
|
platformWatches = new Map;
|
|
72949
73235
|
autoUpdateManager = null;
|
|
72950
73236
|
accountPool;
|
|
73237
|
+
connectorsOff = null;
|
|
72951
73238
|
usageRefreshInFlight = null;
|
|
72952
73239
|
usageRefreshedAt = 0;
|
|
72953
73240
|
constructor(workingDir, permissionModeOrSkipFlag = "default", chromeEnabled = false, worktreeMode = "prompt", sessionsPath, threadLogsEnabled = true, threadLogsRetentionDays = 30, limits, claudeAccounts, respondOnlyWhenMentioned = false, userAttribution = true) {
|
|
@@ -73039,7 +73326,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
73039
73326
|
markNeedsBump(platformId);
|
|
73040
73327
|
this.updateStickyMessage();
|
|
73041
73328
|
});
|
|
73042
|
-
|
|
73329
|
+
log53.info(`\uD83D\uDCE1 Platform "${platformId}" registered`);
|
|
73043
73330
|
}
|
|
73044
73331
|
removePlatform(platformId) {
|
|
73045
73332
|
this.platforms.delete(platformId);
|
|
@@ -73060,7 +73347,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
73060
73347
|
if (users) {
|
|
73061
73348
|
users.add(sessionId);
|
|
73062
73349
|
}
|
|
73063
|
-
|
|
73350
|
+
log53.debug(`Registered session ${sessionId.substring(0, 20)} as worktree user for ${worktreePath}`);
|
|
73064
73351
|
}
|
|
73065
73352
|
unregisterWorktreeUser(worktreePath, sessionId) {
|
|
73066
73353
|
const users = this.worktreeUsers.get(worktreePath);
|
|
@@ -73283,7 +73570,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
73283
73570
|
try {
|
|
73284
73571
|
this.persistSessionUnsafe(session);
|
|
73285
73572
|
} catch (err) {
|
|
73286
|
-
|
|
73573
|
+
log53.error(`Failed to persist session ${session.sessionId}: ${err}`);
|
|
73287
73574
|
}
|
|
73288
73575
|
}
|
|
73289
73576
|
persistSessionUnsafe(session) {
|
|
@@ -73369,9 +73656,34 @@ class SessionManager extends EventEmitter4 {
|
|
|
73369
73656
|
debug: this.debug,
|
|
73370
73657
|
description: this.customDescription,
|
|
73371
73658
|
footer: this.customFooter,
|
|
73372
|
-
accountPoolStatus: this.accountPool.isEmpty ? undefined : this.accountPool.status()
|
|
73659
|
+
accountPoolStatus: this.accountPool.isEmpty ? undefined : this.accountPool.status(),
|
|
73660
|
+
connectorsOff: this.connectorsOff ?? undefined
|
|
73373
73661
|
}, overheadByPlatform);
|
|
73374
73662
|
}
|
|
73663
|
+
async noticeClaudeAiConnectors() {
|
|
73664
|
+
try {
|
|
73665
|
+
const accounts = this.accountPool.isEmpty ? [undefined] : [...this.accountPool.all];
|
|
73666
|
+
const found = new Set;
|
|
73667
|
+
await Promise.all(accounts.map(async (acc) => {
|
|
73668
|
+
const names2 = await probeClaudeAiConnectors(acc);
|
|
73669
|
+
for (const n of names2 ?? [])
|
|
73670
|
+
found.add(n);
|
|
73671
|
+
}));
|
|
73672
|
+
if (found.size === 0)
|
|
73673
|
+
return;
|
|
73674
|
+
const allowedOn = [...this.platforms.entries()].filter(([, client]) => client.getMcpConfig().claudeAiConnectors === true).map(([id]) => id);
|
|
73675
|
+
const names = [...found].sort().join(", ");
|
|
73676
|
+
if (allowedOn.length > 0) {
|
|
73677
|
+
log53.info(`claude.ai connectors (${names}) are available to sessions on: ${allowedOn.join(", ")}`);
|
|
73678
|
+
return;
|
|
73679
|
+
}
|
|
73680
|
+
log53.warn(`This Claude account has claude.ai connectors (${names}). Sessions do not get them: ` + `since 1.35.0 the bot disables them per session. To let a platform's sessions use them, set ` + `claudeAiConnectors: true on that platform in config.yaml (everyone on its allowedUsers gets them).`);
|
|
73681
|
+
this.connectorsOff = found.size;
|
|
73682
|
+
await this.updateAllStickyMessages();
|
|
73683
|
+
} catch (err) {
|
|
73684
|
+
log53.debug(`Connector notice skipped: ${err}`);
|
|
73685
|
+
}
|
|
73686
|
+
}
|
|
73375
73687
|
async updateAllStickyMessages() {
|
|
73376
73688
|
await this.updateStickyMessage();
|
|
73377
73689
|
}
|
|
@@ -73408,11 +73720,11 @@ class SessionManager extends EventEmitter4 {
|
|
|
73408
73720
|
}
|
|
73409
73721
|
}
|
|
73410
73722
|
if (sessionsToKill.length === 0) {
|
|
73411
|
-
|
|
73723
|
+
log53.info(`No active sessions to pause for platform ${platformId}`);
|
|
73412
73724
|
await this.updateStickyMessage();
|
|
73413
73725
|
return;
|
|
73414
73726
|
}
|
|
73415
|
-
|
|
73727
|
+
log53.info(`⏸️ Pausing ${sessionsToKill.length} session(s) for platform ${platformId}`);
|
|
73416
73728
|
for (const session of sessionsToKill) {
|
|
73417
73729
|
try {
|
|
73418
73730
|
const fmt = session.platform.getFormatter();
|
|
@@ -73428,9 +73740,9 @@ class SessionManager extends EventEmitter4 {
|
|
|
73428
73740
|
session.claude.kill();
|
|
73429
73741
|
this.registry.unregister(session.sessionId);
|
|
73430
73742
|
this.emitSessionRemove(session.sessionId);
|
|
73431
|
-
|
|
73743
|
+
log53.info(`⏸️ Paused session ${session.threadId.substring(0, 8)}`);
|
|
73432
73744
|
} catch (err) {
|
|
73433
|
-
|
|
73745
|
+
log53.warn(`Failed to pause session ${session.threadId}: ${err}`);
|
|
73434
73746
|
}
|
|
73435
73747
|
}
|
|
73436
73748
|
for (const session of sessionsToKill) {
|
|
@@ -73451,17 +73763,17 @@ class SessionManager extends EventEmitter4 {
|
|
|
73451
73763
|
sessionsToResume.push(state);
|
|
73452
73764
|
}
|
|
73453
73765
|
if (sessionsToResume.length === 0) {
|
|
73454
|
-
|
|
73766
|
+
log53.info(`No paused sessions to resume for platform ${platformId}`);
|
|
73455
73767
|
await this.updateStickyMessage();
|
|
73456
73768
|
return;
|
|
73457
73769
|
}
|
|
73458
|
-
|
|
73770
|
+
log53.info(`▶️ Resuming ${sessionsToResume.length} paused session(s) for platform ${platformId}`);
|
|
73459
73771
|
for (const state of sessionsToResume) {
|
|
73460
73772
|
try {
|
|
73461
73773
|
await resumeSession(state, this.getContext());
|
|
73462
|
-
|
|
73774
|
+
log53.info(`▶️ Resumed session ${state.threadId.substring(0, 8)}`);
|
|
73463
73775
|
} catch (err) {
|
|
73464
|
-
|
|
73776
|
+
log53.warn(`Failed to resume session ${state.threadId}: ${err}`);
|
|
73465
73777
|
}
|
|
73466
73778
|
}
|
|
73467
73779
|
await this.updateStickyMessage();
|
|
@@ -73500,14 +73812,14 @@ class SessionManager extends EventEmitter4 {
|
|
|
73500
73812
|
const sessionTimeoutMs = this.limits.sessionTimeoutMinutes * 60 * 1000;
|
|
73501
73813
|
const staleIds = this.sessionStore.cleanStale(sessionTimeoutMs * 2);
|
|
73502
73814
|
if (staleIds.length > 0) {
|
|
73503
|
-
|
|
73815
|
+
log53.info(`\uD83E\uDDF9 Soft-deleted ${staleIds.length} stale session(s) (kept for history)`);
|
|
73504
73816
|
}
|
|
73505
73817
|
const removedCount = this.sessionStore.cleanHistory();
|
|
73506
73818
|
if (removedCount > 0) {
|
|
73507
|
-
|
|
73819
|
+
log53.info(`\uD83D\uDDD1️ Permanently removed ${removedCount} old session(s) from history`);
|
|
73508
73820
|
}
|
|
73509
73821
|
const persisted = this.sessionStore.load();
|
|
73510
|
-
|
|
73822
|
+
log53.info(`\uD83D\uDCC2 Loaded ${persisted.size} session(s) from persistence`);
|
|
73511
73823
|
const excludePostIdsByPlatform = new Map;
|
|
73512
73824
|
for (const session of persisted.values()) {
|
|
73513
73825
|
const platformId = session.platformId;
|
|
@@ -73527,10 +73839,10 @@ class SessionManager extends EventEmitter4 {
|
|
|
73527
73839
|
const excludePostIds = excludePostIdsByPlatform.get(platform.platformId);
|
|
73528
73840
|
platform.getBotUser().then((botUser) => {
|
|
73529
73841
|
cleanupOldStickyMessages(platform, botUser.id, true, excludePostIds).catch((err) => {
|
|
73530
|
-
|
|
73842
|
+
log53.warn(`Failed to cleanup old sticky messages for ${platform.platformId}: ${err}`);
|
|
73531
73843
|
});
|
|
73532
73844
|
}).catch((err) => {
|
|
73533
|
-
|
|
73845
|
+
log53.warn(`Failed to get bot user for cleanup on ${platform.platformId}: ${err}`);
|
|
73534
73846
|
});
|
|
73535
73847
|
}
|
|
73536
73848
|
if (persisted.size > 0) {
|
|
@@ -73544,10 +73856,10 @@ class SessionManager extends EventEmitter4 {
|
|
|
73544
73856
|
}
|
|
73545
73857
|
}
|
|
73546
73858
|
if (pausedToSkip.length > 0) {
|
|
73547
|
-
|
|
73859
|
+
log53.info(`⏸️ ${pausedToSkip.length} session(s) remain paused (waiting for user message)`);
|
|
73548
73860
|
}
|
|
73549
73861
|
if (activeToResume.length > 0) {
|
|
73550
|
-
|
|
73862
|
+
log53.info(`\uD83D\uDD04 Attempting to resume ${activeToResume.length} active session(s)...`);
|
|
73551
73863
|
for (const state of activeToResume) {
|
|
73552
73864
|
await resumeSession(state, this.getContext());
|
|
73553
73865
|
}
|
|
@@ -73663,7 +73975,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
73663
73975
|
try {
|
|
73664
73976
|
return await transcribeForEvaluation(transcriber, platform, getSessionUploadDir(platformId, post2.rootId || post2.id), files);
|
|
73665
73977
|
} catch (err) {
|
|
73666
|
-
|
|
73978
|
+
log53.warn(`Watch transcription failed for ${platformId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
73667
73979
|
return "";
|
|
73668
73980
|
}
|
|
73669
73981
|
}
|
|
@@ -73999,7 +74311,7 @@ Mention me to start a session in this worktree.`, threadId);
|
|
|
73999
74311
|
const message = messageBuilder(formatter);
|
|
74000
74312
|
await post(session, "info", message);
|
|
74001
74313
|
} catch (err) {
|
|
74002
|
-
|
|
74314
|
+
log53.warn(`Failed to broadcast to session ${session.threadId}: ${err}`);
|
|
74003
74315
|
}
|
|
74004
74316
|
}
|
|
74005
74317
|
}
|
|
@@ -74018,7 +74330,7 @@ Mention me to start a session in this worktree.`, threadId);
|
|
|
74018
74330
|
session.messageManager?.setPendingUpdatePrompt({ postId: post2.id });
|
|
74019
74331
|
this.registerPost(post2.id, session.threadId);
|
|
74020
74332
|
} catch (err) {
|
|
74021
|
-
|
|
74333
|
+
log53.warn(`Failed to post ask message to ${threadId}: ${err}`);
|
|
74022
74334
|
}
|
|
74023
74335
|
}
|
|
74024
74336
|
}
|
|
@@ -83941,29 +84253,29 @@ function SessionLog({ logs, maxLines = 20 }) {
|
|
|
83941
84253
|
return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
|
|
83942
84254
|
flexDirection: "column",
|
|
83943
84255
|
flexShrink: 0,
|
|
83944
|
-
children: displayLogs.map((
|
|
84256
|
+
children: displayLogs.map((log54) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
|
|
83945
84257
|
flexShrink: 0,
|
|
83946
84258
|
children: [
|
|
83947
84259
|
/* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
|
|
83948
|
-
color: getColorForLevel(
|
|
84260
|
+
color: getColorForLevel(log54.level),
|
|
83949
84261
|
dimColor: true,
|
|
83950
84262
|
wrap: "truncate",
|
|
83951
84263
|
children: [
|
|
83952
84264
|
"[",
|
|
83953
|
-
padComponent(
|
|
84265
|
+
padComponent(log54.component),
|
|
83954
84266
|
"]"
|
|
83955
84267
|
]
|
|
83956
84268
|
}, undefined, true, undefined, this),
|
|
83957
84269
|
/* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
|
|
83958
|
-
color: getColorForLevel(
|
|
84270
|
+
color: getColorForLevel(log54.level),
|
|
83959
84271
|
wrap: "truncate",
|
|
83960
84272
|
children: [
|
|
83961
84273
|
" ",
|
|
83962
|
-
|
|
84274
|
+
log54.message
|
|
83963
84275
|
]
|
|
83964
84276
|
}, undefined, true, undefined, this)
|
|
83965
84277
|
]
|
|
83966
|
-
},
|
|
84278
|
+
}, log54.id, true, undefined, this))
|
|
83967
84279
|
}, undefined, false, undefined, this);
|
|
83968
84280
|
}
|
|
83969
84281
|
// src/ui/components/Footer.tsx
|
|
@@ -84487,7 +84799,7 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
|
|
|
84487
84799
|
const scrollRef = import_react62.default.useRef(null);
|
|
84488
84800
|
const { stdout } = use_stdout_default();
|
|
84489
84801
|
const isDebug = process.env.DEBUG === "1";
|
|
84490
|
-
const displayLogs = logs.filter((
|
|
84802
|
+
const displayLogs = logs.filter((log54) => isDebug || log54.level !== "debug");
|
|
84491
84803
|
const visibleLogs = displayLogs.slice(-Math.max(maxLines * 3, 100));
|
|
84492
84804
|
import_react62.default.useEffect(() => {
|
|
84493
84805
|
const handleResize = () => scrollRef.current?.remeasure();
|
|
@@ -84527,25 +84839,25 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
|
|
|
84527
84839
|
overflow: "hidden",
|
|
84528
84840
|
children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(ScrollView, {
|
|
84529
84841
|
ref: scrollRef,
|
|
84530
|
-
children: visibleLogs.map((
|
|
84842
|
+
children: visibleLogs.map((log54) => /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
|
|
84531
84843
|
children: [
|
|
84532
84844
|
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
|
|
84533
84845
|
dimColor: true,
|
|
84534
84846
|
children: [
|
|
84535
84847
|
"[",
|
|
84536
|
-
padComponent2(
|
|
84848
|
+
padComponent2(log54.component),
|
|
84537
84849
|
"]"
|
|
84538
84850
|
]
|
|
84539
84851
|
}, undefined, true, undefined, this),
|
|
84540
84852
|
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
|
|
84541
|
-
color: getLevelColor(
|
|
84853
|
+
color: getLevelColor(log54.level),
|
|
84542
84854
|
children: [
|
|
84543
84855
|
" ",
|
|
84544
|
-
|
|
84856
|
+
log54.message
|
|
84545
84857
|
]
|
|
84546
84858
|
}, undefined, true, undefined, this)
|
|
84547
84859
|
]
|
|
84548
|
-
},
|
|
84860
|
+
}, log54.id, true, undefined, this))
|
|
84549
84861
|
}, undefined, false, undefined, this)
|
|
84550
84862
|
}, undefined, false, undefined, this);
|
|
84551
84863
|
}
|
|
@@ -85071,10 +85383,10 @@ function useAppState(initialConfig) {
|
|
|
85071
85383
|
});
|
|
85072
85384
|
}, []);
|
|
85073
85385
|
const getLogsForSession = import_react63.useCallback((sessionId) => {
|
|
85074
|
-
return state.logs.filter((
|
|
85386
|
+
return state.logs.filter((log54) => log54.sessionId === sessionId);
|
|
85075
85387
|
}, [state.logs]);
|
|
85076
85388
|
const getGlobalLogs = import_react63.useCallback(() => {
|
|
85077
|
-
return state.logs.filter((
|
|
85389
|
+
return state.logs.filter((log54) => !log54.sessionId);
|
|
85078
85390
|
}, [state.logs]);
|
|
85079
85391
|
const togglePlatformEnabled = import_react63.useCallback((platformId) => {
|
|
85080
85392
|
let newEnabled = false;
|
|
@@ -86214,7 +86526,7 @@ import { EventEmitter as EventEmitter9 } from "events";
|
|
|
86214
86526
|
// src/auto-update/checker.ts
|
|
86215
86527
|
init_logger();
|
|
86216
86528
|
import { EventEmitter as EventEmitter7 } from "events";
|
|
86217
|
-
var
|
|
86529
|
+
var log54 = createLogger("checker");
|
|
86218
86530
|
var PACKAGE_NAME = "claude-threads";
|
|
86219
86531
|
function compareVersions(a, b) {
|
|
86220
86532
|
const partsA = a.replace(/^v/, "").split(".").map(Number);
|
|
@@ -86237,13 +86549,13 @@ async function fetchLatestVersion() {
|
|
|
86237
86549
|
}
|
|
86238
86550
|
});
|
|
86239
86551
|
if (!response.ok) {
|
|
86240
|
-
|
|
86552
|
+
log54.warn(`Failed to fetch latest version: HTTP ${response.status}`);
|
|
86241
86553
|
return null;
|
|
86242
86554
|
}
|
|
86243
86555
|
const data = await response.json();
|
|
86244
86556
|
return data.version ?? null;
|
|
86245
86557
|
} catch (err) {
|
|
86246
|
-
|
|
86558
|
+
log54.warn(`Failed to fetch latest version: ${err}`);
|
|
86247
86559
|
return null;
|
|
86248
86560
|
}
|
|
86249
86561
|
}
|
|
@@ -86260,38 +86572,38 @@ class UpdateChecker extends EventEmitter7 {
|
|
|
86260
86572
|
}
|
|
86261
86573
|
start() {
|
|
86262
86574
|
if (!this.config.enabled) {
|
|
86263
|
-
|
|
86575
|
+
log54.debug("Auto-update disabled, not starting checker");
|
|
86264
86576
|
return;
|
|
86265
86577
|
}
|
|
86266
86578
|
setTimeout(() => {
|
|
86267
86579
|
this.check().catch((err) => {
|
|
86268
|
-
|
|
86580
|
+
log54.warn(`Initial update check failed: ${err}`);
|
|
86269
86581
|
});
|
|
86270
86582
|
}, 5000);
|
|
86271
86583
|
const intervalMs = this.config.checkIntervalMinutes * 60 * 1000;
|
|
86272
86584
|
this.checkInterval = setInterval(() => {
|
|
86273
86585
|
this.check().catch((err) => {
|
|
86274
|
-
|
|
86586
|
+
log54.warn(`Periodic update check failed: ${err}`);
|
|
86275
86587
|
});
|
|
86276
86588
|
}, intervalMs);
|
|
86277
|
-
|
|
86589
|
+
log54.info(`\uD83D\uDD04 Update checker started (every ${this.config.checkIntervalMinutes} minutes)`);
|
|
86278
86590
|
}
|
|
86279
86591
|
stop() {
|
|
86280
86592
|
if (this.checkInterval) {
|
|
86281
86593
|
clearInterval(this.checkInterval);
|
|
86282
86594
|
this.checkInterval = null;
|
|
86283
86595
|
}
|
|
86284
|
-
|
|
86596
|
+
log54.debug("Update checker stopped");
|
|
86285
86597
|
}
|
|
86286
86598
|
async check() {
|
|
86287
86599
|
if (this.isChecking) {
|
|
86288
|
-
|
|
86600
|
+
log54.debug("Check already in progress, skipping");
|
|
86289
86601
|
return this.lastUpdateInfo;
|
|
86290
86602
|
}
|
|
86291
86603
|
this.isChecking = true;
|
|
86292
86604
|
this.emit("check:start");
|
|
86293
86605
|
try {
|
|
86294
|
-
|
|
86606
|
+
log54.debug("Checking for updates...");
|
|
86295
86607
|
const latestVersion2 = await fetchLatestVersion();
|
|
86296
86608
|
if (!latestVersion2) {
|
|
86297
86609
|
this.emit("check:complete", false);
|
|
@@ -86308,18 +86620,18 @@ class UpdateChecker extends EventEmitter7 {
|
|
|
86308
86620
|
detectedAt: new Date
|
|
86309
86621
|
};
|
|
86310
86622
|
if (!this.lastUpdateInfo || this.lastUpdateInfo.latestVersion !== latestVersion2) {
|
|
86311
|
-
|
|
86623
|
+
log54.info(`\uD83C\uDD95 Update available: v${currentVersion} → v${latestVersion2}`);
|
|
86312
86624
|
this.lastUpdateInfo = updateInfo;
|
|
86313
86625
|
this.emit("update", updateInfo);
|
|
86314
86626
|
}
|
|
86315
86627
|
this.emit("check:complete", true);
|
|
86316
86628
|
return updateInfo;
|
|
86317
86629
|
}
|
|
86318
|
-
|
|
86630
|
+
log54.debug(`Up to date (v${currentVersion})`);
|
|
86319
86631
|
this.emit("check:complete", false);
|
|
86320
86632
|
return null;
|
|
86321
86633
|
} catch (err) {
|
|
86322
|
-
|
|
86634
|
+
log54.warn(`Update check failed: ${err}`);
|
|
86323
86635
|
this.emit("check:error", err);
|
|
86324
86636
|
return null;
|
|
86325
86637
|
} finally {
|
|
@@ -86390,7 +86702,7 @@ function isInScheduledWindow(window2) {
|
|
|
86390
86702
|
}
|
|
86391
86703
|
|
|
86392
86704
|
// src/auto-update/scheduler.ts
|
|
86393
|
-
var
|
|
86705
|
+
var log55 = createLogger("scheduler");
|
|
86394
86706
|
|
|
86395
86707
|
class UpdateScheduler extends EventEmitter8 {
|
|
86396
86708
|
config;
|
|
@@ -86414,7 +86726,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
86414
86726
|
scheduleUpdate(updateInfo) {
|
|
86415
86727
|
this.pendingUpdate = updateInfo;
|
|
86416
86728
|
if (this.config.autoRestartMode === "immediate") {
|
|
86417
|
-
|
|
86729
|
+
log55.info("Immediate mode: triggering update now");
|
|
86418
86730
|
this.emit("ready", updateInfo);
|
|
86419
86731
|
return;
|
|
86420
86732
|
}
|
|
@@ -86427,19 +86739,19 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
86427
86739
|
this.scheduledRestartAt = null;
|
|
86428
86740
|
this.askApprovals.clear();
|
|
86429
86741
|
this.askStartTime = null;
|
|
86430
|
-
|
|
86742
|
+
log55.debug("Update schedule cancelled");
|
|
86431
86743
|
}
|
|
86432
86744
|
deferUpdate(minutes) {
|
|
86433
86745
|
const deferUntil = new Date(Date.now() + minutes * 60 * 1000);
|
|
86434
86746
|
this.scheduledRestartAt = null;
|
|
86435
86747
|
this.idleStartTime = null;
|
|
86436
86748
|
this.emit("deferred", deferUntil);
|
|
86437
|
-
|
|
86749
|
+
log55.info(`Update deferred until ${deferUntil.toLocaleTimeString()}`);
|
|
86438
86750
|
return deferUntil;
|
|
86439
86751
|
}
|
|
86440
86752
|
recordAskResponse(threadId, approved) {
|
|
86441
86753
|
this.askApprovals.set(threadId, approved);
|
|
86442
|
-
|
|
86754
|
+
log55.debug(`Thread ${threadId.substring(0, 8)} ${approved ? "approved" : "denied"} update`);
|
|
86443
86755
|
this.checkAskCondition();
|
|
86444
86756
|
}
|
|
86445
86757
|
getScheduledRestartAt() {
|
|
@@ -86460,7 +86772,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
86460
86772
|
return;
|
|
86461
86773
|
this.checkCondition();
|
|
86462
86774
|
this.checkTimer = setInterval(() => this.checkCondition(), 1e4);
|
|
86463
|
-
|
|
86775
|
+
log55.debug(`Started checking for ${this.config.autoRestartMode} condition`);
|
|
86464
86776
|
}
|
|
86465
86777
|
stopChecking() {
|
|
86466
86778
|
if (this.checkTimer) {
|
|
@@ -86491,17 +86803,17 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
86491
86803
|
if (activity.activeSessionCount === 0) {
|
|
86492
86804
|
if (!this.idleStartTime) {
|
|
86493
86805
|
this.idleStartTime = new Date;
|
|
86494
|
-
|
|
86806
|
+
log55.debug("No active sessions, starting idle timer");
|
|
86495
86807
|
}
|
|
86496
86808
|
const idleMs = Date.now() - this.idleStartTime.getTime();
|
|
86497
86809
|
const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
|
|
86498
86810
|
if (idleMs >= requiredMs) {
|
|
86499
|
-
|
|
86811
|
+
log55.info(`Idle for ${this.config.idleTimeoutMinutes} minutes, triggering update`);
|
|
86500
86812
|
this.triggerCountdown();
|
|
86501
86813
|
}
|
|
86502
86814
|
} else {
|
|
86503
86815
|
if (this.idleStartTime) {
|
|
86504
|
-
|
|
86816
|
+
log55.debug("Sessions became active, resetting idle timer");
|
|
86505
86817
|
this.idleStartTime = null;
|
|
86506
86818
|
}
|
|
86507
86819
|
}
|
|
@@ -86512,7 +86824,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
86512
86824
|
const quietMs = Date.now() - activity.lastActivityAt.getTime();
|
|
86513
86825
|
const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
|
|
86514
86826
|
if (quietMs >= requiredMs && !activity.anySessionBusy) {
|
|
86515
|
-
|
|
86827
|
+
log55.info(`Sessions quiet for ${this.config.quietTimeoutMinutes} minutes, triggering update`);
|
|
86516
86828
|
this.triggerCountdown();
|
|
86517
86829
|
}
|
|
86518
86830
|
} else if (activity.activeSessionCount === 0) {
|
|
@@ -86522,7 +86834,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
86522
86834
|
const idleMs = Date.now() - this.idleStartTime.getTime();
|
|
86523
86835
|
const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
|
|
86524
86836
|
if (idleMs >= requiredMs) {
|
|
86525
|
-
|
|
86837
|
+
log55.info("No sessions and quiet timeout reached, triggering update");
|
|
86526
86838
|
this.triggerCountdown();
|
|
86527
86839
|
}
|
|
86528
86840
|
}
|
|
@@ -86533,13 +86845,13 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
86533
86845
|
}
|
|
86534
86846
|
const activity = this.getSessionActivity();
|
|
86535
86847
|
if (activity.activeSessionCount === 0) {
|
|
86536
|
-
|
|
86848
|
+
log55.info("Within scheduled window and no active sessions, triggering update");
|
|
86537
86849
|
this.triggerCountdown();
|
|
86538
86850
|
} else if (activity.lastActivityAt) {
|
|
86539
86851
|
const quietMs = Date.now() - activity.lastActivityAt.getTime();
|
|
86540
86852
|
const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
|
|
86541
86853
|
if (quietMs >= requiredMs && !activity.anySessionBusy) {
|
|
86542
|
-
|
|
86854
|
+
log55.info("Within scheduled window and sessions quiet, triggering update");
|
|
86543
86855
|
this.triggerCountdown();
|
|
86544
86856
|
}
|
|
86545
86857
|
}
|
|
@@ -86547,14 +86859,14 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
86547
86859
|
checkAskCondition() {
|
|
86548
86860
|
const threadIds = this.getActiveThreadIds();
|
|
86549
86861
|
if (threadIds.length === 0) {
|
|
86550
|
-
|
|
86862
|
+
log55.info("No active threads, proceeding with update");
|
|
86551
86863
|
this.triggerCountdown();
|
|
86552
86864
|
return;
|
|
86553
86865
|
}
|
|
86554
86866
|
if (!this.askStartTime && this.pendingUpdate) {
|
|
86555
86867
|
this.askStartTime = new Date;
|
|
86556
86868
|
this.postAskMessage(threadIds, this.pendingUpdate.latestVersion).catch((err) => {
|
|
86557
|
-
|
|
86869
|
+
log55.warn(`Failed to post ask message: ${err}`);
|
|
86558
86870
|
});
|
|
86559
86871
|
return;
|
|
86560
86872
|
}
|
|
@@ -86567,12 +86879,12 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
86567
86879
|
denials++;
|
|
86568
86880
|
}
|
|
86569
86881
|
if (approvals > threadIds.length / 2) {
|
|
86570
|
-
|
|
86882
|
+
log55.info(`Majority approved (${approvals}/${threadIds.length}), triggering update`);
|
|
86571
86883
|
this.triggerCountdown();
|
|
86572
86884
|
return;
|
|
86573
86885
|
}
|
|
86574
86886
|
if (denials > threadIds.length / 2) {
|
|
86575
|
-
|
|
86887
|
+
log55.info(`Majority denied (${denials}/${threadIds.length}), deferring update`);
|
|
86576
86888
|
this.deferUpdate(60);
|
|
86577
86889
|
return;
|
|
86578
86890
|
}
|
|
@@ -86580,7 +86892,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
86580
86892
|
const elapsedMs = Date.now() - this.askStartTime.getTime();
|
|
86581
86893
|
const timeoutMs = this.config.askTimeoutMinutes * 60 * 1000;
|
|
86582
86894
|
if (elapsedMs >= timeoutMs) {
|
|
86583
|
-
|
|
86895
|
+
log55.info(`Ask timeout reached (${this.config.askTimeoutMinutes} min), triggering update`);
|
|
86584
86896
|
this.triggerCountdown();
|
|
86585
86897
|
}
|
|
86586
86898
|
}
|
|
@@ -86600,7 +86912,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
86600
86912
|
this.emit("ready", this.pendingUpdate);
|
|
86601
86913
|
}
|
|
86602
86914
|
}, 1000);
|
|
86603
|
-
|
|
86915
|
+
log55.info("Update countdown started (60 seconds)");
|
|
86604
86916
|
}
|
|
86605
86917
|
stopCountdown() {
|
|
86606
86918
|
if (this.countdownTimer) {
|
|
@@ -86612,28 +86924,28 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
86612
86924
|
|
|
86613
86925
|
// src/auto-update/installer.ts
|
|
86614
86926
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
86615
|
-
import { existsSync as
|
|
86927
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12, mkdirSync as mkdirSync8 } from "fs";
|
|
86616
86928
|
import { dirname as dirname9, resolve as resolve7 } from "path";
|
|
86617
86929
|
import { homedir as homedir9 } from "os";
|
|
86618
86930
|
init_logger();
|
|
86619
|
-
var
|
|
86931
|
+
var log56 = createLogger("installer");
|
|
86620
86932
|
function detectPackageManager() {
|
|
86621
86933
|
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
86622
86934
|
const originalInstaller = detectOriginalInstaller();
|
|
86623
86935
|
if (originalInstaller) {
|
|
86624
|
-
|
|
86936
|
+
log56.debug(`Detected original installer: ${originalInstaller}`);
|
|
86625
86937
|
if (originalInstaller === "bun") {
|
|
86626
86938
|
const bunCheck2 = spawnSync("bun", ["--version"], { stdio: "ignore" });
|
|
86627
86939
|
if (bunCheck2.status === 0) {
|
|
86628
86940
|
return { cmd: "bun", isBun: true };
|
|
86629
86941
|
}
|
|
86630
|
-
|
|
86942
|
+
log56.warn("Originally installed with bun, but bun not found. Falling back to npm.");
|
|
86631
86943
|
} else {
|
|
86632
86944
|
const npmCheck2 = spawnSync(npmCmd, ["--version"], { stdio: "ignore" });
|
|
86633
86945
|
if (npmCheck2.status === 0) {
|
|
86634
86946
|
return { cmd: npmCmd, isBun: false };
|
|
86635
86947
|
}
|
|
86636
|
-
|
|
86948
|
+
log56.warn("Originally installed with npm, but npm not found. Falling back to bun.");
|
|
86637
86949
|
}
|
|
86638
86950
|
}
|
|
86639
86951
|
const bunCheck = spawnSync("bun", ["--version"], { stdio: "ignore" });
|
|
@@ -86679,34 +86991,34 @@ var STATE_PATH = resolve7(homedir9(), ".config", "claude-threads", UPDATE_STATE_
|
|
|
86679
86991
|
var PACKAGE_NAME2 = "claude-threads";
|
|
86680
86992
|
function loadUpdateState() {
|
|
86681
86993
|
try {
|
|
86682
|
-
if (
|
|
86994
|
+
if (existsSync17(STATE_PATH)) {
|
|
86683
86995
|
const content = readFileSync12(STATE_PATH, "utf-8");
|
|
86684
86996
|
return JSON.parse(content);
|
|
86685
86997
|
}
|
|
86686
86998
|
} catch (err) {
|
|
86687
|
-
|
|
86999
|
+
log56.warn(`Failed to load update state: ${err}`);
|
|
86688
87000
|
}
|
|
86689
87001
|
return {};
|
|
86690
87002
|
}
|
|
86691
87003
|
function saveUpdateState(state) {
|
|
86692
87004
|
try {
|
|
86693
87005
|
const dir = dirname9(STATE_PATH);
|
|
86694
|
-
if (!
|
|
87006
|
+
if (!existsSync17(dir)) {
|
|
86695
87007
|
mkdirSync8(dir, { recursive: true, mode: 448 });
|
|
86696
87008
|
}
|
|
86697
87009
|
writeFileAtomic(STATE_PATH, JSON.stringify(state, null, 2));
|
|
86698
|
-
|
|
87010
|
+
log56.debug("Update state saved");
|
|
86699
87011
|
} catch (err) {
|
|
86700
|
-
|
|
87012
|
+
log56.warn(`Failed to save update state: ${err}`);
|
|
86701
87013
|
}
|
|
86702
87014
|
}
|
|
86703
87015
|
function clearUpdateState() {
|
|
86704
87016
|
try {
|
|
86705
|
-
if (
|
|
87017
|
+
if (existsSync17(STATE_PATH)) {
|
|
86706
87018
|
writeFileAtomic(STATE_PATH, "{}");
|
|
86707
87019
|
}
|
|
86708
87020
|
} catch (err) {
|
|
86709
|
-
|
|
87021
|
+
log56.warn(`Failed to clear update state: ${err}`);
|
|
86710
87022
|
}
|
|
86711
87023
|
}
|
|
86712
87024
|
function checkJustUpdated() {
|
|
@@ -86738,11 +87050,11 @@ function clearRuntimeSettings() {
|
|
|
86738
87050
|
}
|
|
86739
87051
|
}
|
|
86740
87052
|
async function installVersion(version) {
|
|
86741
|
-
|
|
87053
|
+
log56.info(`\uD83D\uDCE6 Installing ${PACKAGE_NAME2}@${version}...`);
|
|
86742
87054
|
const pm = detectPackageManager();
|
|
86743
87055
|
if (!pm) {
|
|
86744
87056
|
const error = "Neither bun nor npm found in PATH. Cannot install update.";
|
|
86745
|
-
|
|
87057
|
+
log56.error(`❌ ${error}`);
|
|
86746
87058
|
return { success: false, error };
|
|
86747
87059
|
}
|
|
86748
87060
|
saveUpdateState({
|
|
@@ -86754,7 +87066,7 @@ async function installVersion(version) {
|
|
|
86754
87066
|
return new Promise((resolve8) => {
|
|
86755
87067
|
const { cmd, isBun: isBun3 } = pm;
|
|
86756
87068
|
const args = ["install", "-g", `${PACKAGE_NAME2}@${version}`];
|
|
86757
|
-
|
|
87069
|
+
log56.debug(`Using ${isBun3 ? "bun" : "npm"} for installation`);
|
|
86758
87070
|
const child = spawn4(cmd, args, {
|
|
86759
87071
|
stdio: ["ignore", "pipe", "pipe"],
|
|
86760
87072
|
env: {
|
|
@@ -86772,7 +87084,7 @@ async function installVersion(version) {
|
|
|
86772
87084
|
});
|
|
86773
87085
|
child.on("close", (code) => {
|
|
86774
87086
|
if (code === 0) {
|
|
86775
|
-
|
|
87087
|
+
log56.info(`✅ Successfully installed ${PACKAGE_NAME2}@${version}`);
|
|
86776
87088
|
saveUpdateState({
|
|
86777
87089
|
previousVersion: VERSION,
|
|
86778
87090
|
targetVersion: version,
|
|
@@ -86782,20 +87094,20 @@ async function installVersion(version) {
|
|
|
86782
87094
|
resolve8({ success: true });
|
|
86783
87095
|
} else {
|
|
86784
87096
|
const errorMsg = stderr || stdout || `Exit code: ${code}`;
|
|
86785
|
-
|
|
87097
|
+
log56.error(`❌ Installation failed: ${errorMsg}`);
|
|
86786
87098
|
clearUpdateState();
|
|
86787
87099
|
resolve8({ success: false, error: errorMsg });
|
|
86788
87100
|
}
|
|
86789
87101
|
});
|
|
86790
87102
|
child.on("error", (err) => {
|
|
86791
|
-
|
|
87103
|
+
log56.error(`❌ Failed to spawn npm: ${err}`);
|
|
86792
87104
|
clearUpdateState();
|
|
86793
87105
|
resolve8({ success: false, error: err.message });
|
|
86794
87106
|
});
|
|
86795
87107
|
setTimeout(() => {
|
|
86796
87108
|
if (child.exitCode === null) {
|
|
86797
87109
|
child.kill();
|
|
86798
|
-
|
|
87110
|
+
log56.error("❌ Installation timed out");
|
|
86799
87111
|
clearUpdateState();
|
|
86800
87112
|
resolve8({ success: false, error: "Installation timed out" });
|
|
86801
87113
|
}
|
|
@@ -86839,9 +87151,9 @@ class UpdateInstaller {
|
|
|
86839
87151
|
// src/auto-update/respawn.ts
|
|
86840
87152
|
init_logger();
|
|
86841
87153
|
import { spawn as spawn5 } from "child_process";
|
|
86842
|
-
import { existsSync as
|
|
87154
|
+
import { existsSync as existsSync18, statSync as statSync5 } from "fs";
|
|
86843
87155
|
import { delimiter, join as join17 } from "path";
|
|
86844
|
-
var
|
|
87156
|
+
var log57 = createLogger("respawn");
|
|
86845
87157
|
function decideRespawn(env5 = process.env, isTTY = !!process.stdout.isTTY) {
|
|
86846
87158
|
if (env5.CLAUDE_THREADS_BIN) {
|
|
86847
87159
|
return { kind: "exit-for-supervisor", supervisor: "claude-threads-daemon" };
|
|
@@ -86860,7 +87172,7 @@ function decideRespawn(env5 = process.env, isTTY = !!process.stdout.isTTY) {
|
|
|
86860
87172
|
}
|
|
86861
87173
|
return { kind: "self-respawn" };
|
|
86862
87174
|
}
|
|
86863
|
-
function resolveClaudeThreadsBin(_env = process.env, _existsSync =
|
|
87175
|
+
function resolveClaudeThreadsBin(_env = process.env, _existsSync = existsSync18, _isFileExecutable = isFileExecutable) {
|
|
86864
87176
|
const isWin2 = process.platform === "win32";
|
|
86865
87177
|
const names = isWin2 ? ["claude-threads.cmd", "claude-threads.exe", "claude-threads.bat"] : ["claude-threads"];
|
|
86866
87178
|
const path10 = _env.PATH || _env.Path || "";
|
|
@@ -86897,7 +87209,7 @@ function isFileExecutable(path10) {
|
|
|
86897
87209
|
}
|
|
86898
87210
|
function spawnReplacement(argv = process.argv.slice(2), binPath = resolveClaudeThreadsBin()) {
|
|
86899
87211
|
if (!binPath) {
|
|
86900
|
-
|
|
87212
|
+
log57.error("Could not resolve claude-threads on PATH; self-respawn aborted");
|
|
86901
87213
|
return false;
|
|
86902
87214
|
}
|
|
86903
87215
|
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
@@ -86918,23 +87230,23 @@ function spawnReplacement(argv = process.argv.slice(2), binPath = resolveClaudeT
|
|
|
86918
87230
|
shell: useShell
|
|
86919
87231
|
});
|
|
86920
87232
|
} catch (err) {
|
|
86921
|
-
|
|
87233
|
+
log57.error(`spawn() threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
86922
87234
|
return false;
|
|
86923
87235
|
}
|
|
86924
87236
|
child.once("error", (err) => {
|
|
86925
|
-
|
|
87237
|
+
log57.error(`Replacement process error: ${err.message}`);
|
|
86926
87238
|
});
|
|
86927
87239
|
if (child.pid === undefined) {
|
|
86928
|
-
|
|
87240
|
+
log57.error("Spawn returned no pid (binary likely not executable)");
|
|
86929
87241
|
return false;
|
|
86930
87242
|
}
|
|
86931
87243
|
child.unref();
|
|
86932
|
-
|
|
87244
|
+
log57.info(`Spawned replacement pid=${child.pid} from ${binPath}`);
|
|
86933
87245
|
return true;
|
|
86934
87246
|
}
|
|
86935
87247
|
|
|
86936
87248
|
// src/auto-update/manager.ts
|
|
86937
|
-
var
|
|
87249
|
+
var log58 = createLogger("updater");
|
|
86938
87250
|
|
|
86939
87251
|
class AutoUpdateManager extends EventEmitter9 {
|
|
86940
87252
|
config;
|
|
@@ -86957,23 +87269,23 @@ class AutoUpdateManager extends EventEmitter9 {
|
|
|
86957
87269
|
}
|
|
86958
87270
|
start() {
|
|
86959
87271
|
if (!this.config.enabled) {
|
|
86960
|
-
|
|
87272
|
+
log58.info("Auto-update is disabled");
|
|
86961
87273
|
return;
|
|
86962
87274
|
}
|
|
86963
87275
|
const updateResult = this.installer.checkJustUpdated();
|
|
86964
87276
|
if (updateResult) {
|
|
86965
|
-
|
|
87277
|
+
log58.info(`\uD83C\uDF89 Updated from v${updateResult.previousVersion} to v${updateResult.currentVersion}`);
|
|
86966
87278
|
this.callbacks.broadcastUpdate((fmt) => `\uD83C\uDF89 ${fmt.formatBold("Bot updated")} from v${updateResult.previousVersion} to v${updateResult.currentVersion}`).catch((err) => {
|
|
86967
|
-
|
|
87279
|
+
log58.warn(`Failed to broadcast update notification: ${err}`);
|
|
86968
87280
|
});
|
|
86969
87281
|
}
|
|
86970
87282
|
this.checker.start();
|
|
86971
|
-
|
|
87283
|
+
log58.info(`\uD83D\uDD04 Auto-update manager started (mode: ${this.config.autoRestartMode})`);
|
|
86972
87284
|
}
|
|
86973
87285
|
stop() {
|
|
86974
87286
|
this.checker.stop();
|
|
86975
87287
|
this.scheduler.stop();
|
|
86976
|
-
|
|
87288
|
+
log58.debug("Auto-update manager stopped");
|
|
86977
87289
|
}
|
|
86978
87290
|
getState() {
|
|
86979
87291
|
return { ...this.state };
|
|
@@ -86987,10 +87299,10 @@ class AutoUpdateManager extends EventEmitter9 {
|
|
|
86987
87299
|
async forceUpdate() {
|
|
86988
87300
|
const updateInfo = this.state.updateInfo || await this.checker.check();
|
|
86989
87301
|
if (!updateInfo) {
|
|
86990
|
-
|
|
87302
|
+
log58.info("No update available");
|
|
86991
87303
|
return;
|
|
86992
87304
|
}
|
|
86993
|
-
|
|
87305
|
+
log58.info("Forcing immediate update");
|
|
86994
87306
|
await this.performUpdate(updateInfo);
|
|
86995
87307
|
}
|
|
86996
87308
|
deferUpdate(minutes = 60) {
|
|
@@ -87056,11 +87368,11 @@ class AutoUpdateManager extends EventEmitter9 {
|
|
|
87056
87368
|
await this.callbacks.prepareForRestart();
|
|
87057
87369
|
} catch (err) {
|
|
87058
87370
|
const reason = err instanceof Error ? err.message : String(err);
|
|
87059
|
-
|
|
87371
|
+
log58.error(`prepareForRestart failed: ${reason}`);
|
|
87060
87372
|
await this.callbacks.broadcastUpdate((fmt) => `⚠️ ${fmt.formatBold("Restart aborted")}: shutdown sequence failed (${reason}). Sessions may be in an inconsistent state; please run ${fmt.formatCode("claude-threads")} manually.`).catch(() => {});
|
|
87061
87373
|
process.exit(1);
|
|
87062
87374
|
}
|
|
87063
|
-
|
|
87375
|
+
log58.info(`\uD83D\uDD04 Restarting for update to v${updateInfo.latestVersion}`);
|
|
87064
87376
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
87065
87377
|
process.stdout.write("\x1B[?25h");
|
|
87066
87378
|
if (decision.kind === "self-respawn") {
|
|
@@ -87069,14 +87381,14 @@ class AutoUpdateManager extends EventEmitter9 {
|
|
|
87069
87381
|
if (ok) {
|
|
87070
87382
|
process.exit(0);
|
|
87071
87383
|
}
|
|
87072
|
-
|
|
87384
|
+
log58.error("Self-respawn launch failed after binary resolution succeeded");
|
|
87073
87385
|
await this.callbacks.broadcastUpdate((fmt) => `⚠️ ${fmt.formatBold("Auto-restart failed")} after install: please run ${fmt.formatCode("claude-threads")} to bring the bot back. Sessions are persisted and will resume.`).catch(() => {});
|
|
87074
87386
|
} else {
|
|
87075
|
-
|
|
87387
|
+
log58.error("claude-threads not found on PATH; manual restart required");
|
|
87076
87388
|
}
|
|
87077
87389
|
process.exit(0);
|
|
87078
87390
|
}
|
|
87079
|
-
|
|
87391
|
+
log58.debug(`Restart handled by supervisor: ${decision.supervisor}`);
|
|
87080
87392
|
process.exit(RESTART_EXIT_CODE);
|
|
87081
87393
|
} else {
|
|
87082
87394
|
const errorMsg = result.error ?? "Unknown error";
|
|
@@ -87282,6 +87594,15 @@ async function startWithoutDaemon() {
|
|
|
87282
87594
|
if (!newConfig.platforms || newConfig.platforms.length === 0) {
|
|
87283
87595
|
throw new Error("No platforms configured. Run with --setup to configure.");
|
|
87284
87596
|
}
|
|
87597
|
+
let mcpPostureWarnings = [];
|
|
87598
|
+
try {
|
|
87599
|
+
mcpPostureWarnings = resolvePlatformMcpPosture(newConfig.platforms, newConfig.mcpServers).warnings;
|
|
87600
|
+
} catch (err) {
|
|
87601
|
+
console.error(red(` ❌ ${err instanceof Error ? err.message : String(err)}`));
|
|
87602
|
+
process.exit(1);
|
|
87603
|
+
}
|
|
87604
|
+
for (const w of mcpPostureWarnings)
|
|
87605
|
+
console.warn(w);
|
|
87285
87606
|
const config = newConfig;
|
|
87286
87607
|
const firstPlatformConfig = config.platforms[0];
|
|
87287
87608
|
const initialPermissionMode = resolvePermissionMode({
|
|
@@ -87433,6 +87754,9 @@ async function startWithoutDaemon() {
|
|
|
87433
87754
|
}
|
|
87434
87755
|
}
|
|
87435
87756
|
});
|
|
87757
|
+
for (const w of mcpPostureWarnings) {
|
|
87758
|
+
ui.addLog({ level: "warn", component: "config", message: w });
|
|
87759
|
+
}
|
|
87436
87760
|
setLogHandler((level, component, message, sessionId) => {
|
|
87437
87761
|
ui.addLog({ level, component, message, sessionId });
|
|
87438
87762
|
});
|
|
@@ -87655,6 +87979,7 @@ async function startWithoutDaemon() {
|
|
|
87655
87979
|
});
|
|
87656
87980
|
autoUpdateManager.start();
|
|
87657
87981
|
ui.setReady();
|
|
87982
|
+
session.noticeClaudeAiConnectors();
|
|
87658
87983
|
const shutdown = async (_signal) => {
|
|
87659
87984
|
if (isShuttingDown2)
|
|
87660
87985
|
return;
|