claude-threads 1.27.0 → 1.28.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 +14 -2
- package/dist/index.js +901 -592
- package/dist/mcp/mcp-server.js +270 -189
- package/docs/CONFIGURATION.md +20 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4153,10 +4153,10 @@ __export(exports_worktree, {
|
|
|
4153
4153
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4154
4154
|
import * as path from "path";
|
|
4155
4155
|
import * as fs from "fs/promises";
|
|
4156
|
-
import { homedir as
|
|
4156
|
+
import { homedir as homedir4 } from "os";
|
|
4157
4157
|
async function execGit(args, cwd) {
|
|
4158
4158
|
const cmd = `git ${args.join(" ")}`;
|
|
4159
|
-
|
|
4159
|
+
log8.debug(`Executing: ${cmd}`);
|
|
4160
4160
|
return new Promise((resolve6, reject) => {
|
|
4161
4161
|
const proc = crossSpawn("git", args, { cwd });
|
|
4162
4162
|
let stdout = "";
|
|
@@ -4169,15 +4169,15 @@ async function execGit(args, cwd) {
|
|
|
4169
4169
|
});
|
|
4170
4170
|
proc.on("close", (code) => {
|
|
4171
4171
|
if (code === 0) {
|
|
4172
|
-
|
|
4172
|
+
log8.debug(`${cmd} → success`);
|
|
4173
4173
|
resolve6(stdout.trim());
|
|
4174
4174
|
} else {
|
|
4175
|
-
|
|
4175
|
+
log8.debug(`${cmd} → failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
|
|
4176
4176
|
reject(new Error(`git ${args.join(" ")} failed: ${stderr || stdout}`));
|
|
4177
4177
|
}
|
|
4178
4178
|
});
|
|
4179
4179
|
proc.on("error", (err) => {
|
|
4180
|
-
|
|
4180
|
+
log8.warn(`${cmd} → error: ${err}`);
|
|
4181
4181
|
reject(err);
|
|
4182
4182
|
});
|
|
4183
4183
|
});
|
|
@@ -4187,7 +4187,7 @@ async function isGitRepository(dir) {
|
|
|
4187
4187
|
await execGit(["rev-parse", "--git-dir"], dir);
|
|
4188
4188
|
return true;
|
|
4189
4189
|
} catch (err) {
|
|
4190
|
-
|
|
4190
|
+
log8.debug(`Not a git repository: ${dir} (${err})`);
|
|
4191
4191
|
return false;
|
|
4192
4192
|
}
|
|
4193
4193
|
}
|
|
@@ -4337,49 +4337,49 @@ async function detectWorktreeInfo(workingDir) {
|
|
|
4337
4337
|
const branchOutput = await execGit(["rev-parse", "--abbrev-ref", "HEAD"], workingDir);
|
|
4338
4338
|
const branch = branchOutput?.trim();
|
|
4339
4339
|
if (!branch) {
|
|
4340
|
-
|
|
4340
|
+
log8.debug(`Could not detect branch for worktree at ${workingDir}`);
|
|
4341
4341
|
return null;
|
|
4342
4342
|
}
|
|
4343
4343
|
const repoRoot = await getMainRepositoryRoot(workingDir);
|
|
4344
|
-
|
|
4344
|
+
log8.debug(`Detected worktree: path=${workingDir}, branch=${branch}, repoRoot=${repoRoot}`);
|
|
4345
4345
|
return {
|
|
4346
4346
|
worktreePath: workingDir,
|
|
4347
4347
|
branch,
|
|
4348
4348
|
repoRoot: repoRoot || workingDir
|
|
4349
4349
|
};
|
|
4350
4350
|
} catch (err) {
|
|
4351
|
-
|
|
4351
|
+
log8.debug(`Failed to detect worktree info for ${workingDir}: ${err}`);
|
|
4352
4352
|
return null;
|
|
4353
4353
|
}
|
|
4354
4354
|
}
|
|
4355
4355
|
async function createWorktree(repoRoot, branch, targetDir) {
|
|
4356
|
-
|
|
4356
|
+
log8.info(`Creating worktree for branch '${branch}' at ${targetDir}`);
|
|
4357
4357
|
const parentDir = path.dirname(targetDir);
|
|
4358
|
-
|
|
4358
|
+
log8.debug(`Creating parent directory: ${parentDir}`);
|
|
4359
4359
|
await fs.mkdir(parentDir, { recursive: true });
|
|
4360
4360
|
const exists = await branchExists(repoRoot, branch);
|
|
4361
4361
|
if (exists) {
|
|
4362
|
-
|
|
4362
|
+
log8.debug(`Branch '${branch}' exists, adding worktree`);
|
|
4363
4363
|
await execGit(["worktree", "add", targetDir, branch], repoRoot);
|
|
4364
4364
|
} else {
|
|
4365
|
-
|
|
4365
|
+
log8.debug(`Branch '${branch}' does not exist, creating with worktree`);
|
|
4366
4366
|
await execGit(["worktree", "add", "-b", branch, targetDir], repoRoot);
|
|
4367
4367
|
}
|
|
4368
|
-
|
|
4368
|
+
log8.info(`Worktree created successfully: ${targetDir}`);
|
|
4369
4369
|
return targetDir;
|
|
4370
4370
|
}
|
|
4371
4371
|
async function removeWorktree(repoRoot, worktreePath) {
|
|
4372
|
-
|
|
4372
|
+
log8.info(`Removing worktree: ${worktreePath}`);
|
|
4373
4373
|
try {
|
|
4374
4374
|
await execGit(["worktree", "remove", worktreePath], repoRoot);
|
|
4375
|
-
|
|
4375
|
+
log8.debug("Worktree removed cleanly");
|
|
4376
4376
|
} catch (err) {
|
|
4377
|
-
|
|
4377
|
+
log8.debug(`Clean remove failed (${err}), trying force remove`);
|
|
4378
4378
|
await execGit(["worktree", "remove", "--force", worktreePath], repoRoot);
|
|
4379
4379
|
}
|
|
4380
|
-
|
|
4380
|
+
log8.debug("Pruning stale worktree references");
|
|
4381
4381
|
await execGit(["worktree", "prune"], repoRoot);
|
|
4382
|
-
|
|
4382
|
+
log8.info("Worktree removed and pruned successfully");
|
|
4383
4383
|
}
|
|
4384
4384
|
async function findWorktreeByBranch(repoRoot, branch) {
|
|
4385
4385
|
const worktrees = await listWorktrees(repoRoot);
|
|
@@ -4420,14 +4420,14 @@ async function writeMetadataStore(store) {
|
|
|
4420
4420
|
await fs.writeFile(METADATA_STORE_PATH, JSON.stringify(store, null, 2), { encoding: "utf-8", mode: 384 });
|
|
4421
4421
|
await fs.chmod(METADATA_STORE_PATH, 384);
|
|
4422
4422
|
} catch (err) {
|
|
4423
|
-
|
|
4423
|
+
log8.warn(`Failed to write worktree metadata store: ${err}`);
|
|
4424
4424
|
}
|
|
4425
4425
|
}
|
|
4426
4426
|
async function writeWorktreeMetadata(worktreePath, metadata) {
|
|
4427
4427
|
const store = await readMetadataStore();
|
|
4428
4428
|
store[worktreePath] = metadata;
|
|
4429
4429
|
await writeMetadataStore(store);
|
|
4430
|
-
|
|
4430
|
+
log8.debug(`Wrote worktree metadata for: ${worktreePath}`);
|
|
4431
4431
|
}
|
|
4432
4432
|
async function readWorktreeMetadata(worktreePath) {
|
|
4433
4433
|
const store = await readMetadataStore();
|
|
@@ -4450,16 +4450,16 @@ async function removeWorktreeMetadata(worktreePath) {
|
|
|
4450
4450
|
if (store[worktreePath]) {
|
|
4451
4451
|
delete store[worktreePath];
|
|
4452
4452
|
await writeMetadataStore(store);
|
|
4453
|
-
|
|
4453
|
+
log8.debug(`Removed worktree metadata for: ${worktreePath}`);
|
|
4454
4454
|
}
|
|
4455
4455
|
}
|
|
4456
|
-
var
|
|
4456
|
+
var log8, WORKTREES_DIR, METADATA_STORE_PATH;
|
|
4457
4457
|
var init_worktree = __esm(() => {
|
|
4458
4458
|
init_spawn();
|
|
4459
4459
|
init_logger();
|
|
4460
|
-
|
|
4461
|
-
WORKTREES_DIR = path.join(
|
|
4462
|
-
METADATA_STORE_PATH = path.join(
|
|
4460
|
+
log8 = createLogger("git-wt");
|
|
4461
|
+
WORKTREES_DIR = path.join(homedir4(), ".claude-threads", "worktrees");
|
|
4462
|
+
METADATA_STORE_PATH = path.join(homedir4(), ".claude-threads", "worktree-metadata.json");
|
|
4463
4463
|
});
|
|
4464
4464
|
|
|
4465
4465
|
// node_modules/graceful-fs/polyfills.js
|
|
@@ -4935,14 +4935,14 @@ GFS4: `);
|
|
|
4935
4935
|
return close;
|
|
4936
4936
|
}(fs2.close);
|
|
4937
4937
|
fs2.closeSync = function(fs$closeSync) {
|
|
4938
|
-
function
|
|
4938
|
+
function closeSync2(fd) {
|
|
4939
4939
|
fs$closeSync.apply(fs2, arguments);
|
|
4940
4940
|
resetQueue();
|
|
4941
4941
|
}
|
|
4942
|
-
Object.defineProperty(
|
|
4942
|
+
Object.defineProperty(closeSync2, previousSymbol, {
|
|
4943
4943
|
value: fs$closeSync
|
|
4944
4944
|
});
|
|
4945
|
-
return
|
|
4945
|
+
return closeSync2;
|
|
4946
4946
|
}(fs2.closeSync);
|
|
4947
4947
|
if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || "")) {
|
|
4948
4948
|
process.on("exit", function() {
|
|
@@ -6366,7 +6366,7 @@ var require_minimist = __commonJS((exports, module) => {
|
|
|
6366
6366
|
// node_modules/rc/index.js
|
|
6367
6367
|
var require_rc = __commonJS((exports, module) => {
|
|
6368
6368
|
var cc = require_utils();
|
|
6369
|
-
var
|
|
6369
|
+
var join8 = __require("path").join;
|
|
6370
6370
|
var deepExtend = require_deep_extend();
|
|
6371
6371
|
var etc = "/etc";
|
|
6372
6372
|
var win = process.platform === "win32";
|
|
@@ -6392,15 +6392,15 @@ var require_rc = __commonJS((exports, module) => {
|
|
|
6392
6392
|
}
|
|
6393
6393
|
if (!win)
|
|
6394
6394
|
[
|
|
6395
|
-
|
|
6396
|
-
|
|
6395
|
+
join8(etc, name, "config"),
|
|
6396
|
+
join8(etc, name + "rc")
|
|
6397
6397
|
].forEach(addConfigFile);
|
|
6398
6398
|
if (home)
|
|
6399
6399
|
[
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6400
|
+
join8(home, ".config", name, "config"),
|
|
6401
|
+
join8(home, ".config", name),
|
|
6402
|
+
join8(home, "." + name, "config"),
|
|
6403
|
+
join8(home, "." + name + "rc")
|
|
6404
6404
|
].forEach(addConfigFile);
|
|
6405
6405
|
addConfigFile(cc.find("." + name + "rc"));
|
|
6406
6406
|
if (env3.config)
|
|
@@ -6888,14 +6888,14 @@ GFS4: `);
|
|
|
6888
6888
|
return close;
|
|
6889
6889
|
}(fs4.close);
|
|
6890
6890
|
fs4.closeSync = function(fs$closeSync) {
|
|
6891
|
-
function
|
|
6891
|
+
function closeSync2(fd) {
|
|
6892
6892
|
fs$closeSync.apply(fs4, arguments);
|
|
6893
6893
|
resetQueue();
|
|
6894
6894
|
}
|
|
6895
|
-
Object.defineProperty(
|
|
6895
|
+
Object.defineProperty(closeSync2, previousSymbol, {
|
|
6896
6896
|
value: fs$closeSync
|
|
6897
6897
|
});
|
|
6898
|
-
return
|
|
6898
|
+
return closeSync2;
|
|
6899
6899
|
}(fs4.closeSync);
|
|
6900
6900
|
if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || "")) {
|
|
6901
6901
|
process.on("exit", function() {
|
|
@@ -20451,7 +20451,7 @@ var require_react_reconciler_development = __commonJS((exports, module) => {
|
|
|
20451
20451
|
return hook.checkDCE ? true : false;
|
|
20452
20452
|
}
|
|
20453
20453
|
function setIsStrictModeForDevtools(newIsStrictMode) {
|
|
20454
|
-
typeof
|
|
20454
|
+
typeof log44 === "function" && unstable_setDisableYieldValue2(newIsStrictMode);
|
|
20455
20455
|
if (injectedHook && typeof injectedHook.setStrictMode === "function")
|
|
20456
20456
|
try {
|
|
20457
20457
|
injectedHook.setStrictMode(rendererID, newIsStrictMode);
|
|
@@ -28535,7 +28535,7 @@ Check the render method of %s.`, getComponentNameFromFiber(current) || "Unknown"
|
|
|
28535
28535
|
var fiberStack = [];
|
|
28536
28536
|
var index$jscomp$0 = -1, emptyContextObject = {};
|
|
28537
28537
|
Object.freeze(emptyContextObject);
|
|
28538
|
-
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,
|
|
28538
|
+
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, log44 = Scheduler.log, unstable_setDisableYieldValue2 = Scheduler.unstable_setDisableYieldValue, rendererID = null, injectedHook = null, hasLoggedError = false, isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined", lastResetTime = 0;
|
|
28539
28539
|
if (typeof performance === "object" && typeof performance.now === "function") {
|
|
28540
28540
|
var localPerformance = performance;
|
|
28541
28541
|
var getCurrentTime = function() {
|
|
@@ -48059,6 +48059,107 @@ var {
|
|
|
48059
48059
|
Help
|
|
48060
48060
|
} = import__.default;
|
|
48061
48061
|
|
|
48062
|
+
// src/persistence/audit-log.ts
|
|
48063
|
+
init_logger();
|
|
48064
|
+
import { chmodSync, closeSync, constants as fsConstants, fchmodSync, lstatSync, mkdirSync, openSync, writeSync } from "fs";
|
|
48065
|
+
import { join } from "path";
|
|
48066
|
+
import { homedir } from "os";
|
|
48067
|
+
var log = createLogger("audit");
|
|
48068
|
+
var DETAIL_MAX = 500;
|
|
48069
|
+
var enabledPlatforms = new Set;
|
|
48070
|
+
var preparedDirs = new Set;
|
|
48071
|
+
var openFds = new Map;
|
|
48072
|
+
function auditDir() {
|
|
48073
|
+
return process.env.CLAUDE_THREADS_AUDIT_DIR || join(homedir(), ".claude-threads", "audit");
|
|
48074
|
+
}
|
|
48075
|
+
function configureAuditLog(platformId, enabled) {
|
|
48076
|
+
if (enabled)
|
|
48077
|
+
enabledPlatforms.add(platformId);
|
|
48078
|
+
else
|
|
48079
|
+
enabledPlatforms.delete(platformId);
|
|
48080
|
+
}
|
|
48081
|
+
function isAuditEnabled(platformId) {
|
|
48082
|
+
return enabledPlatforms.has(platformId);
|
|
48083
|
+
}
|
|
48084
|
+
function openAuditFd(platformId) {
|
|
48085
|
+
const cached = openFds.get(platformId);
|
|
48086
|
+
if (cached !== undefined)
|
|
48087
|
+
return cached;
|
|
48088
|
+
const dir = auditDir();
|
|
48089
|
+
if (!preparedDirs.has(dir)) {
|
|
48090
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
48091
|
+
chmodSync(dir, 448);
|
|
48092
|
+
preparedDirs.add(dir);
|
|
48093
|
+
}
|
|
48094
|
+
const file = join(dir, `${encodeURIComponent(platformId)}.jsonl`);
|
|
48095
|
+
try {
|
|
48096
|
+
const st = lstatSync(file);
|
|
48097
|
+
if (st.isSymbolicLink() || !st.isFile()) {
|
|
48098
|
+
throw new Error(`audit path exists but is not a regular file: ${file}`);
|
|
48099
|
+
}
|
|
48100
|
+
} catch (err) {
|
|
48101
|
+
if (err.code !== "ENOENT")
|
|
48102
|
+
throw err;
|
|
48103
|
+
}
|
|
48104
|
+
const noFollow = typeof fsConstants.O_NOFOLLOW === "number" ? fsConstants.O_NOFOLLOW : 0;
|
|
48105
|
+
const fd = openSync(file, fsConstants.O_WRONLY | fsConstants.O_APPEND | fsConstants.O_CREAT | noFollow, 384);
|
|
48106
|
+
try {
|
|
48107
|
+
fchmodSync(fd, 384);
|
|
48108
|
+
} catch {}
|
|
48109
|
+
openFds.set(platformId, fd);
|
|
48110
|
+
return fd;
|
|
48111
|
+
}
|
|
48112
|
+
function auditLog(platformId, entry) {
|
|
48113
|
+
if (!enabledPlatforms.has(platformId))
|
|
48114
|
+
return;
|
|
48115
|
+
try {
|
|
48116
|
+
const full = {
|
|
48117
|
+
ts: new Date().toISOString(),
|
|
48118
|
+
platformId,
|
|
48119
|
+
...entry,
|
|
48120
|
+
...entry.detail !== undefined ? { detail: entry.detail.slice(0, DETAIL_MAX) } : {}
|
|
48121
|
+
};
|
|
48122
|
+
writeSync(openAuditFd(platformId), JSON.stringify(full) + `
|
|
48123
|
+
`);
|
|
48124
|
+
} catch (err) {
|
|
48125
|
+
const fd = openFds.get(platformId);
|
|
48126
|
+
if (fd !== undefined) {
|
|
48127
|
+
openFds.delete(platformId);
|
|
48128
|
+
try {
|
|
48129
|
+
closeSync(fd);
|
|
48130
|
+
} catch {}
|
|
48131
|
+
}
|
|
48132
|
+
log.warn(`audit write failed for ${platformId}: ${err}`);
|
|
48133
|
+
}
|
|
48134
|
+
}
|
|
48135
|
+
function auditDetailForTool(tool, input) {
|
|
48136
|
+
if (!input)
|
|
48137
|
+
return;
|
|
48138
|
+
const str = (v) => typeof v === "string" ? v : undefined;
|
|
48139
|
+
switch (tool) {
|
|
48140
|
+
case "Bash":
|
|
48141
|
+
return str(input.command);
|
|
48142
|
+
case "Edit":
|
|
48143
|
+
case "Write":
|
|
48144
|
+
case "Read":
|
|
48145
|
+
case "NotebookEdit":
|
|
48146
|
+
return str(input.file_path);
|
|
48147
|
+
case "Grep":
|
|
48148
|
+
case "Glob":
|
|
48149
|
+
return str(input.pattern);
|
|
48150
|
+
case "WebFetch":
|
|
48151
|
+
case "WebSearch":
|
|
48152
|
+
return str(input.url) ?? str(input.query);
|
|
48153
|
+
default: {
|
|
48154
|
+
try {
|
|
48155
|
+
return JSON.stringify(input);
|
|
48156
|
+
} catch {
|
|
48157
|
+
return;
|
|
48158
|
+
}
|
|
48159
|
+
}
|
|
48160
|
+
}
|
|
48161
|
+
}
|
|
48162
|
+
|
|
48062
48163
|
// src/platform/dm-discovery.ts
|
|
48063
48164
|
var DM_PLATFORM_SEP = "--dm-";
|
|
48064
48165
|
function dmPlatformId(parentId, channelId) {
|
|
@@ -48079,7 +48180,7 @@ function deriveDmPlatformConfig(parent, channelId, partnerUsernames) {
|
|
|
48079
48180
|
|
|
48080
48181
|
// src/session/lifecycle-fsm.ts
|
|
48081
48182
|
init_logger();
|
|
48082
|
-
var
|
|
48183
|
+
var log2 = createLogger("fsm");
|
|
48083
48184
|
var ALLOWED_TRANSITIONS = {
|
|
48084
48185
|
starting: new Set(["active", "paused", "interrupted", "cancelling", "restarting"]),
|
|
48085
48186
|
active: new Set([
|
|
@@ -48118,7 +48219,7 @@ function checkTransition(from, to, sessionId) {
|
|
|
48118
48219
|
if (process.env.CLAUDE_THREADS_FSM_STRICT === "1") {
|
|
48119
48220
|
throw new Error(`${msg} (sessionId=${sessionId})`);
|
|
48120
48221
|
}
|
|
48121
|
-
|
|
48222
|
+
log2.warn(msg, payload);
|
|
48122
48223
|
}
|
|
48123
48224
|
|
|
48124
48225
|
// src/session/timer-manager.ts
|
|
@@ -48183,9 +48284,9 @@ function getSessionStatus(session) {
|
|
|
48183
48284
|
}
|
|
48184
48285
|
|
|
48185
48286
|
// src/config/index.ts
|
|
48186
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
|
|
48287
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync as mkdirSync2, chmodSync as chmodSync2 } from "fs";
|
|
48187
48288
|
import { resolve, dirname } from "path";
|
|
48188
|
-
import { homedir } from "os";
|
|
48289
|
+
import { homedir as homedir2 } from "os";
|
|
48189
48290
|
|
|
48190
48291
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
48191
48292
|
function getDefaultExportFromCjs(x) {
|
|
@@ -51357,6 +51458,14 @@ function resolveRoutinesEnabled(value, fieldPath) {
|
|
|
51357
51458
|
console.warn(`Invalid ${fieldPath ?? "routines"} config: expected boolean, got ${JSON.stringify(value)} — routines stay enabled`);
|
|
51358
51459
|
return true;
|
|
51359
51460
|
}
|
|
51461
|
+
function resolveAuditLogEnabled(value, fieldPath) {
|
|
51462
|
+
if (value === true)
|
|
51463
|
+
return true;
|
|
51464
|
+
if (value === undefined || value === null || value === false)
|
|
51465
|
+
return false;
|
|
51466
|
+
console.warn(`Invalid ${fieldPath ?? "auditLog"} config: expected boolean, got ${JSON.stringify(value)} — audit log stays off`);
|
|
51467
|
+
return false;
|
|
51468
|
+
}
|
|
51360
51469
|
var LIMITS_DEFAULTS = {
|
|
51361
51470
|
maxSessions: 5,
|
|
51362
51471
|
sessionTimeoutMinutes: 30,
|
|
@@ -51425,7 +51534,7 @@ function effectivePermissionMode(input) {
|
|
|
51425
51534
|
}
|
|
51426
51535
|
|
|
51427
51536
|
// src/config/index.ts
|
|
51428
|
-
var CONFIG_PATH = resolve(
|
|
51537
|
+
var CONFIG_PATH = resolve(homedir2(), ".config", "claude-threads", "config.yaml");
|
|
51429
51538
|
function loadConfigWithMigration() {
|
|
51430
51539
|
if (existsSync(CONFIG_PATH)) {
|
|
51431
51540
|
const content = readFileSync(CONFIG_PATH, "utf-8");
|
|
@@ -51436,7 +51545,7 @@ function loadConfigWithMigration() {
|
|
|
51436
51545
|
function saveConfig(config, path = CONFIG_PATH) {
|
|
51437
51546
|
const configDir = dirname(path);
|
|
51438
51547
|
if (!existsSync(configDir)) {
|
|
51439
|
-
|
|
51548
|
+
mkdirSync2(configDir, { recursive: true, mode: 448 });
|
|
51440
51549
|
}
|
|
51441
51550
|
const yamlContent = yaml.dump(config, {
|
|
51442
51551
|
indent: 2,
|
|
@@ -51446,8 +51555,8 @@ function saveConfig(config, path = CONFIG_PATH) {
|
|
|
51446
51555
|
});
|
|
51447
51556
|
writeFileSync(path, yamlContent, { encoding: "utf-8", mode: 384 });
|
|
51448
51557
|
try {
|
|
51449
|
-
|
|
51450
|
-
|
|
51558
|
+
chmodSync2(configDir, 448);
|
|
51559
|
+
chmodSync2(path, 384);
|
|
51451
51560
|
} catch {}
|
|
51452
51561
|
}
|
|
51453
51562
|
function configExists() {
|
|
@@ -51563,7 +51672,9 @@ var EMOJI_UNICODE_TO_NAME = {
|
|
|
51563
51672
|
"\uD83D\uDC64": "bust_in_silhouette",
|
|
51564
51673
|
"\uD83D\uDCCB": "clipboard",
|
|
51565
51674
|
"\uD83D\uDD3D": "small_red_triangle_down",
|
|
51566
|
-
"\uD83C\uDD95": "new"
|
|
51675
|
+
"\uD83C\uDD95": "new",
|
|
51676
|
+
"\uD83D\uDC40": "eyes",
|
|
51677
|
+
"❤️": "heart"
|
|
51567
51678
|
};
|
|
51568
51679
|
function getEmojiName(emoji) {
|
|
51569
51680
|
const mapped = EMOJI_UNICODE_TO_NAME[emoji];
|
|
@@ -51638,6 +51749,27 @@ function resolveDirectChannelMode(cfg) {
|
|
|
51638
51749
|
function resolveApprovals(configured, isDcmSession) {
|
|
51639
51750
|
return configured ?? (isDcmSession ? "owner" : "all_users");
|
|
51640
51751
|
}
|
|
51752
|
+
function normalizeAckReaction(value, fieldPath) {
|
|
51753
|
+
if (value === undefined || value === null)
|
|
51754
|
+
return;
|
|
51755
|
+
if (typeof value === "boolean")
|
|
51756
|
+
return value;
|
|
51757
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
51758
|
+
const name = getEmojiName(value.trim());
|
|
51759
|
+
if (!/^[a-z0-9_+':.-]+$/i.test(name)) {
|
|
51760
|
+
console.warn(`Invalid ${fieldPath}: unknown emoji ${JSON.stringify(value)} — use its shortcode name (e.g. "eyes"); ack reaction disabled`);
|
|
51761
|
+
return;
|
|
51762
|
+
}
|
|
51763
|
+
return name;
|
|
51764
|
+
}
|
|
51765
|
+
console.warn(`Invalid ${fieldPath}: expected boolean or emoji name, got ${JSON.stringify(value)} — ack reaction disabled`);
|
|
51766
|
+
return;
|
|
51767
|
+
}
|
|
51768
|
+
function resolveAckReaction(configured) {
|
|
51769
|
+
if (!configured)
|
|
51770
|
+
return null;
|
|
51771
|
+
return typeof configured === "string" ? configured : "eyes";
|
|
51772
|
+
}
|
|
51641
51773
|
|
|
51642
51774
|
// src/session/authorization.ts
|
|
51643
51775
|
function isAuthorizedForSession(check) {
|
|
@@ -51654,7 +51786,7 @@ function isAuthorizedForSession(check) {
|
|
|
51654
51786
|
// src/mcp/decision-bridge.ts
|
|
51655
51787
|
import { createServer, createConnection } from "node:net";
|
|
51656
51788
|
import { tmpdir } from "node:os";
|
|
51657
|
-
import { join } from "node:path";
|
|
51789
|
+
import { join as join2 } from "node:path";
|
|
51658
51790
|
import { randomUUID } from "node:crypto";
|
|
51659
51791
|
import { mkdtempSync } from "node:fs";
|
|
51660
51792
|
import { rm } from "node:fs/promises";
|
|
@@ -51665,8 +51797,8 @@ function bridgeSocketPath() {
|
|
|
51665
51797
|
if (process.platform === "win32") {
|
|
51666
51798
|
return `\\\\.\\pipe\\ctb-${randomUUID()}`;
|
|
51667
51799
|
}
|
|
51668
|
-
const dir = mkdtempSync(
|
|
51669
|
-
return
|
|
51800
|
+
const dir = mkdtempSync(join2(tmpdir(), "ctb-"));
|
|
51801
|
+
return join2(dir, "b.sock");
|
|
51670
51802
|
}
|
|
51671
51803
|
|
|
51672
51804
|
class DecisionBridgeServer {
|
|
@@ -51736,7 +51868,7 @@ class DecisionBridgeServer {
|
|
|
51736
51868
|
});
|
|
51737
51869
|
} catch (err) {
|
|
51738
51870
|
if (process.platform !== "win32") {
|
|
51739
|
-
await rm(
|
|
51871
|
+
await rm(join2(path, ".."), { recursive: true, force: true }).catch(() => {});
|
|
51740
51872
|
}
|
|
51741
51873
|
throw err;
|
|
51742
51874
|
}
|
|
@@ -51749,7 +51881,7 @@ class DecisionBridgeServer {
|
|
|
51749
51881
|
socket.destroy();
|
|
51750
51882
|
await new Promise((resolve2) => this.server.close(() => resolve2()));
|
|
51751
51883
|
if (process.platform !== "win32") {
|
|
51752
|
-
await rm(
|
|
51884
|
+
await rm(join2(this.path, ".."), { recursive: true, force: true }).catch(() => {});
|
|
51753
51885
|
}
|
|
51754
51886
|
}
|
|
51755
51887
|
}
|
|
@@ -51762,18 +51894,18 @@ import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
|
51762
51894
|
import { fileURLToPath } from "url";
|
|
51763
51895
|
import { existsSync as existsSync3, readFileSync as readFileSync2, watchFile, unwatchFile, unlinkSync, statSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
51764
51896
|
import { tmpdir as tmpdir2 } from "os";
|
|
51765
|
-
import { join as
|
|
51897
|
+
import { join as join4 } from "path";
|
|
51766
51898
|
|
|
51767
51899
|
// src/claude/version-check.ts
|
|
51768
51900
|
var import_semver = __toESM(require_semver2(), 1);
|
|
51769
51901
|
import { execSync } from "child_process";
|
|
51770
51902
|
import { existsSync as existsSync2 } from "fs";
|
|
51771
|
-
import { join as
|
|
51903
|
+
import { join as join3 } from "path";
|
|
51772
51904
|
var COMMON_CLAUDE_PATHS = process.platform === "win32" ? [
|
|
51773
|
-
...process.env.APPDATA ? [
|
|
51774
|
-
...process.env.LOCALAPPDATA ? [
|
|
51775
|
-
...process.env.NVM_SYMLINK ? [
|
|
51776
|
-
...process.env.USERPROFILE ? [
|
|
51905
|
+
...process.env.APPDATA ? [join3(process.env.APPDATA, "npm", "claude.cmd")] : [],
|
|
51906
|
+
...process.env.LOCALAPPDATA ? [join3(process.env.LOCALAPPDATA, "npm", "claude.cmd")] : [],
|
|
51907
|
+
...process.env.NVM_SYMLINK ? [join3(process.env.NVM_SYMLINK, "claude.cmd")] : [],
|
|
51908
|
+
...process.env.USERPROFILE ? [join3(process.env.USERPROFILE, ".bun", "bin", "claude.cmd")] : []
|
|
51777
51909
|
] : [
|
|
51778
51910
|
"/usr/local/bin/claude",
|
|
51779
51911
|
"/opt/homebrew/bin/claude",
|
|
@@ -52032,25 +52164,25 @@ function parseRateLimitEvent(event, now = Date.now()) {
|
|
|
52032
52164
|
}
|
|
52033
52165
|
|
|
52034
52166
|
// src/claude/cli.ts
|
|
52035
|
-
var
|
|
52167
|
+
var log3 = createLogger("claude");
|
|
52036
52168
|
function cleanupBrowserBridgeSockets() {
|
|
52037
52169
|
try {
|
|
52038
52170
|
const tempDir = tmpdir2();
|
|
52039
52171
|
const files = readdirSync(tempDir);
|
|
52040
52172
|
for (const file of files) {
|
|
52041
52173
|
if (file.startsWith("claude-mcp-browser-bridge-")) {
|
|
52042
|
-
const filePath =
|
|
52174
|
+
const filePath = join4(tempDir, file);
|
|
52043
52175
|
try {
|
|
52044
52176
|
const stats = statSync(filePath);
|
|
52045
52177
|
if (stats.isSocket()) {
|
|
52046
52178
|
unlinkSync(filePath);
|
|
52047
|
-
|
|
52179
|
+
log3.debug(`Removed stale browser bridge socket: ${file}`);
|
|
52048
52180
|
}
|
|
52049
52181
|
} catch {}
|
|
52050
52182
|
}
|
|
52051
52183
|
}
|
|
52052
52184
|
} catch (err) {
|
|
52053
|
-
|
|
52185
|
+
log3.debug(`Browser bridge cleanup failed: ${err}`);
|
|
52054
52186
|
}
|
|
52055
52187
|
}
|
|
52056
52188
|
function buildClaudeChildEnv(parentEnv, account, opts) {
|
|
@@ -52109,7 +52241,7 @@ function materializeMcpConfig(config, sessionId, opts = {}) {
|
|
|
52109
52241
|
return { mode: "inline", value: JSON.stringify(config) };
|
|
52110
52242
|
}
|
|
52111
52243
|
const dir = opts.tmpDirOverride ?? tmpdir2();
|
|
52112
|
-
const path =
|
|
52244
|
+
const path = join4(dir, `claude-threads-mcp-${sessionId ?? process.pid}-${Date.now()}.json`);
|
|
52113
52245
|
writeFileSync2(path, JSON.stringify(config), { mode: 384 });
|
|
52114
52246
|
return { mode: "file", path };
|
|
52115
52247
|
}
|
|
@@ -52292,7 +52424,7 @@ class ClaudeCli extends EventEmitter {
|
|
|
52292
52424
|
}
|
|
52293
52425
|
let statusLineCommand;
|
|
52294
52426
|
if (this.options.sessionId) {
|
|
52295
|
-
this.statusFilePath =
|
|
52427
|
+
this.statusFilePath = join4(tmpdir2(), `claude-threads-status-${this.options.sessionId}.json`);
|
|
52296
52428
|
const statusLineWriterPath = this.getStatusLineWriterPath();
|
|
52297
52429
|
const runtime = runtimeForScriptPath(statusLineWriterPath);
|
|
52298
52430
|
statusLineCommand = `${runtime} ${statusLineWriterPath} ${this.options.sessionId}`;
|
|
@@ -52563,11 +52695,11 @@ class ClaudeCli extends EventEmitter {
|
|
|
52563
52695
|
|
|
52564
52696
|
// src/persistence/thread-logger.ts
|
|
52565
52697
|
init_logger();
|
|
52566
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
52567
|
-
import { homedir as
|
|
52568
|
-
import { join as
|
|
52569
|
-
var
|
|
52570
|
-
var LOGS_BASE_DIR =
|
|
52698
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, appendFileSync, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync2, rmdirSync, readFileSync as readFileSync3, chmodSync as chmodSync3 } from "fs";
|
|
52699
|
+
import { homedir as homedir3 } from "os";
|
|
52700
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
52701
|
+
var log4 = createLogger("thread-log");
|
|
52702
|
+
var LOGS_BASE_DIR = join5(homedir3(), ".claude-threads", "logs");
|
|
52571
52703
|
|
|
52572
52704
|
class ThreadLoggerImpl {
|
|
52573
52705
|
platformId;
|
|
@@ -52587,16 +52719,16 @@ class ThreadLoggerImpl {
|
|
|
52587
52719
|
this.enabled = options?.enabled ?? true;
|
|
52588
52720
|
this.bufferSize = options?.bufferSize ?? 10;
|
|
52589
52721
|
this.flushIntervalMs = options?.flushIntervalMs ?? 1000;
|
|
52590
|
-
this.logPath =
|
|
52722
|
+
this.logPath = join5(LOGS_BASE_DIR, platformId, `${claudeSessionId}.jsonl`);
|
|
52591
52723
|
if (this.enabled) {
|
|
52592
52724
|
const dir = dirname3(this.logPath);
|
|
52593
52725
|
if (!existsSync4(dir)) {
|
|
52594
|
-
|
|
52726
|
+
mkdirSync3(dir, { recursive: true });
|
|
52595
52727
|
}
|
|
52596
52728
|
this.flushTimer = setInterval(() => {
|
|
52597
52729
|
this.flushSync();
|
|
52598
52730
|
}, this.flushIntervalMs);
|
|
52599
|
-
|
|
52731
|
+
log4.debug(`Thread logger initialized: ${this.logPath}`);
|
|
52600
52732
|
}
|
|
52601
52733
|
}
|
|
52602
52734
|
isEnabled() {
|
|
@@ -52710,7 +52842,7 @@ class ThreadLoggerImpl {
|
|
|
52710
52842
|
this.flushTimer = null;
|
|
52711
52843
|
}
|
|
52712
52844
|
this.flushSync();
|
|
52713
|
-
|
|
52845
|
+
log4.debug(`Thread logger closed: ${this.logPath}`);
|
|
52714
52846
|
}
|
|
52715
52847
|
addEntry(entry) {
|
|
52716
52848
|
this.buffer.push(entry);
|
|
@@ -52728,11 +52860,11 @@ class ThreadLoggerImpl {
|
|
|
52728
52860
|
const isNewFile = !existsSync4(this.logPath);
|
|
52729
52861
|
appendFileSync(this.logPath, lines, { encoding: "utf8", mode: 384 });
|
|
52730
52862
|
if (isNewFile) {
|
|
52731
|
-
|
|
52863
|
+
chmodSync3(this.logPath, 384);
|
|
52732
52864
|
}
|
|
52733
52865
|
this.buffer = [];
|
|
52734
52866
|
} catch (err) {
|
|
52735
|
-
|
|
52867
|
+
log4.error(`Failed to flush thread log: ${err}`);
|
|
52736
52868
|
}
|
|
52737
52869
|
}
|
|
52738
52870
|
}
|
|
@@ -52769,7 +52901,7 @@ function cleanupOldLogs(retentionDays = 30) {
|
|
|
52769
52901
|
try {
|
|
52770
52902
|
const platformDirs = readdirSync2(LOGS_BASE_DIR);
|
|
52771
52903
|
for (const platformId of platformDirs) {
|
|
52772
|
-
const platformDir =
|
|
52904
|
+
const platformDir = join5(LOGS_BASE_DIR, platformId);
|
|
52773
52905
|
const stat = statSync2(platformDir);
|
|
52774
52906
|
if (!stat.isDirectory())
|
|
52775
52907
|
continue;
|
|
@@ -52777,49 +52909,49 @@ function cleanupOldLogs(retentionDays = 30) {
|
|
|
52777
52909
|
for (const file of logFiles) {
|
|
52778
52910
|
if (!file.endsWith(".jsonl"))
|
|
52779
52911
|
continue;
|
|
52780
|
-
const filePath =
|
|
52912
|
+
const filePath = join5(platformDir, file);
|
|
52781
52913
|
try {
|
|
52782
52914
|
const fileStat = statSync2(filePath);
|
|
52783
52915
|
if (fileStat.mtimeMs < cutoffMs) {
|
|
52784
52916
|
unlinkSync2(filePath);
|
|
52785
52917
|
deletedCount++;
|
|
52786
|
-
|
|
52918
|
+
log4.debug(`Deleted old log file: ${filePath}`);
|
|
52787
52919
|
}
|
|
52788
52920
|
} catch (err) {
|
|
52789
|
-
|
|
52921
|
+
log4.warn(`Failed to check/delete log file ${filePath}: ${err}`);
|
|
52790
52922
|
}
|
|
52791
52923
|
}
|
|
52792
52924
|
try {
|
|
52793
52925
|
const remaining = readdirSync2(platformDir);
|
|
52794
52926
|
if (remaining.length === 0) {
|
|
52795
52927
|
rmdirSync(platformDir);
|
|
52796
|
-
|
|
52928
|
+
log4.debug(`Removed empty platform log directory: ${platformDir}`);
|
|
52797
52929
|
}
|
|
52798
52930
|
} catch {}
|
|
52799
52931
|
}
|
|
52800
52932
|
if (deletedCount > 0) {
|
|
52801
|
-
|
|
52933
|
+
log4.info(`Cleaned up ${deletedCount} old log file(s)`);
|
|
52802
52934
|
}
|
|
52803
52935
|
} catch (err) {
|
|
52804
|
-
|
|
52936
|
+
log4.error(`Failed to clean up old logs: ${err}`);
|
|
52805
52937
|
}
|
|
52806
52938
|
return deletedCount;
|
|
52807
52939
|
}
|
|
52808
52940
|
function getLogFilePath(platformId, sessionId) {
|
|
52809
|
-
return
|
|
52941
|
+
return join5(LOGS_BASE_DIR, platformId, `${sessionId}.jsonl`);
|
|
52810
52942
|
}
|
|
52811
52943
|
function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
|
|
52812
52944
|
const logPath = getLogFilePath(platformId, sessionId);
|
|
52813
|
-
|
|
52945
|
+
log4.debug(`Reading log entries from: ${logPath}`);
|
|
52814
52946
|
if (!existsSync4(logPath)) {
|
|
52815
|
-
|
|
52947
|
+
log4.debug(`Log file does not exist: ${logPath}`);
|
|
52816
52948
|
return [];
|
|
52817
52949
|
}
|
|
52818
52950
|
try {
|
|
52819
52951
|
const content = readFileSync3(logPath, "utf8");
|
|
52820
52952
|
const lines = content.trim().split(`
|
|
52821
52953
|
`);
|
|
52822
|
-
|
|
52954
|
+
log4.debug(`Log file has ${lines.length} lines`);
|
|
52823
52955
|
const recentLines = lines.slice(-maxLines);
|
|
52824
52956
|
const entries = [];
|
|
52825
52957
|
for (const line of recentLines) {
|
|
@@ -52829,10 +52961,10 @@ function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
|
|
|
52829
52961
|
entries.push(JSON.parse(line));
|
|
52830
52962
|
} catch {}
|
|
52831
52963
|
}
|
|
52832
|
-
|
|
52964
|
+
log4.debug(`Parsed ${entries.length} log entries`);
|
|
52833
52965
|
return entries;
|
|
52834
52966
|
} catch (err) {
|
|
52835
|
-
|
|
52967
|
+
log4.error(`Failed to read log file: ${err}`);
|
|
52836
52968
|
return [];
|
|
52837
52969
|
}
|
|
52838
52970
|
}
|
|
@@ -53799,7 +53931,7 @@ async function handleDynamicSlashCommand(command, args, ctx) {
|
|
|
53799
53931
|
}
|
|
53800
53932
|
// src/commands/system-prompt-generator.ts
|
|
53801
53933
|
init_logger();
|
|
53802
|
-
var
|
|
53934
|
+
var log5 = createLogger("system-prompt");
|
|
53803
53935
|
function formatUserCommand(cmd) {
|
|
53804
53936
|
const cmdStr = cmd.args ? `\`!${cmd.command} ${cmd.args}\`` : `\`!${cmd.command}\``;
|
|
53805
53937
|
const description = cmd.description;
|
|
@@ -53838,7 +53970,7 @@ async function resolveCollaborators(platform, platformId, ownerUsername, allowed
|
|
|
53838
53970
|
continue;
|
|
53839
53971
|
const email = githubEmailsStore.get(platformId, username);
|
|
53840
53972
|
if (!email) {
|
|
53841
|
-
|
|
53973
|
+
log5.debug(`Collaborator @${username} has no registered GitHub noreply email — skipping`);
|
|
53842
53974
|
continue;
|
|
53843
53975
|
}
|
|
53844
53976
|
let name = username;
|
|
@@ -53847,7 +53979,7 @@ async function resolveCollaborators(platform, platformId, ownerUsername, allowed
|
|
|
53847
53979
|
if (user)
|
|
53848
53980
|
name = user.displayName || user.username;
|
|
53849
53981
|
} catch (err) {
|
|
53850
|
-
|
|
53982
|
+
log5.debug(`Display name lookup failed for @${username}: ${err.message}`);
|
|
53851
53983
|
}
|
|
53852
53984
|
resolved.push({ username, name, email });
|
|
53853
53985
|
}
|
|
@@ -53976,7 +54108,7 @@ import { existsSync as existsSync11 } from "fs";
|
|
|
53976
54108
|
// src/utils/keep-alive.ts
|
|
53977
54109
|
init_logger();
|
|
53978
54110
|
import { spawn } from "child_process";
|
|
53979
|
-
var
|
|
54111
|
+
var log6 = createLogger("keepalive");
|
|
53980
54112
|
function keepAliveSpawnSpec(platform, parentPid) {
|
|
53981
54113
|
switch (platform) {
|
|
53982
54114
|
case "darwin":
|
|
@@ -54032,7 +54164,7 @@ class KeepAliveManager {
|
|
|
54032
54164
|
if (!enabled && this.keepAliveProcess) {
|
|
54033
54165
|
this.stopKeepAlive();
|
|
54034
54166
|
}
|
|
54035
|
-
|
|
54167
|
+
log6.debug(`Keep-alive ${enabled ? "enabled" : "disabled"}`);
|
|
54036
54168
|
}
|
|
54037
54169
|
isEnabled() {
|
|
54038
54170
|
return this.enabled;
|
|
@@ -54042,7 +54174,7 @@ class KeepAliveManager {
|
|
|
54042
54174
|
}
|
|
54043
54175
|
sessionStarted() {
|
|
54044
54176
|
this.activeSessionCount++;
|
|
54045
|
-
|
|
54177
|
+
log6.debug(`Session started (${this.activeSessionCount} active)`);
|
|
54046
54178
|
if (this.activeSessionCount === 1) {
|
|
54047
54179
|
this.startKeepAlive();
|
|
54048
54180
|
}
|
|
@@ -54051,7 +54183,7 @@ class KeepAliveManager {
|
|
|
54051
54183
|
if (this.activeSessionCount > 0) {
|
|
54052
54184
|
this.activeSessionCount--;
|
|
54053
54185
|
}
|
|
54054
|
-
|
|
54186
|
+
log6.debug(`Session ended (${this.activeSessionCount} active)`);
|
|
54055
54187
|
if (this.activeSessionCount === 0) {
|
|
54056
54188
|
this.stopKeepAlive();
|
|
54057
54189
|
}
|
|
@@ -54065,11 +54197,11 @@ class KeepAliveManager {
|
|
|
54065
54197
|
}
|
|
54066
54198
|
startKeepAlive() {
|
|
54067
54199
|
if (!this.enabled) {
|
|
54068
|
-
|
|
54200
|
+
log6.debug("Keep-alive disabled, skipping");
|
|
54069
54201
|
return;
|
|
54070
54202
|
}
|
|
54071
54203
|
if (this.keepAliveProcess) {
|
|
54072
|
-
|
|
54204
|
+
log6.debug("Keep-alive already running");
|
|
54073
54205
|
return;
|
|
54074
54206
|
}
|
|
54075
54207
|
switch (this.platform) {
|
|
@@ -54083,12 +54215,12 @@ class KeepAliveManager {
|
|
|
54083
54215
|
this.startWindowsKeepAlive();
|
|
54084
54216
|
break;
|
|
54085
54217
|
default:
|
|
54086
|
-
|
|
54218
|
+
log6.warn(`Keep-alive not supported on ${this.platform}`);
|
|
54087
54219
|
}
|
|
54088
54220
|
}
|
|
54089
54221
|
stopKeepAlive() {
|
|
54090
54222
|
if (this.keepAliveProcess) {
|
|
54091
|
-
|
|
54223
|
+
log6.debug("Stopping keep-alive");
|
|
54092
54224
|
this.keepAliveProcess.kill();
|
|
54093
54225
|
this.keepAliveProcess = null;
|
|
54094
54226
|
}
|
|
@@ -54103,18 +54235,18 @@ class KeepAliveManager {
|
|
|
54103
54235
|
detached: false
|
|
54104
54236
|
});
|
|
54105
54237
|
this.keepAliveProcess.on("error", (err) => {
|
|
54106
|
-
|
|
54238
|
+
log6.error(`Failed to start caffeinate: ${err.message}`);
|
|
54107
54239
|
this.keepAliveProcess = null;
|
|
54108
54240
|
});
|
|
54109
54241
|
this.keepAliveProcess.on("exit", (code) => {
|
|
54110
54242
|
if (code !== null && code !== 0 && this.activeSessionCount > 0) {
|
|
54111
|
-
|
|
54243
|
+
log6.debug(`caffeinate exited with code ${code}`);
|
|
54112
54244
|
}
|
|
54113
54245
|
this.keepAliveProcess = null;
|
|
54114
54246
|
});
|
|
54115
|
-
|
|
54247
|
+
log6.info("Sleep prevention active (caffeinate)");
|
|
54116
54248
|
} catch (err) {
|
|
54117
|
-
|
|
54249
|
+
log6.error(`Failed to start caffeinate: ${err}`);
|
|
54118
54250
|
}
|
|
54119
54251
|
}
|
|
54120
54252
|
startLinuxKeepAlive() {
|
|
@@ -54127,19 +54259,19 @@ class KeepAliveManager {
|
|
|
54127
54259
|
detached: false
|
|
54128
54260
|
});
|
|
54129
54261
|
this.keepAliveProcess.on("error", (err) => {
|
|
54130
|
-
|
|
54262
|
+
log6.debug(`systemd-inhibit not available: ${err.message}`);
|
|
54131
54263
|
this.keepAliveProcess = null;
|
|
54132
54264
|
this.startLinuxKeepAliveFallback();
|
|
54133
54265
|
});
|
|
54134
54266
|
this.keepAliveProcess.on("exit", (code) => {
|
|
54135
54267
|
if (code !== null && code !== 0 && this.activeSessionCount > 0) {
|
|
54136
|
-
|
|
54268
|
+
log6.debug(`systemd-inhibit exited with code ${code}`);
|
|
54137
54269
|
}
|
|
54138
54270
|
this.keepAliveProcess = null;
|
|
54139
54271
|
});
|
|
54140
|
-
|
|
54272
|
+
log6.info("Sleep prevention active (systemd-inhibit)");
|
|
54141
54273
|
} catch (err) {
|
|
54142
|
-
|
|
54274
|
+
log6.debug(`Failed to start systemd-inhibit: ${err}`);
|
|
54143
54275
|
this.startLinuxKeepAliveFallback();
|
|
54144
54276
|
}
|
|
54145
54277
|
}
|
|
@@ -54150,15 +54282,15 @@ class KeepAliveManager {
|
|
|
54150
54282
|
detached: false
|
|
54151
54283
|
});
|
|
54152
54284
|
this.keepAliveProcess.on("error", (err) => {
|
|
54153
|
-
|
|
54285
|
+
log6.warn(`Linux keep-alive fallback not available: ${err.message}`);
|
|
54154
54286
|
this.keepAliveProcess = null;
|
|
54155
54287
|
});
|
|
54156
54288
|
this.keepAliveProcess.on("exit", () => {
|
|
54157
54289
|
this.keepAliveProcess = null;
|
|
54158
54290
|
});
|
|
54159
|
-
|
|
54291
|
+
log6.info("Sleep prevention active (xdg-screensaver)");
|
|
54160
54292
|
} catch (err) {
|
|
54161
|
-
|
|
54293
|
+
log6.warn(`Linux keep-alive not available: ${err}`);
|
|
54162
54294
|
}
|
|
54163
54295
|
}
|
|
54164
54296
|
startWindowsKeepAlive() {
|
|
@@ -54170,18 +54302,18 @@ class KeepAliveManager {
|
|
|
54170
54302
|
windowsHide: true
|
|
54171
54303
|
});
|
|
54172
54304
|
this.keepAliveProcess.on("error", (err) => {
|
|
54173
|
-
|
|
54305
|
+
log6.warn(`Windows keep-alive not available: ${err.message}`);
|
|
54174
54306
|
this.keepAliveProcess = null;
|
|
54175
54307
|
});
|
|
54176
54308
|
this.keepAliveProcess.on("exit", (code) => {
|
|
54177
54309
|
if (code !== null && code !== 0 && this.activeSessionCount > 0) {
|
|
54178
|
-
|
|
54310
|
+
log6.debug(`PowerShell keep-alive exited with code ${code}`);
|
|
54179
54311
|
}
|
|
54180
54312
|
this.keepAliveProcess = null;
|
|
54181
54313
|
});
|
|
54182
|
-
|
|
54314
|
+
log6.info("Sleep prevention active (SetThreadExecutionState)");
|
|
54183
54315
|
} catch (err) {
|
|
54184
|
-
|
|
54316
|
+
log6.warn(`Windows keep-alive not available: ${err}`);
|
|
54185
54317
|
}
|
|
54186
54318
|
}
|
|
54187
54319
|
}
|
|
@@ -54249,7 +54381,7 @@ function truncateAtWord(str2, maxLength) {
|
|
|
54249
54381
|
}
|
|
54250
54382
|
|
|
54251
54383
|
// src/utils/error-handler/index.ts
|
|
54252
|
-
var
|
|
54384
|
+
var log7 = createLogger("error");
|
|
54253
54385
|
|
|
54254
54386
|
class SessionError extends Error {
|
|
54255
54387
|
sessionId;
|
|
@@ -54275,19 +54407,19 @@ async function handleError(error, context, severity = "recoverable") {
|
|
|
54275
54407
|
const sessionPart = sessionId ? ` (${formatShortId(sessionId)})` : "";
|
|
54276
54408
|
const logMessage = `${context.action}${sessionPart}: ${message}`;
|
|
54277
54409
|
if (severity === "recoverable") {
|
|
54278
|
-
|
|
54410
|
+
log7.warn(logMessage);
|
|
54279
54411
|
} else {
|
|
54280
|
-
|
|
54412
|
+
log7.error(logMessage, error instanceof Error ? error : undefined);
|
|
54281
54413
|
}
|
|
54282
54414
|
if (context.details) {
|
|
54283
|
-
|
|
54415
|
+
log7.debugJson("Error details", context.details);
|
|
54284
54416
|
}
|
|
54285
54417
|
if (context.notifyUser && context.session) {
|
|
54286
54418
|
try {
|
|
54287
54419
|
const fmt = context.session.platform.getFormatter();
|
|
54288
54420
|
await context.session.platform.createPost(`⚠️ ${fmt.formatBold("Error")}: ${context.action} failed - ${message}`, context.session.threadId);
|
|
54289
54421
|
} catch (notifyError) {
|
|
54290
|
-
|
|
54422
|
+
log7.warn(`Could not notify user: ${notifyError}`);
|
|
54291
54423
|
}
|
|
54292
54424
|
}
|
|
54293
54425
|
if (severity === "session-fatal" || severity === "system-fatal") {
|
|
@@ -54314,7 +54446,7 @@ async function logAndNotify(error, context) {
|
|
|
54314
54446
|
}
|
|
54315
54447
|
function logSilentError(context, error) {
|
|
54316
54448
|
const message = error instanceof Error ? error.message : String(error);
|
|
54317
|
-
|
|
54449
|
+
log7.debug(`[${context}] Silently caught: ${message}`);
|
|
54318
54450
|
}
|
|
54319
54451
|
|
|
54320
54452
|
// src/session/lifecycle.ts
|
|
@@ -54334,8 +54466,8 @@ function createSessionLog(baseLog) {
|
|
|
54334
54466
|
init_logger();
|
|
54335
54467
|
init_emoji();
|
|
54336
54468
|
init_worktree();
|
|
54337
|
-
var
|
|
54338
|
-
var sessionLog = createSessionLog(
|
|
54469
|
+
var log9 = createLogger("helpers");
|
|
54470
|
+
var sessionLog = createSessionLog(log9);
|
|
54339
54471
|
var POST_TYPES = {
|
|
54340
54472
|
info: "",
|
|
54341
54473
|
success: "✅",
|
|
@@ -54423,7 +54555,7 @@ function updateLastMessage(session, post2) {
|
|
|
54423
54555
|
init_logger();
|
|
54424
54556
|
import { lstat, mkdir as mkdir2, mkdtemp, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
54425
54557
|
import { tmpdir as tmpdir3 } from "os";
|
|
54426
|
-
import { join as
|
|
54558
|
+
import { join as join7 } from "path";
|
|
54427
54559
|
|
|
54428
54560
|
// src/utils/safe-filename.ts
|
|
54429
54561
|
import { basename as basename2 } from "path";
|
|
@@ -54464,13 +54596,13 @@ function formatBytes(bytes) {
|
|
|
54464
54596
|
}
|
|
54465
54597
|
|
|
54466
54598
|
// src/operations/streaming/handler.ts
|
|
54467
|
-
var
|
|
54599
|
+
var log10 = createLogger("streaming");
|
|
54468
54600
|
var UPLOAD_ROOT_DIR = "claude-threads-uploads";
|
|
54469
54601
|
function safeIdSegment(id) {
|
|
54470
54602
|
return id.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
54471
54603
|
}
|
|
54472
54604
|
function getSessionUploadDir(platformId, threadId) {
|
|
54473
|
-
return
|
|
54605
|
+
return join7(tmpdir3(), UPLOAD_ROOT_DIR, `${safeIdSegment(platformId)}-${safeIdSegment(threadId)}`);
|
|
54474
54606
|
}
|
|
54475
54607
|
async function cleanupSessionUploads(platformId, threadId) {
|
|
54476
54608
|
if (!platformId || !threadId)
|
|
@@ -54479,7 +54611,7 @@ async function cleanupSessionUploads(platformId, threadId) {
|
|
|
54479
54611
|
try {
|
|
54480
54612
|
await rm2(dir, { recursive: true, force: true });
|
|
54481
54613
|
} catch (err) {
|
|
54482
|
-
|
|
54614
|
+
log10.debug(`Upload cleanup for ${platformId}:${threadId} failed (ignored): ${err}`);
|
|
54483
54615
|
}
|
|
54484
54616
|
}
|
|
54485
54617
|
function sanitizeForPrompt(value) {
|
|
@@ -54500,16 +54632,16 @@ async function saveFilesToUploadDir(platform, uploadDir, files, debug = false) {
|
|
|
54500
54632
|
for (const file of files) {
|
|
54501
54633
|
skipped.push({ name: file.name, reason: "Refusing to write under symlinked upload directory" });
|
|
54502
54634
|
}
|
|
54503
|
-
|
|
54635
|
+
log10.error(`Upload dir is a symlink, refusing all writes: ${uploadDir}`);
|
|
54504
54636
|
return { saved, skipped };
|
|
54505
54637
|
}
|
|
54506
|
-
const messageDir = await mkdtemp(
|
|
54638
|
+
const messageDir = await mkdtemp(join7(uploadDir, `${Date.now().toString(36)}-`));
|
|
54507
54639
|
const usedNames = new Set;
|
|
54508
54640
|
for (const file of files) {
|
|
54509
54641
|
try {
|
|
54510
54642
|
const buffer = await platform.downloadFile(file.id);
|
|
54511
54643
|
const safeName = dedupeFilename(sanitizeFilename(file.name), usedNames);
|
|
54512
|
-
const absolutePath =
|
|
54644
|
+
const absolutePath = join7(messageDir, safeName);
|
|
54513
54645
|
await writeFile2(absolutePath, buffer, { mode: 384, flag: "wx" });
|
|
54514
54646
|
saved.push({
|
|
54515
54647
|
originalName: file.name,
|
|
@@ -54518,11 +54650,11 @@ async function saveFilesToUploadDir(platform, uploadDir, files, debug = false) {
|
|
|
54518
54650
|
size: buffer.length
|
|
54519
54651
|
});
|
|
54520
54652
|
if (debug) {
|
|
54521
|
-
|
|
54653
|
+
log10.debug(`Saved ${file.name} → ${absolutePath} (${formatBytes(buffer.length)})`);
|
|
54522
54654
|
}
|
|
54523
54655
|
} catch (err) {
|
|
54524
54656
|
const message = err instanceof Error ? err.message : String(err);
|
|
54525
|
-
|
|
54657
|
+
log10.error(`Failed to save uploaded file ${file.name}: ${message}`);
|
|
54526
54658
|
skipped.push({
|
|
54527
54659
|
name: file.name,
|
|
54528
54660
|
reason: `Download failed: ${message}`
|
|
@@ -58024,7 +58156,7 @@ init_emoji();
|
|
|
58024
58156
|
import { execSync as execSync2 } from "child_process";
|
|
58025
58157
|
import { writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
58026
58158
|
import { tmpdir as tmpdir4 } from "os";
|
|
58027
|
-
import { join as
|
|
58159
|
+
import { join as join8 } from "path";
|
|
58028
58160
|
|
|
58029
58161
|
// node_modules/@redactpii/node/lib/index.mjs
|
|
58030
58162
|
class Redactor {
|
|
@@ -58604,7 +58736,7 @@ async function createGitHubIssue(title, body, workingDir) {
|
|
|
58604
58736
|
if (!ghStatus.installed || !ghStatus.authenticated) {
|
|
58605
58737
|
throw new Error(ghStatus.error);
|
|
58606
58738
|
}
|
|
58607
|
-
const bodyFile =
|
|
58739
|
+
const bodyFile = join8(tmpdir4(), `bug-body-${Date.now()}.md`);
|
|
58608
58740
|
try {
|
|
58609
58741
|
writeFileSync4(bodyFile, body, "utf-8");
|
|
58610
58742
|
const cmd = `gh issue create --repo "${GITHUB_REPO}" --title "${escapeShell(title)}" --body-file "${bodyFile}"`;
|
|
@@ -62155,14 +62287,29 @@ class QuestionApprovalExecutor extends BaseExecutor {
|
|
|
62155
62287
|
return false;
|
|
62156
62288
|
}
|
|
62157
62289
|
if (this.state.pendingApproval?.postId === postId) {
|
|
62290
|
+
const approvalType = this.state.pendingApproval.type;
|
|
62291
|
+
const auditDecision = (approved) => {
|
|
62292
|
+
if (approvalType !== "plan")
|
|
62293
|
+
return;
|
|
62294
|
+
auditLog(ctx.platform.platformId, {
|
|
62295
|
+
threadId: ctx.threadId,
|
|
62296
|
+
sessionId: ctx.sessionId,
|
|
62297
|
+
actor: user,
|
|
62298
|
+
kind: "plan_approval",
|
|
62299
|
+
approved,
|
|
62300
|
+
detail: "via reaction"
|
|
62301
|
+
});
|
|
62302
|
+
};
|
|
62158
62303
|
if (isApprovalEmoji(emoji)) {
|
|
62159
62304
|
ctx.logger.debug(`Approval reaction from @${user}: approved`);
|
|
62305
|
+
auditDecision(true);
|
|
62160
62306
|
const handled = await this.handleApprovalResponse(postId, true, ctx);
|
|
62161
62307
|
ctx.logger.debug(`QuestionApprovalExecutor: approval outcome=approved, handled=${handled}`);
|
|
62162
62308
|
return handled;
|
|
62163
62309
|
}
|
|
62164
62310
|
if (isDenialEmoji(emoji)) {
|
|
62165
62311
|
ctx.logger.debug(`Approval reaction from @${user}: denied`);
|
|
62312
|
+
auditDecision(false);
|
|
62166
62313
|
const handled = await this.handleApprovalResponse(postId, false, ctx);
|
|
62167
62314
|
ctx.logger.debug(`QuestionApprovalExecutor: approval outcome=denied, handled=${handled}`);
|
|
62168
62315
|
return handled;
|
|
@@ -62620,7 +62767,7 @@ class BugReportExecutor extends BaseExecutor {
|
|
|
62620
62767
|
// src/operations/executors/worktree-prompt.ts
|
|
62621
62768
|
init_emoji();
|
|
62622
62769
|
init_logger();
|
|
62623
|
-
var
|
|
62770
|
+
var log11 = createLogger("wt-prompt");
|
|
62624
62771
|
// src/operations/message-manager.ts
|
|
62625
62772
|
init_logger();
|
|
62626
62773
|
|
|
@@ -62700,7 +62847,7 @@ function formatRelativeTime(date) {
|
|
|
62700
62847
|
return `${diffMin} min ago`;
|
|
62701
62848
|
}
|
|
62702
62849
|
// src/operations/message-manager.ts
|
|
62703
|
-
var
|
|
62850
|
+
var log12 = createLogger("msg-mgr");
|
|
62704
62851
|
|
|
62705
62852
|
class MessageManager {
|
|
62706
62853
|
platform;
|
|
@@ -62793,7 +62940,7 @@ class MessageManager {
|
|
|
62793
62940
|
});
|
|
62794
62941
|
}
|
|
62795
62942
|
async handleEvent(event) {
|
|
62796
|
-
const logger =
|
|
62943
|
+
const logger = log12.forSession(this.sessionId);
|
|
62797
62944
|
const transformCtx = {
|
|
62798
62945
|
sessionId: this.sessionId,
|
|
62799
62946
|
formatter: this.platform.getFormatter(),
|
|
@@ -62847,7 +62994,7 @@ class MessageManager {
|
|
|
62847
62994
|
}
|
|
62848
62995
|
}
|
|
62849
62996
|
async executeOperation(op) {
|
|
62850
|
-
const logger =
|
|
62997
|
+
const logger = log12.forSession(this.sessionId);
|
|
62851
62998
|
const ctx = this.getExecutorContext();
|
|
62852
62999
|
try {
|
|
62853
63000
|
if (isContentOp(op)) {
|
|
@@ -62915,7 +63062,7 @@ class MessageManager {
|
|
|
62915
63062
|
threadId: this.threadId,
|
|
62916
63063
|
platform: this.platform,
|
|
62917
63064
|
formatter: this.platform.getFormatter(),
|
|
62918
|
-
logger:
|
|
63065
|
+
logger: log12.forSession(this.sessionId),
|
|
62919
63066
|
postTracker: this.postTracker,
|
|
62920
63067
|
contentBreaker: this.contentBreaker,
|
|
62921
63068
|
threadLogger: this.session.threadLogger,
|
|
@@ -63135,13 +63282,13 @@ class MessageManager {
|
|
|
63135
63282
|
return this.systemExecutor.postSuccess(message, this.getExecutorContext());
|
|
63136
63283
|
}
|
|
63137
63284
|
async prepareForUserMessage() {
|
|
63138
|
-
const logger =
|
|
63285
|
+
const logger = log12.forSession(this.sessionId);
|
|
63139
63286
|
logger.debug("Preparing for new user message");
|
|
63140
63287
|
await this.closeCurrentPost();
|
|
63141
63288
|
await this.bumpTaskList();
|
|
63142
63289
|
}
|
|
63143
63290
|
async handleUserMessage(message, files, username, displayName) {
|
|
63144
|
-
const logger =
|
|
63291
|
+
const logger = log12.forSession(this.sessionId);
|
|
63145
63292
|
if (!this.session.claude.isRunning()) {
|
|
63146
63293
|
logger.debug("Claude not running, ignoring user message");
|
|
63147
63294
|
return false;
|
|
@@ -63184,7 +63331,7 @@ class MessageManager {
|
|
|
63184
63331
|
];
|
|
63185
63332
|
}
|
|
63186
63333
|
async handleReaction(postId, emoji, user, action) {
|
|
63187
|
-
const logger =
|
|
63334
|
+
const logger = log12.forSession(this.sessionId);
|
|
63188
63335
|
const ctx = this.getExecutorContext();
|
|
63189
63336
|
logger.debug(`Routing reaction: postId=${postId}, emoji=${emoji}, user=${user}, action=${action}`);
|
|
63190
63337
|
for (const { name, executor } of this.reactionDispatchList()) {
|
|
@@ -63282,7 +63429,7 @@ class MessageManager {
|
|
|
63282
63429
|
}
|
|
63283
63430
|
// src/operations/sticky-message/handler.ts
|
|
63284
63431
|
init_logger();
|
|
63285
|
-
var
|
|
63432
|
+
var log13 = createLogger("sticky");
|
|
63286
63433
|
var botStartedAt = new Date;
|
|
63287
63434
|
function getPendingPrompts(session) {
|
|
63288
63435
|
const prompts = [];
|
|
@@ -63357,21 +63504,21 @@ function initialize(store) {
|
|
|
63357
63504
|
stickyPostIds.set(platformId, postId);
|
|
63358
63505
|
}
|
|
63359
63506
|
if (persistedIds.size > 0) {
|
|
63360
|
-
|
|
63507
|
+
log13.info(`\uD83D\uDCCC Restored ${persistedIds.size} sticky post ID(s) from persistence`);
|
|
63361
63508
|
}
|
|
63362
63509
|
}
|
|
63363
63510
|
function setPlatformPaused(platformId, paused) {
|
|
63364
63511
|
if (paused) {
|
|
63365
63512
|
pausedPlatforms.set(platformId, true);
|
|
63366
|
-
|
|
63513
|
+
log13.debug(`Platform ${platformId} marked as paused`);
|
|
63367
63514
|
} else {
|
|
63368
63515
|
pausedPlatforms.delete(platformId);
|
|
63369
|
-
|
|
63516
|
+
log13.debug(`Platform ${platformId} marked as active`);
|
|
63370
63517
|
}
|
|
63371
63518
|
}
|
|
63372
63519
|
function setShuttingDown(shuttingDown) {
|
|
63373
63520
|
isShuttingDown = shuttingDown;
|
|
63374
|
-
|
|
63521
|
+
log13.debug(`Bot shutdown state: ${shuttingDown}`);
|
|
63375
63522
|
}
|
|
63376
63523
|
function getTaskContent(session) {
|
|
63377
63524
|
const taskState = session.messageManager?.getTaskListState();
|
|
@@ -63704,12 +63851,12 @@ async function validateLastMessageIds(platform, sessions) {
|
|
|
63704
63851
|
try {
|
|
63705
63852
|
const post2 = await platform.getPost(lastMessageId);
|
|
63706
63853
|
if (!post2) {
|
|
63707
|
-
|
|
63854
|
+
log13.debug(`lastMessageId ${lastMessageId.substring(0, 8)} for session ${session.sessionId} was deleted, clearing`);
|
|
63708
63855
|
session.lastMessageId = undefined;
|
|
63709
63856
|
session.lastMessageTs = undefined;
|
|
63710
63857
|
}
|
|
63711
63858
|
} catch (err) {
|
|
63712
|
-
|
|
63859
|
+
log13.debug(`Failed to validate lastMessageId for session ${session.sessionId}, clearing: ${err}`);
|
|
63713
63860
|
session.lastMessageId = undefined;
|
|
63714
63861
|
session.lastMessageTs = undefined;
|
|
63715
63862
|
}
|
|
@@ -63726,7 +63873,7 @@ async function updateStickyMessageImpl(platform, sessions, config) {
|
|
|
63726
63873
|
hiddenCleanupDone.add(platform.platformId);
|
|
63727
63874
|
const existing = stickyPostIds.get(platform.platformId);
|
|
63728
63875
|
if (existing) {
|
|
63729
|
-
|
|
63876
|
+
log13.info(`sticky[${platform.platformId}] hidden mode: removing leftover ${formatShortId(existing)}`);
|
|
63730
63877
|
try {
|
|
63731
63878
|
await platform.unpinPost(existing);
|
|
63732
63879
|
} catch {}
|
|
@@ -63746,63 +63893,63 @@ async function updateStickyMessageImpl(platform, sessions, config) {
|
|
|
63746
63893
|
return;
|
|
63747
63894
|
}
|
|
63748
63895
|
const platformSessions = [...sessions.values()].filter((s) => s.platformId === platform.platformId);
|
|
63749
|
-
|
|
63896
|
+
log13.debug(`updateStickyMessage for ${platform.platformId}, ${platformSessions.length} sessions`);
|
|
63750
63897
|
for (const s of platformSessions) {
|
|
63751
|
-
|
|
63898
|
+
log13.debug(` - ${s.sessionId}: title="${s.sessionTitle}" firstPrompt="${s.firstPrompt?.substring(0, 30)}..."`);
|
|
63752
63899
|
}
|
|
63753
63900
|
await validateLastMessageIds(platform, platformSessions);
|
|
63754
63901
|
const formatter = platform.getFormatter();
|
|
63755
63902
|
const content = await buildStickyMessage(sessions, platform.platformId, config, formatter, (threadId) => platform.getThreadLink(threadId));
|
|
63756
63903
|
const existingPostId = stickyPostIds.get(platform.platformId);
|
|
63757
63904
|
const shouldBump = needsBump.get(platform.platformId) ?? false;
|
|
63758
|
-
|
|
63905
|
+
log13.debug(`existingPostId: ${existingPostId || "(none)"}, needsBump: ${shouldBump}`);
|
|
63759
63906
|
try {
|
|
63760
63907
|
if (existingPostId && !shouldBump) {
|
|
63761
|
-
|
|
63908
|
+
log13.debug(`Updating existing post in place...`);
|
|
63762
63909
|
try {
|
|
63763
63910
|
await platform.updatePost(existingPostId, content);
|
|
63764
63911
|
try {
|
|
63765
63912
|
await platform.pinPost(existingPostId);
|
|
63766
|
-
|
|
63913
|
+
log13.debug(`Re-pinned post`);
|
|
63767
63914
|
} catch (pinErr) {
|
|
63768
|
-
|
|
63915
|
+
log13.debug(`Re-pin failed (might already be pinned): ${pinErr}`);
|
|
63769
63916
|
}
|
|
63770
|
-
|
|
63917
|
+
log13.debug(`Updated successfully`);
|
|
63771
63918
|
return;
|
|
63772
63919
|
} catch (err) {
|
|
63773
|
-
|
|
63920
|
+
log13.debug(`Update failed, will create new: ${err}`);
|
|
63774
63921
|
}
|
|
63775
63922
|
}
|
|
63776
63923
|
needsBump.set(platform.platformId, false);
|
|
63777
63924
|
if (existingPostId) {
|
|
63778
|
-
|
|
63925
|
+
log13.debug(`Unpinning and deleting existing post ${existingPostId.substring(0, 8)}...`);
|
|
63779
63926
|
try {
|
|
63780
63927
|
await platform.unpinPost(existingPostId);
|
|
63781
|
-
|
|
63928
|
+
log13.debug(`Unpinned successfully`);
|
|
63782
63929
|
} catch (err) {
|
|
63783
|
-
|
|
63930
|
+
log13.debug(`Unpin failed (probably already unpinned): ${err}`);
|
|
63784
63931
|
}
|
|
63785
63932
|
try {
|
|
63786
63933
|
await platform.deletePost(existingPostId);
|
|
63787
|
-
|
|
63934
|
+
log13.debug(`Deleted successfully`);
|
|
63788
63935
|
} catch (err) {
|
|
63789
|
-
|
|
63936
|
+
log13.debug(`Delete failed (probably already deleted): ${err}`);
|
|
63790
63937
|
}
|
|
63791
63938
|
stickyPostIds.delete(platform.platformId);
|
|
63792
63939
|
}
|
|
63793
|
-
|
|
63940
|
+
log13.debug(`Creating new post...`);
|
|
63794
63941
|
const post2 = await platform.createPost(content);
|
|
63795
63942
|
stickyPostIds.set(platform.platformId, post2.id);
|
|
63796
63943
|
try {
|
|
63797
63944
|
await platform.pinPost(post2.id);
|
|
63798
|
-
|
|
63945
|
+
log13.debug(`Pinned post successfully`);
|
|
63799
63946
|
} catch (err) {
|
|
63800
|
-
|
|
63947
|
+
log13.debug(`Failed to pin post: ${err}`);
|
|
63801
63948
|
}
|
|
63802
63949
|
if (sessionStore) {
|
|
63803
63950
|
sessionStore.saveStickyPostId(platform.platformId, post2.id);
|
|
63804
63951
|
}
|
|
63805
|
-
|
|
63952
|
+
log13.info(`\uD83D\uDCCC Created sticky message for ${platform.platformId}: ${formatShortId(post2.id)}`);
|
|
63806
63953
|
const excludePostIds = new Set;
|
|
63807
63954
|
if (sessionStore) {
|
|
63808
63955
|
for (const session of sessionStore.load().values()) {
|
|
@@ -63818,10 +63965,10 @@ async function updateStickyMessageImpl(platform, sessions, config) {
|
|
|
63818
63965
|
}
|
|
63819
63966
|
const botUser = await platform.getBotUser();
|
|
63820
63967
|
cleanupOldStickyMessages(platform, botUser.id, false, excludePostIds).catch((err) => {
|
|
63821
|
-
|
|
63968
|
+
log13.debug(`Background cleanup failed: ${err}`);
|
|
63822
63969
|
});
|
|
63823
63970
|
} catch (err) {
|
|
63824
|
-
|
|
63971
|
+
log13.error(`Failed to update sticky message for ${platform.platformId}`, err instanceof Error ? err : undefined);
|
|
63825
63972
|
}
|
|
63826
63973
|
}
|
|
63827
63974
|
async function updateAllStickyMessages(platforms, sessions, config, overheadByPlatform) {
|
|
@@ -63849,7 +63996,7 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
|
|
|
63849
63996
|
if (!forceRun) {
|
|
63850
63997
|
const lastRun = lastCleanupTime.get(platformId) || 0;
|
|
63851
63998
|
if (now - lastRun < CLEANUP_THROTTLE_MS) {
|
|
63852
|
-
|
|
63999
|
+
log13.debug(`Cleanup throttled for ${platformId} (last run ${Math.round((now - lastRun) / 1000)}s ago)`);
|
|
63853
64000
|
return;
|
|
63854
64001
|
}
|
|
63855
64002
|
}
|
|
@@ -63859,31 +64006,31 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
|
|
|
63859
64006
|
const pinnedPostIds = await platform.getPinnedPosts();
|
|
63860
64007
|
const recentPinnedIds = pinnedPostIds.filter((id) => id !== currentStickyId && !excludePostIds?.has(id) && isRecentPost(id));
|
|
63861
64008
|
if (recentPinnedIds.length === 0) {
|
|
63862
|
-
|
|
64009
|
+
log13.debug(`No recent pinned posts to check (${pinnedPostIds.length} total, current: ${currentStickyId?.substring(0, 8) || "(none)"})`);
|
|
63863
64010
|
return;
|
|
63864
64011
|
}
|
|
63865
|
-
|
|
64012
|
+
log13.debug(`Checking ${recentPinnedIds.length} recent pinned posts (of ${pinnedPostIds.length} total)`);
|
|
63866
64013
|
for (const postId of recentPinnedIds) {
|
|
63867
64014
|
try {
|
|
63868
64015
|
const post2 = await platform.getPost(postId);
|
|
63869
64016
|
if (!post2)
|
|
63870
64017
|
continue;
|
|
63871
64018
|
if (post2.userId === botUserId) {
|
|
63872
|
-
|
|
64019
|
+
log13.debug(`Cleaning up old sticky: ${postId.substring(0, 8)}...`);
|
|
63873
64020
|
try {
|
|
63874
64021
|
await platform.unpinPost(postId);
|
|
63875
64022
|
await platform.deletePost(postId);
|
|
63876
|
-
|
|
64023
|
+
log13.info(`\uD83E\uDDF9 Cleaned up old sticky message: ${postId.substring(0, 8)}...`);
|
|
63877
64024
|
} catch (err) {
|
|
63878
|
-
|
|
64025
|
+
log13.debug(`Failed to cleanup ${postId}: ${err}`);
|
|
63879
64026
|
}
|
|
63880
64027
|
}
|
|
63881
64028
|
} catch (err) {
|
|
63882
|
-
|
|
64029
|
+
log13.debug(`Could not check post ${postId}: ${err}`);
|
|
63883
64030
|
}
|
|
63884
64031
|
}
|
|
63885
64032
|
} catch (err) {
|
|
63886
|
-
|
|
64033
|
+
log13.error(`Failed to cleanup old sticky messages`, err instanceof Error ? err : undefined);
|
|
63887
64034
|
}
|
|
63888
64035
|
}
|
|
63889
64036
|
// src/memory/store.ts
|
|
@@ -63891,18 +64038,18 @@ init_logger();
|
|
|
63891
64038
|
init_worktree();
|
|
63892
64039
|
import { createHash } from "crypto";
|
|
63893
64040
|
import {
|
|
63894
|
-
chmodSync as
|
|
64041
|
+
chmodSync as chmodSync4,
|
|
63895
64042
|
existsSync as existsSync7,
|
|
63896
|
-
mkdirSync as
|
|
64043
|
+
mkdirSync as mkdirSync4,
|
|
63897
64044
|
readFileSync as readFileSync6,
|
|
63898
64045
|
renameSync,
|
|
63899
64046
|
realpathSync,
|
|
63900
64047
|
writeFileSync as writeFileSync5
|
|
63901
64048
|
} from "fs";
|
|
63902
|
-
import { homedir as
|
|
63903
|
-
import { basename as basename3, dirname as dirname7, join as
|
|
63904
|
-
var
|
|
63905
|
-
var DEFAULT_ROOT =
|
|
64049
|
+
import { homedir as homedir5 } from "os";
|
|
64050
|
+
import { basename as basename3, dirname as dirname7, join as join9, sep as sep2 } from "path";
|
|
64051
|
+
var log14 = createLogger("memory");
|
|
64052
|
+
var DEFAULT_ROOT = join9(homedir5(), ".config", "claude-threads", "memory");
|
|
63906
64053
|
var CHANNEL_BLOCK_MAX_LINES = 200;
|
|
63907
64054
|
var CHANNEL_BLOCK_MAX_BYTES = 25 * 1024;
|
|
63908
64055
|
var CHANNEL_FILE_MAX_ENTRIES = 400;
|
|
@@ -63965,10 +64112,10 @@ class MemoryStore {
|
|
|
63965
64112
|
return this.root;
|
|
63966
64113
|
}
|
|
63967
64114
|
channelMemoryPath(platformId) {
|
|
63968
|
-
return
|
|
64115
|
+
return join9(this.root, platformSegment(platformId), "channel", "MEMORY.md");
|
|
63969
64116
|
}
|
|
63970
64117
|
repoMemoryDir(platformId, repoKey) {
|
|
63971
|
-
const dir =
|
|
64118
|
+
const dir = join9(this.root, platformSegment(platformId), "repos", repoKey);
|
|
63972
64119
|
this.ensureDir(dir);
|
|
63973
64120
|
return dir;
|
|
63974
64121
|
}
|
|
@@ -64015,7 +64162,7 @@ class MemoryStore {
|
|
|
64015
64162
|
if (result.added.length > 0) {
|
|
64016
64163
|
this.enforceFileCap(lines);
|
|
64017
64164
|
this.writeLines(platformId, lines);
|
|
64018
|
-
|
|
64165
|
+
log14.debug(`Channel memory for ${platformId}: +${result.added.length} entries` + (result.duplicates.length ? ` (${result.duplicates.length} duplicates skipped)` : ""));
|
|
64019
64166
|
}
|
|
64020
64167
|
return result;
|
|
64021
64168
|
});
|
|
@@ -64054,14 +64201,14 @@ class MemoryStore {
|
|
|
64054
64201
|
}
|
|
64055
64202
|
lines.splice(target.lineIndex, 1);
|
|
64056
64203
|
this.writeLines(platformId, lines);
|
|
64057
|
-
|
|
64204
|
+
log14.debug(`Channel memory for ${platformId}: removed one entry`);
|
|
64058
64205
|
return { ok: true, removed: target.entry };
|
|
64059
64206
|
});
|
|
64060
64207
|
}
|
|
64061
64208
|
clearChannel(platformId) {
|
|
64062
64209
|
return this.runExclusive(platformId, () => {
|
|
64063
64210
|
this.writeLines(platformId, []);
|
|
64064
|
-
|
|
64211
|
+
log14.debug(`Channel memory for ${platformId}: cleared`);
|
|
64065
64212
|
});
|
|
64066
64213
|
}
|
|
64067
64214
|
buildChannelMemoryBlock(platformId) {
|
|
@@ -64069,7 +64216,7 @@ class MemoryStore {
|
|
|
64069
64216
|
try {
|
|
64070
64217
|
lines = this.loadLines(platformId);
|
|
64071
64218
|
} catch (err) {
|
|
64072
|
-
|
|
64219
|
+
log14.warn(`Failed to read channel memory for ${platformId}: ${err.message}`);
|
|
64073
64220
|
return null;
|
|
64074
64221
|
}
|
|
64075
64222
|
if (lines.length === 0)
|
|
@@ -64144,11 +64291,11 @@ _(older entries omitted — \`!memory\` shows all)_` : rendered;
|
|
|
64144
64291
|
const tempFile = `${file}.tmp`;
|
|
64145
64292
|
writeFileSync5(tempFile, content, { encoding: "utf-8", mode: 384 });
|
|
64146
64293
|
renameSync(tempFile, file);
|
|
64147
|
-
|
|
64294
|
+
chmodSync4(file, 384);
|
|
64148
64295
|
}
|
|
64149
64296
|
ensureDir(dir) {
|
|
64150
64297
|
if (!existsSync7(dir)) {
|
|
64151
|
-
|
|
64298
|
+
mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
64152
64299
|
}
|
|
64153
64300
|
}
|
|
64154
64301
|
}
|
|
@@ -64159,7 +64306,7 @@ async function resolveSessionMemory(memoryStore, memoryConfig, platformId, worki
|
|
|
64159
64306
|
const repoKey = await resolveRepoKey(workingDir, worktreeRepoRoot);
|
|
64160
64307
|
return { autoMemoryDir: memoryStore.repoMemoryDir(platformId, repoKey) };
|
|
64161
64308
|
} catch (err) {
|
|
64162
|
-
|
|
64309
|
+
log14.warn(`Failed to resolve repo memory dir for ${platformId}: ${err.message}`);
|
|
64163
64310
|
return null;
|
|
64164
64311
|
}
|
|
64165
64312
|
}
|
|
@@ -64167,7 +64314,7 @@ async function resolveSessionMemory(memoryStore, memoryConfig, platformId, worki
|
|
|
64167
64314
|
// src/claude/quick-query.ts
|
|
64168
64315
|
init_spawn();
|
|
64169
64316
|
init_logger();
|
|
64170
|
-
var
|
|
64317
|
+
var log15 = createLogger("query");
|
|
64171
64318
|
async function quickQuery(options) {
|
|
64172
64319
|
const {
|
|
64173
64320
|
prompt,
|
|
@@ -64182,7 +64329,7 @@ async function quickQuery(options) {
|
|
|
64182
64329
|
if (systemPrompt) {
|
|
64183
64330
|
args.push("--system-prompt", systemPrompt);
|
|
64184
64331
|
}
|
|
64185
|
-
|
|
64332
|
+
log15.debug(`Quick query: model=${model}, timeout=${timeout2}ms, prompt="${prompt.substring(0, 50)}..."`);
|
|
64186
64333
|
return new Promise((resolve6) => {
|
|
64187
64334
|
let stdout = "";
|
|
64188
64335
|
let stderr = "";
|
|
@@ -64196,7 +64343,7 @@ async function quickQuery(options) {
|
|
|
64196
64343
|
if (!resolved) {
|
|
64197
64344
|
resolved = true;
|
|
64198
64345
|
proc.kill("SIGTERM");
|
|
64199
|
-
|
|
64346
|
+
log15.debug(`Quick query timed out after ${timeout2}ms`);
|
|
64200
64347
|
resolve6({
|
|
64201
64348
|
success: false,
|
|
64202
64349
|
error: "timeout",
|
|
@@ -64214,7 +64361,7 @@ async function quickQuery(options) {
|
|
|
64214
64361
|
if (!resolved) {
|
|
64215
64362
|
resolved = true;
|
|
64216
64363
|
clearTimeout(timeoutId);
|
|
64217
|
-
|
|
64364
|
+
log15.debug(`Quick query error: ${err.message}`);
|
|
64218
64365
|
resolve6({
|
|
64219
64366
|
success: false,
|
|
64220
64367
|
error: err.message,
|
|
@@ -64228,14 +64375,14 @@ async function quickQuery(options) {
|
|
|
64228
64375
|
clearTimeout(timeoutId);
|
|
64229
64376
|
const durationMs = Date.now() - startTime;
|
|
64230
64377
|
if (code === 0 && stdout.trim()) {
|
|
64231
|
-
|
|
64378
|
+
log15.debug(`Quick query success: ${durationMs}ms, ${stdout.length} chars`);
|
|
64232
64379
|
resolve6({
|
|
64233
64380
|
success: true,
|
|
64234
64381
|
response: stdout.trim(),
|
|
64235
64382
|
durationMs
|
|
64236
64383
|
});
|
|
64237
64384
|
} else {
|
|
64238
|
-
|
|
64385
|
+
log15.debug(`Quick query failed: code=${code}, stderr=${stderr.substring(0, 100)}`);
|
|
64239
64386
|
resolve6({
|
|
64240
64387
|
success: false,
|
|
64241
64388
|
error: stderr || `exit code ${code}`,
|
|
@@ -64254,7 +64401,7 @@ init_logger();
|
|
|
64254
64401
|
import { exec as exec3 } from "child_process";
|
|
64255
64402
|
import { promisify as promisify3 } from "util";
|
|
64256
64403
|
var execAsync2 = promisify3(exec3);
|
|
64257
|
-
var
|
|
64404
|
+
var log16 = createLogger("branch");
|
|
64258
64405
|
var SUGGESTION_TIMEOUT = 15000;
|
|
64259
64406
|
var MAX_SUGGESTIONS = 3;
|
|
64260
64407
|
async function getCurrentBranch3(workingDir) {
|
|
@@ -64303,7 +64450,7 @@ function parseBranchSuggestions(response) {
|
|
|
64303
64450
|
return lines.slice(0, MAX_SUGGESTIONS);
|
|
64304
64451
|
}
|
|
64305
64452
|
async function suggestBranchNames(workingDir, userMessage) {
|
|
64306
|
-
|
|
64453
|
+
log16.debug(`Suggesting branch names for: "${userMessage.substring(0, 50)}..."`);
|
|
64307
64454
|
try {
|
|
64308
64455
|
const [currentBranch, recentCommits] = await Promise.all([
|
|
64309
64456
|
getCurrentBranch3(workingDir),
|
|
@@ -64317,14 +64464,14 @@ async function suggestBranchNames(workingDir, userMessage) {
|
|
|
64317
64464
|
workingDir
|
|
64318
64465
|
});
|
|
64319
64466
|
if (!result.success || !result.response) {
|
|
64320
|
-
|
|
64467
|
+
log16.debug(`Branch suggestion failed: ${result.error || "no response"}`);
|
|
64321
64468
|
return [];
|
|
64322
64469
|
}
|
|
64323
64470
|
const suggestions = parseBranchSuggestions(result.response);
|
|
64324
|
-
|
|
64471
|
+
log16.debug(`Got ${suggestions.length} branch suggestions: ${suggestions.join(", ")}`);
|
|
64325
64472
|
return suggestions;
|
|
64326
64473
|
} catch (err) {
|
|
64327
|
-
|
|
64474
|
+
log16.debug(`Branch suggestion error: ${err}`);
|
|
64328
64475
|
return [];
|
|
64329
64476
|
}
|
|
64330
64477
|
}
|
|
@@ -64333,8 +64480,8 @@ async function suggestBranchNames(workingDir, userMessage) {
|
|
|
64333
64480
|
init_worktree();
|
|
64334
64481
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
64335
64482
|
init_logger();
|
|
64336
|
-
var
|
|
64337
|
-
var sessionLog2 = createSessionLog(
|
|
64483
|
+
var log17 = createLogger("worktree");
|
|
64484
|
+
var sessionLog2 = createSessionLog(log17);
|
|
64338
64485
|
function parseWorktreeError(error) {
|
|
64339
64486
|
const message = error instanceof Error ? error.message : String(error);
|
|
64340
64487
|
const lowerMessage = message.toLowerCase();
|
|
@@ -64860,6 +65007,14 @@ async function removeWorktreeCommand(session, branchOrPath, username) {
|
|
|
64860
65007
|
}
|
|
64861
65008
|
try {
|
|
64862
65009
|
await removeWorktree(repoRoot, target.path);
|
|
65010
|
+
auditLog(session.platformId, {
|
|
65011
|
+
threadId: session.threadId,
|
|
65012
|
+
sessionId: session.sessionId,
|
|
65013
|
+
actor: username,
|
|
65014
|
+
kind: "command",
|
|
65015
|
+
tool: "worktree remove",
|
|
65016
|
+
detail: target.path
|
|
65017
|
+
});
|
|
64863
65018
|
const shortPath = shortenPath(target.path, undefined, { path: target.path, branch: target.branch });
|
|
64864
65019
|
await post(session, "success", `Removed worktree \`${target.branch}\` at \`${shortPath}\``);
|
|
64865
65020
|
sessionLog2(session).info(`\uD83D\uDDD1️ Removed worktree ${target.branch} at ${shortPath}`);
|
|
@@ -64916,8 +65071,8 @@ async function cleanupWorktreeCommand(session, username, hasOtherSessionsUsingWo
|
|
|
64916
65071
|
}
|
|
64917
65072
|
// src/operations/events/handler.ts
|
|
64918
65073
|
init_logger();
|
|
64919
|
-
var
|
|
64920
|
-
var sessionLog3 = createSessionLog(
|
|
65074
|
+
var log18 = createLogger("events");
|
|
65075
|
+
var sessionLog3 = createSessionLog(log18);
|
|
64921
65076
|
function detectAndExecuteClaudeCommands(text, session, ctx) {
|
|
64922
65077
|
const parsed = parseClaudeCommand(text);
|
|
64923
65078
|
if (parsed && isClaudeAllowedCommand(parsed.command)) {
|
|
@@ -64980,6 +65135,34 @@ function isSidechainEvent(event) {
|
|
|
64980
65135
|
}
|
|
64981
65136
|
function handleEventPreProcessing(session, event, ctx) {
|
|
64982
65137
|
session.threadLogger?.logEvent(event);
|
|
65138
|
+
try {
|
|
65139
|
+
if (isAuditEnabled(session.platformId)) {
|
|
65140
|
+
const subagent = isSidechainEvent(event) || undefined;
|
|
65141
|
+
const record = (name, input) => auditLog(session.platformId, {
|
|
65142
|
+
threadId: session.threadId,
|
|
65143
|
+
sessionId: session.sessionId,
|
|
65144
|
+
actor: session.lastActorUsername ?? session.startedBy,
|
|
65145
|
+
kind: "tool_use",
|
|
65146
|
+
tool: name,
|
|
65147
|
+
detail: auditDetailForTool(name, input),
|
|
65148
|
+
subagent
|
|
65149
|
+
});
|
|
65150
|
+
if (event.type === "assistant") {
|
|
65151
|
+
const msg = event.message;
|
|
65152
|
+
if (Array.isArray(msg?.content)) {
|
|
65153
|
+
for (const block of msg.content) {
|
|
65154
|
+
if ((block.type === "tool_use" || block.type === "server_tool_use") && block.name) {
|
|
65155
|
+
record(block.name, block.input);
|
|
65156
|
+
}
|
|
65157
|
+
}
|
|
65158
|
+
}
|
|
65159
|
+
} else if (event.type === "tool_use") {
|
|
65160
|
+
const tool = event.tool_use;
|
|
65161
|
+
if (tool?.name)
|
|
65162
|
+
record(tool.name, tool.input);
|
|
65163
|
+
}
|
|
65164
|
+
}
|
|
65165
|
+
} catch {}
|
|
64983
65166
|
resetSessionActivity(session);
|
|
64984
65167
|
if (!session.lifecycle.hasClaudeResponded && (event.type === "assistant" || event.type === "tool_use")) {
|
|
64985
65168
|
markClaudeResponded(session);
|
|
@@ -65232,7 +65415,7 @@ function updateUsageFromStatusLine(session) {
|
|
|
65232
65415
|
}
|
|
65233
65416
|
// src/operations/monitor/handler.ts
|
|
65234
65417
|
init_logger();
|
|
65235
|
-
var
|
|
65418
|
+
var log19 = createLogger("monitor");
|
|
65236
65419
|
var DEFAULT_INTERVAL_MS = 60 * 1000;
|
|
65237
65420
|
|
|
65238
65421
|
class SessionMonitor {
|
|
@@ -65254,14 +65437,14 @@ class SessionMonitor {
|
|
|
65254
65437
|
}
|
|
65255
65438
|
start() {
|
|
65256
65439
|
if (this.isRunning) {
|
|
65257
|
-
|
|
65440
|
+
log19.debug("Session monitor already running");
|
|
65258
65441
|
return;
|
|
65259
65442
|
}
|
|
65260
65443
|
this.isRunning = true;
|
|
65261
|
-
|
|
65444
|
+
log19.debug(`Session monitor started (interval: ${this.intervalMs / 1000}s)`);
|
|
65262
65445
|
this.timer = setInterval(() => {
|
|
65263
65446
|
this.runCheck().catch((err) => {
|
|
65264
|
-
|
|
65447
|
+
log19.error(`Error during session monitoring: ${err}`);
|
|
65265
65448
|
});
|
|
65266
65449
|
}, this.intervalMs);
|
|
65267
65450
|
}
|
|
@@ -65271,7 +65454,7 @@ class SessionMonitor {
|
|
|
65271
65454
|
this.timer = null;
|
|
65272
65455
|
}
|
|
65273
65456
|
this.isRunning = false;
|
|
65274
|
-
|
|
65457
|
+
log19.debug("Session monitor stopped");
|
|
65275
65458
|
}
|
|
65276
65459
|
async runCheck() {
|
|
65277
65460
|
await cleanupIdleSessions(this.sessionTimeoutMs, this.sessionWarningMs, this.getContext());
|
|
@@ -65291,8 +65474,8 @@ function createSessionContext(config, state, ops) {
|
|
|
65291
65474
|
// src/operations/context-prompt/handler.ts
|
|
65292
65475
|
init_emoji();
|
|
65293
65476
|
init_logger();
|
|
65294
|
-
var
|
|
65295
|
-
var sessionLog4 = createSessionLog(
|
|
65477
|
+
var log20 = createLogger("context");
|
|
65478
|
+
var sessionLog4 = createSessionLog(log20);
|
|
65296
65479
|
var CONTEXT_PROMPT_TIMEOUT_MS = 30000;
|
|
65297
65480
|
var CONTEXT_OPTIONS = [3, 5, 10];
|
|
65298
65481
|
var contextPromptTimeouts = new Map;
|
|
@@ -65522,7 +65705,7 @@ async function offerContextPrompt(session, queuedPrompt, queuedFiles, ctx, exclu
|
|
|
65522
65705
|
}
|
|
65523
65706
|
// src/operations/suggestions/tag.ts
|
|
65524
65707
|
init_logger();
|
|
65525
|
-
var
|
|
65708
|
+
var log21 = createLogger("tags");
|
|
65526
65709
|
var SUGGESTION_TIMEOUT2 = 15000;
|
|
65527
65710
|
var MAX_TAGS = 3;
|
|
65528
65711
|
var VALID_TAGS = [
|
|
@@ -65554,7 +65737,7 @@ function parseTags(response) {
|
|
|
65554
65737
|
return [...new Set(tags)].slice(0, MAX_TAGS);
|
|
65555
65738
|
}
|
|
65556
65739
|
async function suggestSessionTags(userMessage) {
|
|
65557
|
-
|
|
65740
|
+
log21.debug(`Suggesting tags for: "${userMessage.substring(0, 50)}..."`);
|
|
65558
65741
|
try {
|
|
65559
65742
|
const result = await quickQuery({
|
|
65560
65743
|
prompt: buildTagPrompt(userMessage),
|
|
@@ -65562,20 +65745,20 @@ async function suggestSessionTags(userMessage) {
|
|
|
65562
65745
|
timeout: SUGGESTION_TIMEOUT2
|
|
65563
65746
|
});
|
|
65564
65747
|
if (!result.success || !result.response) {
|
|
65565
|
-
|
|
65748
|
+
log21.debug(`Tag suggestion failed: ${result.error || "no response"}`);
|
|
65566
65749
|
return [];
|
|
65567
65750
|
}
|
|
65568
65751
|
const tags = parseTags(result.response);
|
|
65569
|
-
|
|
65752
|
+
log21.debug(`Got tags: ${tags.join(", ")} (${result.durationMs}ms)`);
|
|
65570
65753
|
return tags;
|
|
65571
65754
|
} catch (err) {
|
|
65572
|
-
|
|
65755
|
+
log21.debug(`Tag suggestion error: ${err}`);
|
|
65573
65756
|
return [];
|
|
65574
65757
|
}
|
|
65575
65758
|
}
|
|
65576
65759
|
// src/operations/suggestions/title.ts
|
|
65577
65760
|
init_logger();
|
|
65578
|
-
var
|
|
65761
|
+
var log22 = createLogger("title");
|
|
65579
65762
|
var SUGGESTION_TIMEOUT3 = 15000;
|
|
65580
65763
|
var MIN_TITLE_LENGTH = 3;
|
|
65581
65764
|
var MAX_TITLE_LENGTH = 50;
|
|
@@ -65639,32 +65822,32 @@ function parseMetadata(response) {
|
|
|
65639
65822
|
const titleMatch = response.match(/TITLE:\s*(.+)/i);
|
|
65640
65823
|
const descMatch = response.match(/DESC:\s*(.+)/i);
|
|
65641
65824
|
if (!titleMatch || !descMatch) {
|
|
65642
|
-
|
|
65825
|
+
log22.debug("Failed to parse title/description from response");
|
|
65643
65826
|
return null;
|
|
65644
65827
|
}
|
|
65645
65828
|
let title = titleMatch[1].trim();
|
|
65646
65829
|
let description = descMatch[1].trim();
|
|
65647
65830
|
if (title.length < MIN_TITLE_LENGTH) {
|
|
65648
|
-
|
|
65831
|
+
log22.debug(`Title too short: ${title.length} chars`);
|
|
65649
65832
|
return null;
|
|
65650
65833
|
}
|
|
65651
65834
|
if (title.length > MAX_TITLE_LENGTH) {
|
|
65652
|
-
|
|
65835
|
+
log22.debug(`Title too long (${title.length} chars), truncating`);
|
|
65653
65836
|
title = truncateAtWord(title, MAX_TITLE_LENGTH);
|
|
65654
65837
|
}
|
|
65655
65838
|
if (description.length < MIN_DESC_LENGTH) {
|
|
65656
|
-
|
|
65839
|
+
log22.debug(`Description too short: ${description.length} chars`);
|
|
65657
65840
|
return null;
|
|
65658
65841
|
}
|
|
65659
65842
|
if (description.length > MAX_DESC_LENGTH) {
|
|
65660
|
-
|
|
65843
|
+
log22.debug(`Description too long (${description.length} chars), truncating`);
|
|
65661
65844
|
description = truncateAtWord(description, MAX_DESC_LENGTH);
|
|
65662
65845
|
}
|
|
65663
65846
|
return { title, description };
|
|
65664
65847
|
}
|
|
65665
65848
|
async function suggestSessionMetadata(context) {
|
|
65666
65849
|
const logContext = typeof context === "string" ? context.substring(0, 50) : context.originalTask.substring(0, 50);
|
|
65667
|
-
|
|
65850
|
+
log22.debug(`Suggesting title for: "${logContext}..."`);
|
|
65668
65851
|
try {
|
|
65669
65852
|
const result = await quickQuery({
|
|
65670
65853
|
prompt: buildTitlePrompt(context),
|
|
@@ -65672,27 +65855,27 @@ async function suggestSessionMetadata(context) {
|
|
|
65672
65855
|
timeout: SUGGESTION_TIMEOUT3
|
|
65673
65856
|
});
|
|
65674
65857
|
if (!result.success || !result.response) {
|
|
65675
|
-
|
|
65858
|
+
log22.debug(`Title suggestion failed: ${result.error || "no response"}`);
|
|
65676
65859
|
return null;
|
|
65677
65860
|
}
|
|
65678
65861
|
const metadata = parseMetadata(result.response);
|
|
65679
65862
|
if (metadata) {
|
|
65680
|
-
|
|
65863
|
+
log22.debug(`Got title: "${metadata.title}" (${result.durationMs}ms)`);
|
|
65681
65864
|
}
|
|
65682
65865
|
return metadata;
|
|
65683
65866
|
} catch (err) {
|
|
65684
|
-
|
|
65867
|
+
log22.debug(`Title suggestion error: ${err}`);
|
|
65685
65868
|
return null;
|
|
65686
65869
|
}
|
|
65687
65870
|
}
|
|
65688
65871
|
// src/persistence/github-emails-store.ts
|
|
65689
|
-
import { existsSync as existsSync8, mkdirSync as
|
|
65690
|
-
import { homedir as
|
|
65691
|
-
import { join as
|
|
65872
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync6, renameSync as renameSync2, chmodSync as chmodSync5 } from "fs";
|
|
65873
|
+
import { homedir as homedir6 } from "os";
|
|
65874
|
+
import { join as join10 } from "path";
|
|
65692
65875
|
init_logger();
|
|
65693
|
-
var
|
|
65694
|
-
var DEFAULT_CONFIG_DIR =
|
|
65695
|
-
var DEFAULT_FILE =
|
|
65876
|
+
var log23 = createLogger("gh-emails");
|
|
65877
|
+
var DEFAULT_CONFIG_DIR = join10(homedir6(), ".config", "claude-threads");
|
|
65878
|
+
var DEFAULT_FILE = join10(DEFAULT_CONFIG_DIR, "github-emails.yaml");
|
|
65696
65879
|
var NOREPLY_REGEX = /^\d+\+[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})@users\.noreply\.github\.com$/;
|
|
65697
65880
|
var STORE_VERSION = 1;
|
|
65698
65881
|
function isValidGitHubNoreplyEmail(s) {
|
|
@@ -65707,13 +65890,13 @@ class GitHubEmailsStore {
|
|
|
65707
65890
|
const effective = filePath ?? envPath;
|
|
65708
65891
|
if (effective) {
|
|
65709
65892
|
this.file = effective;
|
|
65710
|
-
this.configDir =
|
|
65893
|
+
this.configDir = join10(effective, "..");
|
|
65711
65894
|
} else {
|
|
65712
65895
|
this.file = DEFAULT_FILE;
|
|
65713
65896
|
this.configDir = DEFAULT_CONFIG_DIR;
|
|
65714
65897
|
}
|
|
65715
65898
|
if (!existsSync8(this.configDir)) {
|
|
65716
|
-
|
|
65899
|
+
mkdirSync5(this.configDir, { recursive: true });
|
|
65717
65900
|
}
|
|
65718
65901
|
}
|
|
65719
65902
|
get(platformId, username) {
|
|
@@ -65730,7 +65913,7 @@ class GitHubEmailsStore {
|
|
|
65730
65913
|
}
|
|
65731
65914
|
data.emails[platformId][username] = email;
|
|
65732
65915
|
this.writeAtomic(data);
|
|
65733
|
-
|
|
65916
|
+
log23.debug(`Stored GitHub email for ${platformId}/${username}`);
|
|
65734
65917
|
}
|
|
65735
65918
|
delete(platformId, username) {
|
|
65736
65919
|
const data = this.loadRaw();
|
|
@@ -65742,7 +65925,7 @@ class GitHubEmailsStore {
|
|
|
65742
65925
|
delete data.emails[platformId];
|
|
65743
65926
|
}
|
|
65744
65927
|
this.writeAtomic(data);
|
|
65745
|
-
|
|
65928
|
+
log23.debug(`Removed GitHub email for ${platformId}/${username}`);
|
|
65746
65929
|
return true;
|
|
65747
65930
|
}
|
|
65748
65931
|
loadRaw() {
|
|
@@ -65758,7 +65941,7 @@ class GitHubEmailsStore {
|
|
|
65758
65941
|
const emails = parsed.emails && typeof parsed.emails === "object" ? parsed.emails : {};
|
|
65759
65942
|
return { version: parsed.version ?? STORE_VERSION, emails };
|
|
65760
65943
|
} catch (err) {
|
|
65761
|
-
|
|
65944
|
+
log23.warn(`Failed to read ${this.file}: ${err.message} — starting empty`);
|
|
65762
65945
|
return { version: STORE_VERSION, emails: {} };
|
|
65763
65946
|
}
|
|
65764
65947
|
}
|
|
@@ -65767,19 +65950,19 @@ class GitHubEmailsStore {
|
|
|
65767
65950
|
const yamlText = yaml.dump(data, { sortKeys: true, lineWidth: -1 });
|
|
65768
65951
|
writeFileSync6(tempFile, yamlText, { encoding: "utf-8", mode: 384 });
|
|
65769
65952
|
renameSync2(tempFile, this.file);
|
|
65770
|
-
|
|
65953
|
+
chmodSync5(this.file, 384);
|
|
65771
65954
|
}
|
|
65772
65955
|
}
|
|
65773
65956
|
|
|
65774
65957
|
// src/persistence/routines-store.ts
|
|
65775
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
65776
|
-
import { homedir as
|
|
65777
|
-
import { join as
|
|
65958
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync8 } from "fs";
|
|
65959
|
+
import { homedir as homedir7 } from "os";
|
|
65960
|
+
import { join as join11 } from "path";
|
|
65778
65961
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
65779
65962
|
init_logger();
|
|
65780
65963
|
|
|
65781
65964
|
// src/persistence/atomic-file.ts
|
|
65782
|
-
import { chmodSync as
|
|
65965
|
+
import { chmodSync as chmodSync6, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
65783
65966
|
|
|
65784
65967
|
class SerialQueue {
|
|
65785
65968
|
tail = Promise.resolve();
|
|
@@ -65795,13 +65978,13 @@ function writeFileAtomic(file, content) {
|
|
|
65795
65978
|
const tempFile = `${file}.tmp`;
|
|
65796
65979
|
writeFileSync7(tempFile, content, { encoding: "utf-8", mode: 384 });
|
|
65797
65980
|
renameSync3(tempFile, file);
|
|
65798
|
-
|
|
65981
|
+
chmodSync6(file, 384);
|
|
65799
65982
|
}
|
|
65800
65983
|
|
|
65801
65984
|
// src/persistence/routines-store.ts
|
|
65802
|
-
var
|
|
65803
|
-
var DEFAULT_CONFIG_DIR2 =
|
|
65804
|
-
var DEFAULT_FILE2 =
|
|
65985
|
+
var log24 = createLogger("routines");
|
|
65986
|
+
var DEFAULT_CONFIG_DIR2 = join11(homedir7(), ".config", "claude-threads");
|
|
65987
|
+
var DEFAULT_FILE2 = join11(DEFAULT_CONFIG_DIR2, "routines.yaml");
|
|
65805
65988
|
var STORE_VERSION2 = 1;
|
|
65806
65989
|
var MAX_CONSECUTIVE_FAILURES = 3;
|
|
65807
65990
|
var DEFAULT_MAX_ROUTINES = 10;
|
|
@@ -65860,13 +66043,13 @@ class RoutinesStore {
|
|
|
65860
66043
|
const effective = filePath ?? process.env.CLAUDE_THREADS_ROUTINES_PATH;
|
|
65861
66044
|
if (effective) {
|
|
65862
66045
|
this.file = effective;
|
|
65863
|
-
this.configDir =
|
|
66046
|
+
this.configDir = join11(effective, "..");
|
|
65864
66047
|
} else {
|
|
65865
66048
|
this.file = DEFAULT_FILE2;
|
|
65866
66049
|
this.configDir = DEFAULT_CONFIG_DIR2;
|
|
65867
66050
|
}
|
|
65868
66051
|
if (!existsSync9(this.configDir)) {
|
|
65869
|
-
|
|
66052
|
+
mkdirSync6(this.configDir, { recursive: true, mode: 448 });
|
|
65870
66053
|
}
|
|
65871
66054
|
}
|
|
65872
66055
|
list(platformId) {
|
|
@@ -65900,7 +66083,7 @@ class RoutinesStore {
|
|
|
65900
66083
|
};
|
|
65901
66084
|
data.routines[platformId] = [...existing, full];
|
|
65902
66085
|
this.writeAtomic(data);
|
|
65903
|
-
|
|
66086
|
+
log24.info(`Routine "${full.name}" created on ${platformId} by @${full.createdBy}`);
|
|
65904
66087
|
return { ok: true, routine: full };
|
|
65905
66088
|
});
|
|
65906
66089
|
}
|
|
@@ -65927,7 +66110,7 @@ class RoutinesStore {
|
|
|
65927
66110
|
if (routines.length === 0)
|
|
65928
66111
|
delete data.routines[platformId];
|
|
65929
66112
|
this.writeAtomic(data);
|
|
65930
|
-
|
|
66113
|
+
log24.info(`Routine "${removed.name}" removed from ${platformId}`);
|
|
65931
66114
|
return removed;
|
|
65932
66115
|
});
|
|
65933
66116
|
}
|
|
@@ -65952,7 +66135,7 @@ class RoutinesStore {
|
|
|
65952
66135
|
}
|
|
65953
66136
|
return { version: parsed.version ?? STORE_VERSION2, routines };
|
|
65954
66137
|
} catch (err) {
|
|
65955
|
-
|
|
66138
|
+
log24.warn(`Failed to read ${this.file}: ${err.message} — starting empty`);
|
|
65956
66139
|
return { version: STORE_VERSION2, routines: {} };
|
|
65957
66140
|
}
|
|
65958
66141
|
}
|
|
@@ -65963,7 +66146,7 @@ class RoutinesStore {
|
|
|
65963
66146
|
|
|
65964
66147
|
// src/routines/parser.ts
|
|
65965
66148
|
init_logger();
|
|
65966
|
-
var
|
|
66149
|
+
var log25 = createLogger("routines");
|
|
65967
66150
|
var PARSE_TIMEOUT_MS = 15000;
|
|
65968
66151
|
function hostTimezone() {
|
|
65969
66152
|
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
@@ -66032,7 +66215,7 @@ async function parseRoutineRequest(request, defaultTimezone = hostTimezone()) {
|
|
|
66032
66215
|
timeout: PARSE_TIMEOUT_MS
|
|
66033
66216
|
});
|
|
66034
66217
|
if (!result.success || !result.response) {
|
|
66035
|
-
|
|
66218
|
+
log25.debug(`Routine parse quickQuery failed: ${result.error ?? "no response"}`);
|
|
66036
66219
|
return { ok: false, error: "could not reach the parsing model — try again in a moment" };
|
|
66037
66220
|
}
|
|
66038
66221
|
const raw = extractJsonObject(result.response);
|
|
@@ -66043,8 +66226,8 @@ async function parseRoutineRequest(request, defaultTimezone = hostTimezone()) {
|
|
|
66043
66226
|
}
|
|
66044
66227
|
|
|
66045
66228
|
// src/operations/commands/handler.ts
|
|
66046
|
-
var
|
|
66047
|
-
var sessionLog5 = createSessionLog(
|
|
66229
|
+
var log26 = createLogger("commands");
|
|
66230
|
+
var sessionLog5 = createSessionLog(log26);
|
|
66048
66231
|
function sessionAccountOption(session, ctx) {
|
|
66049
66232
|
if (!session.claudeAccountId)
|
|
66050
66233
|
return;
|
|
@@ -66088,6 +66271,16 @@ async function restartClaudeSession(session, cliOptions, ctx, actionName) {
|
|
|
66088
66271
|
return false;
|
|
66089
66272
|
}
|
|
66090
66273
|
}
|
|
66274
|
+
function auditCommand(session, command, detail, username) {
|
|
66275
|
+
auditLog(session.platformId, {
|
|
66276
|
+
threadId: session.threadId,
|
|
66277
|
+
sessionId: session.sessionId,
|
|
66278
|
+
actor: username,
|
|
66279
|
+
kind: "command",
|
|
66280
|
+
tool: command,
|
|
66281
|
+
detail
|
|
66282
|
+
});
|
|
66283
|
+
}
|
|
66091
66284
|
async function requireSessionOwner(session, username, action) {
|
|
66092
66285
|
const formatter = session.platform.getFormatter();
|
|
66093
66286
|
if (session.startedBy !== username && !session.platform.isUserAllowed(username)) {
|
|
@@ -66123,6 +66316,7 @@ function formatContextBar(percent) {
|
|
|
66123
66316
|
}
|
|
66124
66317
|
async function cancelSession(session, username, ctx) {
|
|
66125
66318
|
sessionLog5(session).info(`\uD83D\uDED1 Cancelled by @${username}`);
|
|
66319
|
+
auditCommand(session, "stop", undefined, username);
|
|
66126
66320
|
session.threadLogger?.logCommand("stop", undefined, username);
|
|
66127
66321
|
transitionTo(session, "cancelling");
|
|
66128
66322
|
const formatter = session.platform.getFormatter();
|
|
@@ -66161,6 +66355,14 @@ async function approvePendingPlan(session, username, ctx) {
|
|
|
66161
66355
|
return;
|
|
66162
66356
|
}
|
|
66163
66357
|
const { postId } = pendingApproval;
|
|
66358
|
+
auditLog(session.platformId, {
|
|
66359
|
+
threadId: session.threadId,
|
|
66360
|
+
sessionId: session.sessionId,
|
|
66361
|
+
actor: username,
|
|
66362
|
+
kind: "plan_approval",
|
|
66363
|
+
approved: true,
|
|
66364
|
+
detail: "via !approve"
|
|
66365
|
+
});
|
|
66164
66366
|
sessionLog5(session).info(`✅ Plan approved by @${username} via command`);
|
|
66165
66367
|
const viaBridge = session.messageManager?.resolveBridgePlan(true) ?? false;
|
|
66166
66368
|
const formatter = session.platform.getFormatter();
|
|
@@ -66231,6 +66433,7 @@ async function changeDirectory(session, newDir, username, ctx) {
|
|
|
66231
66433
|
const worktreeContext = session.worktreeInfo ? { path: session.worktreeInfo.worktreePath, branch: session.worktreeInfo.branch } : undefined;
|
|
66232
66434
|
const shortDir = shortenPath(absoluteDir, undefined, worktreeContext);
|
|
66233
66435
|
sessionLog5(session).info(`\uD83D\uDCC2 Changing directory to ${shortDir}`);
|
|
66436
|
+
auditCommand(session, "cd", absoluteDir, username);
|
|
66234
66437
|
session.threadLogger?.logCommand("cd", absoluteDir, username);
|
|
66235
66438
|
const previousDir = session.workingDir;
|
|
66236
66439
|
const workSummary = await generateWorkSummary(session);
|
|
@@ -66313,6 +66516,7 @@ async function inviteUser(session, invitedUser, invitedBy, ctx) {
|
|
|
66313
66516
|
session.sessionAllowedUsers.add(invitedUser);
|
|
66314
66517
|
await post(session, "success", `${formatter.formatUserMention(invitedUser)} can now participate in this session (invited by ${formatter.formatUserMention(invitedBy)})`);
|
|
66315
66518
|
sessionLog5(session).info(`\uD83D\uDC4B @${invitedUser} invited by @${invitedBy}`);
|
|
66519
|
+
auditCommand(session, "invite", invitedUser, invitedBy);
|
|
66316
66520
|
session.threadLogger?.logCommand("invite", invitedUser, invitedBy);
|
|
66317
66521
|
await updateSessionHeader(session, ctx);
|
|
66318
66522
|
ctx.ops.persistSession(session);
|
|
@@ -66343,6 +66547,7 @@ async function kickUser(session, kickedUser, kickedBy, ctx) {
|
|
|
66343
66547
|
if (session.sessionAllowedUsers.delete(kickedUser)) {
|
|
66344
66548
|
await post(session, "user", `${formatter.formatUserMention(kickedUser)} removed from this session by ${formatter.formatUserMention(kickedBy)}`);
|
|
66345
66549
|
sessionLog5(session).info(`\uD83D\uDEAB @${kickedUser} kicked by @${kickedBy}`);
|
|
66550
|
+
auditCommand(session, "kick", kickedUser, kickedBy);
|
|
66346
66551
|
session.threadLogger?.logCommand("kick", kickedUser, kickedBy);
|
|
66347
66552
|
await updateSessionHeader(session, ctx);
|
|
66348
66553
|
ctx.ops.persistSession(session);
|
|
@@ -66499,6 +66704,7 @@ async function forgetMemory(session, selector, username, ctx) {
|
|
|
66499
66704
|
await ctx.state.memoryStore.clearChannel(session.platformId);
|
|
66500
66705
|
await post(session, "success", `\uD83E\uDDE0 Channel memory cleared (${count} ${count === 1 ? "entry" : "entries"} removed). Running sessions keep their copy until their next restart.`);
|
|
66501
66706
|
sessionLog5(session).info(`\uD83E\uDDE0 @${username} cleared channel memory (${count} entries)`);
|
|
66707
|
+
auditCommand(session, "memory", "forget all", username);
|
|
66502
66708
|
session.threadLogger?.logCommand("memory", "forget all", username);
|
|
66503
66709
|
return;
|
|
66504
66710
|
}
|
|
@@ -66507,6 +66713,7 @@ async function forgetMemory(session, selector, username, ctx) {
|
|
|
66507
66713
|
if (result.ok) {
|
|
66508
66714
|
await post(session, "success", `\uD83E\uDDE0 Forgot: ${formatter.formatItalic(result.removed.text)}`);
|
|
66509
66715
|
sessionLog5(session).info(`\uD83E\uDDE0 @${username} removed a channel memory entry`);
|
|
66716
|
+
auditCommand(session, "memory", "forget", username);
|
|
66510
66717
|
session.threadLogger?.logCommand("memory", "forget", username);
|
|
66511
66718
|
return;
|
|
66512
66719
|
}
|
|
@@ -66646,6 +66853,7 @@ async function manageRoutines(session, args, username, ctx) {
|
|
|
66646
66853
|
}
|
|
66647
66854
|
}
|
|
66648
66855
|
sessionLog5(session).info(`\uD83D\uDD58 @${username}: !routines ${lowered} ${indexArg} ("${routine.name}")`);
|
|
66856
|
+
auditCommand(session, "routines", `${lowered} ${indexArg}`, username);
|
|
66649
66857
|
session.threadLogger?.logCommand("routines", `${lowered} ${indexArg}`, username);
|
|
66650
66858
|
}
|
|
66651
66859
|
async function setSessionPermissionMode(session, username, mode, ctx) {
|
|
@@ -66655,6 +66863,7 @@ async function setSessionPermissionMode(session, username, mode, ctx) {
|
|
|
66655
66863
|
session.permissionModeOverride = mode;
|
|
66656
66864
|
session.forceInteractivePermissions = mode === "default";
|
|
66657
66865
|
sessionLog5(session).info(`\uD83D\uDD10 Setting permission mode to "${mode}"`);
|
|
66866
|
+
auditCommand(session, "permissions", mode, username);
|
|
66658
66867
|
session.threadLogger?.logCommand("permissions", mode, username);
|
|
66659
66868
|
const canResume = session.lifecycle.hasClaudeResponded;
|
|
66660
66869
|
const memoryConfig = ctx.ops.getPlatformMemoryConfig(session.platformId);
|
|
@@ -66948,7 +67157,7 @@ init_worktree();
|
|
|
66948
67157
|
|
|
66949
67158
|
// src/memory/distiller.ts
|
|
66950
67159
|
init_logger();
|
|
66951
|
-
var
|
|
67160
|
+
var log27 = createLogger("memory");
|
|
66952
67161
|
var MIN_THREAD_MESSAGES = 4;
|
|
66953
67162
|
var DISTILL_MESSAGE_LIMIT = 30;
|
|
66954
67163
|
var MESSAGE_CHAR_CAP = 500;
|
|
@@ -66992,17 +67201,17 @@ function scheduleDistillation(session, ctx, reason) {
|
|
|
66992
67201
|
return;
|
|
66993
67202
|
}
|
|
66994
67203
|
if (isDcmThreadId(session.threadId)) {
|
|
66995
|
-
|
|
67204
|
+
log27.debug(`Skipping distillation for DCM session ${session.platformId}:${session.threadId}`);
|
|
66996
67205
|
return;
|
|
66997
67206
|
}
|
|
66998
67207
|
const { platformId, threadId, platform } = session;
|
|
66999
67208
|
const store = ctx.state.memoryStore;
|
|
67000
67209
|
distillThread(store, platformId, threadId, platform).then((added) => {
|
|
67001
67210
|
if (added > 0) {
|
|
67002
|
-
|
|
67211
|
+
log27.debug(`Distilled ${added} memory entries from ${platformId}:${threadId} (${reason})`);
|
|
67003
67212
|
}
|
|
67004
67213
|
}).catch((err) => {
|
|
67005
|
-
|
|
67214
|
+
log27.debug(`Distillation failed for ${platformId}:${threadId}: ${err.message}`);
|
|
67006
67215
|
});
|
|
67007
67216
|
}
|
|
67008
67217
|
async function distillThread(store, platformId, threadId, platform) {
|
|
@@ -67029,8 +67238,8 @@ async function distillThread(store, platformId, threadId, platform) {
|
|
|
67029
67238
|
}
|
|
67030
67239
|
|
|
67031
67240
|
// src/session/lifecycle.ts
|
|
67032
|
-
var
|
|
67033
|
-
var sessionLog6 = createSessionLog(
|
|
67241
|
+
var log28 = createLogger("lifecycle");
|
|
67242
|
+
var sessionLog6 = createSessionLog(log28);
|
|
67034
67243
|
function mutableSessions(ctx) {
|
|
67035
67244
|
return ctx.state.sessions;
|
|
67036
67245
|
}
|
|
@@ -67046,7 +67255,22 @@ function mutablePostIndex(ctx) {
|
|
|
67046
67255
|
function cleanupSessionTimers(session) {
|
|
67047
67256
|
clearAllTimers(session.timers);
|
|
67048
67257
|
}
|
|
67049
|
-
|
|
67258
|
+
function auditSessionEnd(session, reason) {
|
|
67259
|
+
if (session.auditEndRecorded)
|
|
67260
|
+
return;
|
|
67261
|
+
session.auditEndRecorded = true;
|
|
67262
|
+
auditLog(session.platformId, {
|
|
67263
|
+
threadId: session.threadId,
|
|
67264
|
+
sessionId: session.sessionId,
|
|
67265
|
+
actor: session.lastActorUsername ?? session.startedBy,
|
|
67266
|
+
kind: "session_end",
|
|
67267
|
+
detail: reason
|
|
67268
|
+
});
|
|
67269
|
+
}
|
|
67270
|
+
async function closeThreadLogger(session, action, details, auditReason) {
|
|
67271
|
+
if (auditReason ?? action) {
|
|
67272
|
+
auditSessionEnd(session, auditReason ?? action);
|
|
67273
|
+
}
|
|
67050
67274
|
if (session.threadLogger) {
|
|
67051
67275
|
if (action) {
|
|
67052
67276
|
session.threadLogger.logLifecycle(action, details);
|
|
@@ -67071,12 +67295,15 @@ async function cleanupSession(session, ctx, options = {}) {
|
|
|
67071
67295
|
action,
|
|
67072
67296
|
details,
|
|
67073
67297
|
closeLogger: doCloseLogger = true,
|
|
67074
|
-
cleanupPostIndex: doCleanupPostIndex = true
|
|
67298
|
+
cleanupPostIndex: doCleanupPostIndex = true,
|
|
67299
|
+
auditReason
|
|
67075
67300
|
} = options;
|
|
67076
67301
|
ctx.ops.stopTyping(session);
|
|
67077
67302
|
cleanupSessionTimers(session);
|
|
67078
67303
|
if (doCloseLogger) {
|
|
67079
|
-
await closeThreadLogger(session, action, details);
|
|
67304
|
+
await closeThreadLogger(session, action, details, auditReason);
|
|
67305
|
+
} else if (auditReason ?? action) {
|
|
67306
|
+
auditSessionEnd(session, auditReason ?? action);
|
|
67080
67307
|
}
|
|
67081
67308
|
session.messageManager?.dispose();
|
|
67082
67309
|
session.decisionBridge?.close();
|
|
@@ -67096,7 +67323,9 @@ function releaseAccountIfHeld(session, ctx) {
|
|
|
67096
67323
|
session.claudeAccountId = undefined;
|
|
67097
67324
|
}
|
|
67098
67325
|
}
|
|
67099
|
-
function removeFromRegistry(session, ctx) {
|
|
67326
|
+
function removeFromRegistry(session, ctx, auditReason) {
|
|
67327
|
+
if (auditReason)
|
|
67328
|
+
auditSessionEnd(session, auditReason);
|
|
67100
67329
|
session.messageManager?.dispose();
|
|
67101
67330
|
session.decisionBridge?.close();
|
|
67102
67331
|
session.decisionBridge = undefined;
|
|
@@ -67135,7 +67364,7 @@ async function createSessionDecisionBridge(ref) {
|
|
|
67135
67364
|
return messageManager.handleBridgeRequest(request, signal);
|
|
67136
67365
|
});
|
|
67137
67366
|
} catch (err) {
|
|
67138
|
-
|
|
67367
|
+
log28.warn(`Decision bridge unavailable — falling back to legacy MCP prompts: ${err}`);
|
|
67139
67368
|
return null;
|
|
67140
67369
|
}
|
|
67141
67370
|
}
|
|
@@ -67204,6 +67433,14 @@ function createMessageManager(session, ctx) {
|
|
|
67204
67433
|
}
|
|
67205
67434
|
});
|
|
67206
67435
|
messageManager.events.on("routine-prompt:complete", async ({ approved, parsed, requestedBy, postId }) => {
|
|
67436
|
+
auditLog(session.platformId, {
|
|
67437
|
+
threadId: session.threadId,
|
|
67438
|
+
sessionId: session.sessionId,
|
|
67439
|
+
actor: requestedBy,
|
|
67440
|
+
kind: "command",
|
|
67441
|
+
tool: "routine",
|
|
67442
|
+
detail: `${approved ? "created" : "discarded"}: ${parsed.name}`
|
|
67443
|
+
});
|
|
67207
67444
|
session.threadLogger?.logCommand("routine", approved ? "created" : "discarded", requestedBy);
|
|
67208
67445
|
if (!approved) {
|
|
67209
67446
|
sessionLog6(session).info(`\uD83D\uDD58 Routine "${parsed.name}" discarded before saving`);
|
|
@@ -67422,7 +67659,7 @@ function resumeSessionHeaderMode(persisted, platformConfigured) {
|
|
|
67422
67659
|
function resolveSessionHeaderMode(configured, replyToPostId, platformId) {
|
|
67423
67660
|
const mode = configured ?? DEFAULT_OVERHEAD_VISIBILITY;
|
|
67424
67661
|
if (mode === "hidden" && !replyToPostId) {
|
|
67425
|
-
|
|
67662
|
+
log28.error(`sessionHeader: hidden requires a replyToPostId for ${platformId}; ` + `downgrading this session to 'minimal' so the header post is still short.`);
|
|
67426
67663
|
return "minimal";
|
|
67427
67664
|
}
|
|
67428
67665
|
return mode;
|
|
@@ -67462,7 +67699,7 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
|
|
|
67462
67699
|
throw new Error(`Platform '${platformId}' not found. Call addPlatform() first.`);
|
|
67463
67700
|
}
|
|
67464
67701
|
if (!isAuthorizedForSession({ username, platform, sessionAllowedUsers: undefined })) {
|
|
67465
|
-
|
|
67702
|
+
log28.warn(`auth.denied.startSession: @${username || "unknown"} not authorized to start session in ${threadId.substring(0, 8)}...`);
|
|
67466
67703
|
return;
|
|
67467
67704
|
}
|
|
67468
67705
|
const activeOrPending = ctx.state.sessions.size + pendingStartsCount;
|
|
@@ -67523,17 +67760,17 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
|
|
|
67523
67760
|
return;
|
|
67524
67761
|
}
|
|
67525
67762
|
workingDir = resolvedDir;
|
|
67526
|
-
|
|
67763
|
+
log28.info(`Starting session in directory: ${workingDir} (from !cd command)`);
|
|
67527
67764
|
}
|
|
67528
67765
|
if (initialOptions?.permissionMode) {
|
|
67529
67766
|
permissionMode = initialOptions.permissionMode;
|
|
67530
67767
|
forceInteractivePermissions = permissionMode === "default";
|
|
67531
67768
|
sessionPermissionModeOverride = permissionMode;
|
|
67532
|
-
|
|
67769
|
+
log28.info(`Starting session with permission mode "${permissionMode}" (from !permissions command)`);
|
|
67533
67770
|
} else if (initialOptions?.forceInteractivePermissions) {
|
|
67534
67771
|
forceInteractivePermissions = true;
|
|
67535
67772
|
permissionMode = "default";
|
|
67536
|
-
|
|
67773
|
+
log28.info(`Starting session with interactive permissions (from !permissions command)`);
|
|
67537
67774
|
}
|
|
67538
67775
|
const userAttribution = ctx.config.userAttribution ?? true;
|
|
67539
67776
|
const memoryConfig = ctx.ops.getPlatformMemoryConfig(platformId);
|
|
@@ -67547,7 +67784,7 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
|
|
|
67547
67784
|
balanceByUsage: true
|
|
67548
67785
|
});
|
|
67549
67786
|
if (claudeAccount) {
|
|
67550
|
-
|
|
67787
|
+
log28.info(`Session ${sessionId.substring(0, 20)} reserved Claude account "${claudeAccount.id}"`);
|
|
67551
67788
|
}
|
|
67552
67789
|
const bridgeSessionRef = {};
|
|
67553
67790
|
const decisionBridge = await createSessionDecisionBridge(bridgeSessionRef);
|
|
@@ -67612,6 +67849,12 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
|
|
|
67612
67849
|
session.decisionBridge = decisionBridge ?? undefined;
|
|
67613
67850
|
session.messageManager = createMessageManager(session, ctx);
|
|
67614
67851
|
bridgeSessionRef.current = session;
|
|
67852
|
+
auditLog(session.platformId, {
|
|
67853
|
+
threadId: session.threadId,
|
|
67854
|
+
sessionId: session.sessionId,
|
|
67855
|
+
actor: session.startedBy,
|
|
67856
|
+
kind: "session_start"
|
|
67857
|
+
});
|
|
67615
67858
|
session.threadLogger?.logLifecycle("start", {
|
|
67616
67859
|
username,
|
|
67617
67860
|
workingDir: ctx.config.workingDir
|
|
@@ -67636,6 +67879,7 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
|
|
|
67636
67879
|
claude.start();
|
|
67637
67880
|
} catch (err) {
|
|
67638
67881
|
await logAndNotify(err, { action: "Start Claude", session });
|
|
67882
|
+
auditSessionEnd(session, "start-failed");
|
|
67639
67883
|
ctx.ops.stopTyping(session);
|
|
67640
67884
|
session.messageManager?.dispose();
|
|
67641
67885
|
session.decisionBridge?.close();
|
|
@@ -67670,12 +67914,12 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
|
|
|
67670
67914
|
claude.sendMessage(formatUserTurn(content, username, shouldAttribute(session.userAttribution, session.sessionAllowedUsers.size)));
|
|
67671
67915
|
await postSkippedFilesFeedback(session.platform, actualThreadId, skipped);
|
|
67672
67916
|
}
|
|
67673
|
-
async function resumeSession(state, ctx) {
|
|
67917
|
+
async function resumeSession(state, ctx, resumedBy) {
|
|
67674
67918
|
if (state.threadId && state.platformId) {
|
|
67675
67919
|
const sessionKey = `${state.platformId}:${state.threadId}`;
|
|
67676
67920
|
const sessions = ctx.state?.sessions;
|
|
67677
67921
|
if (sessions?.has(sessionKey)) {
|
|
67678
|
-
|
|
67922
|
+
log28.debug(`Session ${state.threadId.substring(0, 8)}... already active, skipping resume`);
|
|
67679
67923
|
return;
|
|
67680
67924
|
}
|
|
67681
67925
|
const inFlight = _inFlightSessionStarts.get(sessionKey);
|
|
@@ -67683,7 +67927,7 @@ async function resumeSession(state, ctx) {
|
|
|
67683
67927
|
await inFlight.catch(() => {});
|
|
67684
67928
|
return;
|
|
67685
67929
|
}
|
|
67686
|
-
const attempt = resumeSessionImpl(state, ctx);
|
|
67930
|
+
const attempt = resumeSessionImpl(state, ctx, resumedBy);
|
|
67687
67931
|
_inFlightSessionStarts.set(sessionKey, attempt);
|
|
67688
67932
|
try {
|
|
67689
67933
|
await attempt;
|
|
@@ -67692,9 +67936,9 @@ async function resumeSession(state, ctx) {
|
|
|
67692
67936
|
}
|
|
67693
67937
|
return;
|
|
67694
67938
|
}
|
|
67695
|
-
await resumeSessionImpl(state, ctx);
|
|
67939
|
+
await resumeSessionImpl(state, ctx, resumedBy);
|
|
67696
67940
|
}
|
|
67697
|
-
async function resumeSessionImpl(state, ctx) {
|
|
67941
|
+
async function resumeSessionImpl(state, ctx, resumedBy) {
|
|
67698
67942
|
if (!state.threadId || !state.platformId || !state.claudeSessionId || !state.workingDir) {
|
|
67699
67943
|
const missing = [
|
|
67700
67944
|
!state.threadId && "threadId",
|
|
@@ -67702,35 +67946,35 @@ async function resumeSessionImpl(state, ctx) {
|
|
|
67702
67946
|
!state.claudeSessionId && "claudeSessionId",
|
|
67703
67947
|
!state.workingDir && "workingDir"
|
|
67704
67948
|
].filter(Boolean).join(", ");
|
|
67705
|
-
|
|
67949
|
+
log28.warn(`Skipping session with missing required fields: ${missing}`);
|
|
67706
67950
|
return;
|
|
67707
67951
|
}
|
|
67708
67952
|
const shortId = state.threadId.substring(0, 8);
|
|
67709
67953
|
const platforms = ctx.state.platforms;
|
|
67710
67954
|
const platform = platforms.get(state.platformId);
|
|
67711
67955
|
if (!platform) {
|
|
67712
|
-
|
|
67956
|
+
log28.warn(`Platform ${state.platformId} not registered, skipping resume for ${shortId}...`);
|
|
67713
67957
|
return;
|
|
67714
67958
|
}
|
|
67715
67959
|
if (isDcmThreadId(state.threadId) && !platform.directChannelMode?.enabled) {
|
|
67716
|
-
|
|
67960
|
+
log28.warn(`Direct channel mode disabled for ${state.platformId}, dropping persisted DCM session`);
|
|
67717
67961
|
ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
|
|
67718
67962
|
return;
|
|
67719
67963
|
}
|
|
67720
67964
|
if (!isDcmThreadId(state.threadId)) {
|
|
67721
67965
|
const threadPost = await platform.getPost(state.threadId);
|
|
67722
67966
|
if (!threadPost) {
|
|
67723
|
-
|
|
67967
|
+
log28.warn(`Thread ${shortId}... deleted, skipping resume`);
|
|
67724
67968
|
ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
|
|
67725
67969
|
return;
|
|
67726
67970
|
}
|
|
67727
67971
|
}
|
|
67728
67972
|
if (ctx.state.sessions.size >= ctx.config.maxSessions) {
|
|
67729
|
-
|
|
67973
|
+
log28.warn(`Max sessions reached, skipping resume for ${shortId}...`);
|
|
67730
67974
|
return;
|
|
67731
67975
|
}
|
|
67732
67976
|
if (!existsSync11(state.workingDir)) {
|
|
67733
|
-
|
|
67977
|
+
log28.warn(`Working directory ${state.workingDir} no longer exists, skipping resume for ${shortId}...`);
|
|
67734
67978
|
ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
|
|
67735
67979
|
const resumeFormatter = platform.getFormatter();
|
|
67736
67980
|
const tempSession = {
|
|
@@ -67756,7 +68000,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
|
|
|
67756
68000
|
const appendSystemPrompt = await buildAppendSystemPrompt(platform, state.platformId, state.workingDir, state.threadId, state.startedBy, state.sessionAllowedUsers || [state.startedBy], CHAT_PLATFORM_PROMPT, ctx.state.githubEmailsStore, memoryConfig.enabled && memoryConfig.channelLayer ? ctx.state.memoryStore : null, { userAttribution });
|
|
67757
68001
|
const claudeAccount = ctx.ops.acquireClaudeAccount(state.claudeAccountId, state.threadId);
|
|
67758
68002
|
if (state.claudeAccountId && !claudeAccount) {
|
|
67759
|
-
|
|
68003
|
+
log28.warn(`Persisted session referenced Claude account "${state.claudeAccountId}" ` + `which is no longer configured — resuming under default env`);
|
|
67760
68004
|
}
|
|
67761
68005
|
const resumeBridgeRef = {};
|
|
67762
68006
|
const resumeBridge = await createSessionDecisionBridge(resumeBridgeRef);
|
|
@@ -67839,7 +68083,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
|
|
|
67839
68083
|
worktreePath: detected.worktreePath,
|
|
67840
68084
|
branch: detected.branch
|
|
67841
68085
|
};
|
|
67842
|
-
|
|
68086
|
+
log28.info(`Auto-detected worktree info for resumed session: branch=${detected.branch}`);
|
|
67843
68087
|
}
|
|
67844
68088
|
}
|
|
67845
68089
|
session.messageManager = createMessageManager(session, ctx);
|
|
@@ -67858,6 +68102,14 @@ Please start a new session.`), { action: "Post resume failure notification" });
|
|
|
67858
68102
|
pendingApproval: persistedWithInteractive.pendingApproval
|
|
67859
68103
|
});
|
|
67860
68104
|
}
|
|
68105
|
+
if (resumedBy)
|
|
68106
|
+
session.lastActorUsername = resumedBy;
|
|
68107
|
+
auditLog(session.platformId, {
|
|
68108
|
+
threadId: session.threadId,
|
|
68109
|
+
sessionId: session.sessionId,
|
|
68110
|
+
actor: resumedBy ?? session.startedBy,
|
|
68111
|
+
kind: "session_resume"
|
|
68112
|
+
});
|
|
67861
68113
|
session.threadLogger?.logLifecycle("resume", {
|
|
67862
68114
|
username: state.startedBy,
|
|
67863
68115
|
workingDir: state.workingDir
|
|
@@ -67898,7 +68150,8 @@ ${sessionFormatter.formatItalic("Reconnected to Claude session. You can continue
|
|
|
67898
68150
|
await postResumeCoAuthorOnboarding(session, ctx);
|
|
67899
68151
|
ctx.ops.persistSession(session);
|
|
67900
68152
|
} catch (err) {
|
|
67901
|
-
|
|
68153
|
+
log28.error(`Failed to resume session ${shortId}`, err instanceof Error ? err : undefined);
|
|
68154
|
+
auditSessionEnd(session, "resume-failed");
|
|
67902
68155
|
session.messageManager?.dispose();
|
|
67903
68156
|
session.decisionBridge?.close();
|
|
67904
68157
|
session.decisionBridge = undefined;
|
|
@@ -67931,6 +68184,8 @@ async function sendFollowUp(session, message, files, ctx, username, displayName,
|
|
|
67931
68184
|
return;
|
|
67932
68185
|
}
|
|
67933
68186
|
}
|
|
68187
|
+
if (username && !options?.system)
|
|
68188
|
+
session.lastActorUsername = username;
|
|
67934
68189
|
if (session.needsContextPromptOnNextMessage) {
|
|
67935
68190
|
session.needsContextPromptOnNextMessage = false;
|
|
67936
68191
|
await session.messageManager?.prepareForUserMessage();
|
|
@@ -67951,28 +68206,28 @@ async function resumePausedSession(threadId, message, files, ctx, username) {
|
|
|
67951
68206
|
const persisted = ctx.state.sessionStore.load();
|
|
67952
68207
|
const state = findPersistedByThreadId(persisted, threadId);
|
|
67953
68208
|
if (!state) {
|
|
67954
|
-
|
|
68209
|
+
log28.debug(`No persisted session found for ${threadId.substring(0, 8)}...`);
|
|
67955
68210
|
return;
|
|
67956
68211
|
}
|
|
67957
68212
|
const shortId = threadId.substring(0, 8);
|
|
67958
68213
|
const platform = ctx.state.platforms.get(state.platformId);
|
|
67959
68214
|
if (!platform) {
|
|
67960
|
-
|
|
68215
|
+
log28.warn(`auth.denied.resume: platform '${state.platformId}' not found for ${shortId}...`);
|
|
67961
68216
|
return;
|
|
67962
68217
|
}
|
|
67963
68218
|
const sessionAllowedUsers = new Set(state.sessionAllowedUsers || [state.startedBy].filter(Boolean));
|
|
67964
68219
|
if (!isAuthorizedForSession({ username, platform, sessionAllowedUsers })) {
|
|
67965
|
-
|
|
68220
|
+
log28.warn(`auth.denied.resume: @${username || "unknown"} not authorized to resume ${shortId}...`);
|
|
67966
68221
|
return;
|
|
67967
68222
|
}
|
|
67968
|
-
|
|
67969
|
-
await resumeSession(state, ctx);
|
|
68223
|
+
log28.info(`\uD83D\uDD04 Resuming paused session ${shortId}... for new message`);
|
|
68224
|
+
await resumeSession(state, ctx, username);
|
|
67970
68225
|
const session = ctx.ops.findSessionByThreadId(threadId);
|
|
67971
68226
|
if (session && session.claude.isRunning() && session.messageManager) {
|
|
67972
68227
|
session.messageCount++;
|
|
67973
68228
|
await session.messageManager.handleUserMessage(message, files, username);
|
|
67974
68229
|
} else {
|
|
67975
|
-
|
|
68230
|
+
log28.warn(`Failed to resume session ${shortId}..., could not send message`);
|
|
67976
68231
|
}
|
|
67977
68232
|
}
|
|
67978
68233
|
async function handleExit(sessionId, code, ctx, source) {
|
|
@@ -67980,7 +68235,7 @@ async function handleExit(sessionId, code, ctx, source) {
|
|
|
67980
68235
|
const shortId = sessionId.substring(0, 8);
|
|
67981
68236
|
sessionLog6(session).debug(`handleExit called code=${code} isShuttingDown=${ctx.state.isShuttingDown}`);
|
|
67982
68237
|
if (!session) {
|
|
67983
|
-
|
|
68238
|
+
log28.debug(`Session ${shortId}... not found (already cleaned up)`);
|
|
67984
68239
|
return;
|
|
67985
68240
|
}
|
|
67986
68241
|
if (source && session.claude !== source) {
|
|
@@ -68001,7 +68256,8 @@ async function handleExit(sessionId, code, ctx, source) {
|
|
|
68001
68256
|
await cleanupSession(session, ctx, {
|
|
68002
68257
|
action: "exit",
|
|
68003
68258
|
details: { reason: "shutdown", exitCode: code },
|
|
68004
|
-
cleanupPostIndex: false
|
|
68259
|
+
cleanupPostIndex: false,
|
|
68260
|
+
auditReason: "shutdown"
|
|
68005
68261
|
});
|
|
68006
68262
|
return;
|
|
68007
68263
|
}
|
|
@@ -68009,7 +68265,7 @@ async function handleExit(sessionId, code, ctx, source) {
|
|
|
68009
68265
|
sessionLog6(session).debug(`Exited after interrupt, preserving for resume`);
|
|
68010
68266
|
ctx.ops.stopTyping(session);
|
|
68011
68267
|
cleanupSessionTimers(session);
|
|
68012
|
-
await closeThreadLogger(session, "interrupt", { exitCode: code });
|
|
68268
|
+
await closeThreadLogger(session, "interrupt", { exitCode: code }, "pause");
|
|
68013
68269
|
const message = session.lifecycle.hasClaudeResponded ? `ℹ️ Session paused. Send a new message to continue.` : `ℹ️ Session ended before Claude could respond. Send a new message to start fresh.`;
|
|
68014
68270
|
const pausePost = await withErrorHandling(() => post(session, "info", message), { action: "Post session pause notification", session });
|
|
68015
68271
|
if (session.lifecycle.hasClaudeResponded) {
|
|
@@ -68020,7 +68276,7 @@ async function handleExit(sessionId, code, ctx, source) {
|
|
|
68020
68276
|
}
|
|
68021
68277
|
ctx.ops.persistSession(session);
|
|
68022
68278
|
}
|
|
68023
|
-
removeFromRegistry(session, ctx);
|
|
68279
|
+
removeFromRegistry(session, ctx, "pause");
|
|
68024
68280
|
sessionLog6(session).info(`⏸ Session paused`);
|
|
68025
68281
|
await ctx.ops.updateStickyMessage();
|
|
68026
68282
|
return;
|
|
@@ -68030,7 +68286,8 @@ async function handleExit(sessionId, code, ctx, source) {
|
|
|
68030
68286
|
sessionLog6(session).debug(`Exited before Claude responded, not persisting`);
|
|
68031
68287
|
await cleanupSession(session, ctx, {
|
|
68032
68288
|
action: "exit",
|
|
68033
|
-
details: { reason: "early_exit", exitCode: code }
|
|
68289
|
+
details: { reason: "early_exit", exitCode: code },
|
|
68290
|
+
auditReason: "early-exit"
|
|
68034
68291
|
});
|
|
68035
68292
|
const earlyExitFormatter = session.platform.getFormatter();
|
|
68036
68293
|
await withErrorHandling(() => post(session, "warning", `${earlyExitFormatter.formatBold("Session ended")} before Claude could respond (exit code ${code}). Please start a new session.`), { action: "Post early exit notification", session });
|
|
@@ -68044,6 +68301,7 @@ async function handleExit(sessionId, code, ctx, source) {
|
|
|
68044
68301
|
const isPermanent = session.claude.isPermanentFailure();
|
|
68045
68302
|
const permanentReason = session.claude.getPermanentFailureReason();
|
|
68046
68303
|
sessionLog6(session).debug(`Resumed session failed with code ${code}, attempt ${session.lifecycle.resumeFailCount}/${MAX_RESUME_FAILURES}, permanent=${isPermanent}`);
|
|
68304
|
+
auditSessionEnd(session, code === null ? "exit" : `exit:${code}`);
|
|
68047
68305
|
await cleanupSession(session, ctx, {
|
|
68048
68306
|
closeLogger: false,
|
|
68049
68307
|
cleanupPostIndex: false
|
|
@@ -68079,7 +68337,7 @@ Please start a new session.`), { action: "Post session permanent failure", sessi
|
|
|
68079
68337
|
scheduleDistillation(session, ctx, "exit");
|
|
68080
68338
|
ctx.ops.stopTyping(session);
|
|
68081
68339
|
cleanupSessionTimers(session);
|
|
68082
|
-
await closeThreadLogger(session, "exit", { exitCode: code });
|
|
68340
|
+
await closeThreadLogger(session, "exit", { exitCode: code }, code === 0 || code === null ? "exit" : `exit:${code}`);
|
|
68083
68341
|
const exitTaskState = session.messageManager?.getTaskListState();
|
|
68084
68342
|
if (exitTaskState?.postId) {
|
|
68085
68343
|
await session.platform.unpinPost(exitTaskState.postId).catch(() => {});
|
|
@@ -68092,7 +68350,7 @@ Please start a new session.`), { action: "Post session permanent failure", sessi
|
|
|
68092
68350
|
if (session.worktreeInfo) {
|
|
68093
68351
|
ctx.ops.unregisterWorktreeUser(session.worktreeInfo.worktreePath, session.sessionId);
|
|
68094
68352
|
}
|
|
68095
|
-
removeFromRegistry(session, ctx);
|
|
68353
|
+
removeFromRegistry(session, ctx, code === 0 ? "exit" : `exit:${code}`);
|
|
68096
68354
|
if (code === 0 || code === null) {
|
|
68097
68355
|
ctx.ops.unpersistSession(session.sessionId);
|
|
68098
68356
|
} else {
|
|
@@ -68101,7 +68359,7 @@ Please start a new session.`), { action: "Post session permanent failure", sessi
|
|
|
68101
68359
|
sessionLog6(session).info(`■ Session ended`);
|
|
68102
68360
|
await ctx.ops.updateStickyMessage();
|
|
68103
68361
|
}
|
|
68104
|
-
async function killSession(session, unpersist, ctx) {
|
|
68362
|
+
async function killSession(session, unpersist, ctx, auditCause = "kill") {
|
|
68105
68363
|
if (!unpersist) {
|
|
68106
68364
|
transitionTo(session, "restarting");
|
|
68107
68365
|
}
|
|
@@ -68109,7 +68367,7 @@ async function killSession(session, unpersist, ctx) {
|
|
|
68109
68367
|
scheduleDistillation(session, ctx, "stop");
|
|
68110
68368
|
}
|
|
68111
68369
|
ctx.ops.stopTyping(session);
|
|
68112
|
-
await closeThreadLogger(session, "kill", { unpersist });
|
|
68370
|
+
await closeThreadLogger(session, "kill", { unpersist }, auditCause);
|
|
68113
68371
|
session.claude.kill();
|
|
68114
68372
|
const killTaskState = session.messageManager?.getTaskListState();
|
|
68115
68373
|
if (killTaskState?.postId) {
|
|
@@ -68118,7 +68376,7 @@ async function killSession(session, unpersist, ctx) {
|
|
|
68118
68376
|
if (unpersist && session.worktreeInfo) {
|
|
68119
68377
|
ctx.ops.unregisterWorktreeUser(session.worktreeInfo.worktreePath, session.sessionId);
|
|
68120
68378
|
}
|
|
68121
|
-
removeFromRegistry(session, ctx);
|
|
68379
|
+
removeFromRegistry(session, ctx, auditCause);
|
|
68122
68380
|
if (unpersist) {
|
|
68123
68381
|
ctx.ops.unpersistSession(session.sessionId);
|
|
68124
68382
|
}
|
|
@@ -68132,6 +68390,7 @@ async function killAllSessions(ctx) {
|
|
|
68132
68390
|
if (ctx.state.isShuttingDown) {
|
|
68133
68391
|
ctx.ops.persistSession(session);
|
|
68134
68392
|
}
|
|
68393
|
+
auditSessionEnd(session, ctx.state.isShuttingDown ? "shutdown" : "kill");
|
|
68135
68394
|
killPromises.push(session.claude.kill());
|
|
68136
68395
|
}
|
|
68137
68396
|
await Promise.all(killPromises);
|
|
@@ -68162,7 +68421,7 @@ async function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
|
|
|
68162
68421
|
transitionTo(session, "paused");
|
|
68163
68422
|
ctx.ops.persistSession(session);
|
|
68164
68423
|
scheduleDistillation(session, ctx, "timeout");
|
|
68165
|
-
await killSession(session, false, ctx);
|
|
68424
|
+
await killSession(session, false, ctx, "timeout");
|
|
68166
68425
|
continue;
|
|
68167
68426
|
}
|
|
68168
68427
|
const warningThresholdMs = timeoutMs - warningMs;
|
|
@@ -68183,7 +68442,7 @@ async function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
|
|
|
68183
68442
|
|
|
68184
68443
|
// src/platform/dm-discovery-runtime.ts
|
|
68185
68444
|
function createDmDiscoveryRuntime(deps) {
|
|
68186
|
-
const { platforms, session, log:
|
|
68445
|
+
const { platforms, session, log: log29 } = deps;
|
|
68187
68446
|
const graceMs = deps.graceMs ?? 30000;
|
|
68188
68447
|
const orphanTtlMs = deps.orphanTtlMs ?? 10 * 60000;
|
|
68189
68448
|
const instanceByChannel = new Map;
|
|
@@ -68226,9 +68485,10 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
68226
68485
|
session.removePlatform(dmId);
|
|
68227
68486
|
if (client)
|
|
68228
68487
|
Promise.resolve(client.disconnect()).catch(() => {});
|
|
68488
|
+
configureAuditLog(dmId, false);
|
|
68229
68489
|
if (deps.isEnabled?.(dmId) !== false)
|
|
68230
68490
|
deps.removeUiRow?.(dmId);
|
|
68231
|
-
|
|
68491
|
+
log29("info", `\uD83E\uDDF9 DM instance ${dmId} torn down (${reason})`);
|
|
68232
68492
|
};
|
|
68233
68493
|
const register = (parentCfg, channelId, partnerUsernames) => {
|
|
68234
68494
|
const dmConfig = deriveDmPlatformConfig(parentCfg, channelId, partnerUsernames);
|
|
@@ -68257,10 +68517,10 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
68257
68517
|
}
|
|
68258
68518
|
const dmId = dmPlatformId(parentConfig.id, post2.channelId);
|
|
68259
68519
|
if (deps.isEnabled && !deps.isEnabled(dmId)) {
|
|
68260
|
-
|
|
68520
|
+
log29("info", `Ignoring DM for disabled instance ${dmId}`);
|
|
68261
68521
|
return;
|
|
68262
68522
|
}
|
|
68263
|
-
|
|
68523
|
+
log29("info", `\uD83D\uDCE9 New DM conversation with @${username} — spawning ${dmId}`);
|
|
68264
68524
|
const dmClient = register(parentConfig, post2.channelId, [username]);
|
|
68265
68525
|
connecting.add(dmId);
|
|
68266
68526
|
dmClient.connect().then(() => {
|
|
@@ -68270,7 +68530,7 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
68270
68530
|
}).catch((err) => {
|
|
68271
68531
|
if (platforms.get(dmId) !== dmClient)
|
|
68272
68532
|
return;
|
|
68273
|
-
|
|
68533
|
+
log29("error", `Failed to connect DM instance ${dmId}, discarding: ${err}`);
|
|
68274
68534
|
(async () => {
|
|
68275
68535
|
const threadId = `dcm:${dmId}`;
|
|
68276
68536
|
const inFlightDeadline = Date.now() + 30000;
|
|
@@ -68279,7 +68539,7 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
68279
68539
|
if (!inFlight)
|
|
68280
68540
|
break;
|
|
68281
68541
|
if (Date.now() > inFlightDeadline) {
|
|
68282
|
-
|
|
68542
|
+
log29("warn", `In-flight session start for ${dmId} did not settle within 30s — proceeding with teardown`);
|
|
68283
68543
|
break;
|
|
68284
68544
|
}
|
|
68285
68545
|
await Promise.race([
|
|
@@ -68294,7 +68554,7 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
68294
68554
|
try {
|
|
68295
68555
|
await session.cancelSession(threadId, dmClient.getBotName());
|
|
68296
68556
|
} catch (cancelErr) {
|
|
68297
|
-
|
|
68557
|
+
log29("warn", `Failed to cancel stranded DM session ${threadId} (will be reaped by idle cleanup): ${cancelErr}`);
|
|
68298
68558
|
}
|
|
68299
68559
|
}
|
|
68300
68560
|
}
|
|
@@ -68304,7 +68564,7 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
68304
68564
|
return;
|
|
68305
68565
|
if (!session.registry.findByThreadId(threadId))
|
|
68306
68566
|
return;
|
|
68307
|
-
|
|
68567
|
+
log29("warn", `Sweeping session stranded on removed DM platform ${dmId}`);
|
|
68308
68568
|
session.cancelSession(threadId, dmClient.getBotName()).catch(() => {});
|
|
68309
68569
|
}, 2000);
|
|
68310
68570
|
})();
|
|
@@ -68335,21 +68595,21 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
68335
68595
|
continue;
|
|
68336
68596
|
const parentCfg = platformConfigs.filter((p) => p.type === "mattermost" && !!p.directMessages && pid.startsWith(`${p.id}${DM_PLATFORM_SEP}`)).sort((a, b) => b.id.length - a.id.length)[0];
|
|
68337
68597
|
if (!parentCfg) {
|
|
68338
|
-
|
|
68598
|
+
log29("warn", `Skipping persisted DM session for ${pid} (parent missing, renamed, or directMessages off)`);
|
|
68339
68599
|
continue;
|
|
68340
68600
|
}
|
|
68341
68601
|
const channelId = pid.slice(parentCfg.id.length + DM_PLATFORM_SEP.length);
|
|
68342
68602
|
if (instanceByChannel.has(channelId)) {
|
|
68343
|
-
|
|
68603
|
+
log29("warn", `Skipping persisted DM session for ${pid} (channel already owned by ${instanceByChannel.get(channelId)})`);
|
|
68344
68604
|
continue;
|
|
68345
68605
|
}
|
|
68346
68606
|
if (!isEnabled(pid)) {
|
|
68347
|
-
|
|
68607
|
+
log29("info", `Skipping disabled DM instance ${pid}`);
|
|
68348
68608
|
skippedDisabled.push({ platformId: pid, channelId });
|
|
68349
68609
|
continue;
|
|
68350
68610
|
}
|
|
68351
68611
|
const partners = persisted.sessionAllowedUsers && persisted.sessionAllowedUsers.length > 0 ? persisted.sessionAllowedUsers : [persisted.startedBy].filter((u) => !!u);
|
|
68352
|
-
|
|
68612
|
+
log29("info", `♻️ Reconstructing DM instance ${pid}`);
|
|
68353
68613
|
register(parentCfg, channelId, partners);
|
|
68354
68614
|
connecting.add(pid);
|
|
68355
68615
|
reconstructed.set(pid, channelId);
|
|
@@ -68391,7 +68651,7 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
68391
68651
|
// src/onboarding.ts
|
|
68392
68652
|
var import_prompts = __toESM(require_prompts3(), 1);
|
|
68393
68653
|
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
68394
|
-
import { join as
|
|
68654
|
+
import { join as join12, dirname as dirname8 } from "path";
|
|
68395
68655
|
import { spawn as spawn3 } from "child_process";
|
|
68396
68656
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
68397
68657
|
|
|
@@ -68455,7 +68715,7 @@ function overheadVisibilityChoiceIndex(mode) {
|
|
|
68455
68715
|
return OVERHEAD_VISIBILITY_CHOICES.findIndex((c) => c.value === mode);
|
|
68456
68716
|
}
|
|
68457
68717
|
var __dirname6 = dirname8(fileURLToPath6(import.meta.url));
|
|
68458
|
-
var SLACK_MANIFEST_PATH =
|
|
68718
|
+
var SLACK_MANIFEST_PATH = join12(__dirname6, "..", "docs", "slack-app-manifest.yaml");
|
|
68459
68719
|
var onCancel = () => {
|
|
68460
68720
|
console.log("");
|
|
68461
68721
|
console.log(dim(" Setup cancelled."));
|
|
@@ -69781,7 +70041,7 @@ async function setupSlackPlatform(id, existing) {
|
|
|
69781
70041
|
// src/platform/base-client.ts
|
|
69782
70042
|
init_logger();
|
|
69783
70043
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
69784
|
-
var
|
|
70044
|
+
var log29 = createLogger("base-client");
|
|
69785
70045
|
|
|
69786
70046
|
class BasePlatformClient extends EventEmitter3 {
|
|
69787
70047
|
allowedUsers = [];
|
|
@@ -69814,7 +70074,7 @@ class BasePlatformClient extends EventEmitter3 {
|
|
|
69814
70074
|
try {
|
|
69815
70075
|
await this.addReaction(post2.id, emoji);
|
|
69816
70076
|
} catch (err) {
|
|
69817
|
-
|
|
70077
|
+
log29.warn(`Failed to add reaction ${emoji}: ${err}`);
|
|
69818
70078
|
}
|
|
69819
70079
|
}
|
|
69820
70080
|
return post2;
|
|
@@ -69841,7 +70101,7 @@ class BasePlatformClient extends EventEmitter3 {
|
|
|
69841
70101
|
this.heartbeatInterval = setInterval(() => {
|
|
69842
70102
|
const silentFor = Date.now() - this.lastMessageAt;
|
|
69843
70103
|
if (silentFor > this.HEARTBEAT_TIMEOUT_MS) {
|
|
69844
|
-
|
|
70104
|
+
log29.warn(`Connection dead (no activity for ${Math.round(silentFor / 1000)}s), reconnecting...`);
|
|
69845
70105
|
this.stopHeartbeat();
|
|
69846
70106
|
this.scheduleReconnect();
|
|
69847
70107
|
return;
|
|
@@ -69861,7 +70121,7 @@ class BasePlatformClient extends EventEmitter3 {
|
|
|
69861
70121
|
this.reconnectTimeout = null;
|
|
69862
70122
|
}
|
|
69863
70123
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
69864
|
-
|
|
70124
|
+
log29.error("Max reconnection attempts reached");
|
|
69865
70125
|
return;
|
|
69866
70126
|
}
|
|
69867
70127
|
this.forceCloseConnection();
|
|
@@ -69888,7 +70148,7 @@ class BasePlatformClient extends EventEmitter3 {
|
|
|
69888
70148
|
this.emit("connected");
|
|
69889
70149
|
if (this.isReconnecting) {
|
|
69890
70150
|
this.recoverMissedMessages().catch((err) => {
|
|
69891
|
-
|
|
70151
|
+
log29.warn(`Failed to recover missed messages: ${err}`);
|
|
69892
70152
|
});
|
|
69893
70153
|
}
|
|
69894
70154
|
this.isReconnecting = false;
|
|
@@ -69922,7 +70182,7 @@ init_logger();
|
|
|
69922
70182
|
// src/platform/mattermost/upload.ts
|
|
69923
70183
|
init_logger();
|
|
69924
70184
|
import { readFile as readFile3 } from "fs/promises";
|
|
69925
|
-
var
|
|
70185
|
+
var log30 = createLogger("mm-upload");
|
|
69926
70186
|
async function uploadFileMattermost(args) {
|
|
69927
70187
|
const { url, token, channelId, threadId, filePath, filename, caption } = args;
|
|
69928
70188
|
const buffer = await readFile3(filePath);
|
|
@@ -69930,7 +70190,7 @@ async function uploadFileMattermost(args) {
|
|
|
69930
70190
|
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
69931
70191
|
const formData = new FormData;
|
|
69932
70192
|
formData.append("files", new Blob([arrayBuffer]), filename);
|
|
69933
|
-
|
|
70193
|
+
log30.debug(`POST /files (${buffer.length} bytes, ${filename})`);
|
|
69934
70194
|
const uploadResponse = await fetch(uploadUrl, {
|
|
69935
70195
|
method: "POST",
|
|
69936
70196
|
headers: {
|
|
@@ -69954,7 +70214,7 @@ async function uploadFileMattermost(args) {
|
|
|
69954
70214
|
root_id: resolvePostThreadId(threadId),
|
|
69955
70215
|
file_ids: [fileInfo.id]
|
|
69956
70216
|
};
|
|
69957
|
-
|
|
70217
|
+
log30.debug(`POST /posts (file_ids=[${fileInfo.id}])`);
|
|
69958
70218
|
const postResponse = await fetch(postUrl, {
|
|
69959
70219
|
method: "POST",
|
|
69960
70220
|
headers: {
|
|
@@ -70041,7 +70301,7 @@ ${code}
|
|
|
70041
70301
|
}
|
|
70042
70302
|
|
|
70043
70303
|
// src/platform/mattermost/client.ts
|
|
70044
|
-
var
|
|
70304
|
+
var log31 = createLogger("mattermost");
|
|
70045
70305
|
|
|
70046
70306
|
class MattermostClient extends BasePlatformClient {
|
|
70047
70307
|
platformId;
|
|
@@ -70049,6 +70309,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70049
70309
|
displayName;
|
|
70050
70310
|
directChannelMode;
|
|
70051
70311
|
approvals;
|
|
70312
|
+
ackReaction;
|
|
70052
70313
|
ws = null;
|
|
70053
70314
|
url;
|
|
70054
70315
|
token;
|
|
@@ -70072,6 +70333,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70072
70333
|
this.outboundFiles = platformConfig.outboundFiles;
|
|
70073
70334
|
this.directChannelMode = resolveDirectChannelMode(platformConfig.directChannelMode);
|
|
70074
70335
|
this.approvals = platformConfig.approvals;
|
|
70336
|
+
this.ackReaction = normalizeAckReaction(platformConfig.ackReaction, `platforms[${platformConfig.id}].ackReaction`);
|
|
70075
70337
|
}
|
|
70076
70338
|
normalizePlatformUser(mattermostUser) {
|
|
70077
70339
|
const displayName = mattermostUser.first_name || mattermostUser.nickname || mattermostUser.username;
|
|
@@ -70129,7 +70391,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70129
70391
|
const hasFileIds = fileIds && fileIds.length > 0;
|
|
70130
70392
|
const hasFileMetadata = post2.metadata?.files && post2.metadata.files.length > 0;
|
|
70131
70393
|
if (hasFileIds && !hasFileMetadata) {
|
|
70132
|
-
|
|
70394
|
+
log31.debug(`Post ${formatShortId(post2.id)} has ${fileIds.length} file(s), fetching metadata`);
|
|
70133
70395
|
try {
|
|
70134
70396
|
const files = [];
|
|
70135
70397
|
for (const fileId of fileIds) {
|
|
@@ -70137,7 +70399,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70137
70399
|
const file = await this.api("GET", `/files/${fileId}/info`);
|
|
70138
70400
|
files.push(file);
|
|
70139
70401
|
} catch (err) {
|
|
70140
|
-
|
|
70402
|
+
log31.warn(`Failed to fetch file info for ${fileId}: ${err}`);
|
|
70141
70403
|
}
|
|
70142
70404
|
}
|
|
70143
70405
|
if (files.length > 0) {
|
|
@@ -70145,10 +70407,10 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70145
70407
|
...post2.metadata,
|
|
70146
70408
|
files
|
|
70147
70409
|
};
|
|
70148
|
-
|
|
70410
|
+
log31.debug(`Enriched post ${formatShortId(post2.id)} with ${files.length} file(s)`);
|
|
70149
70411
|
}
|
|
70150
70412
|
} catch (err) {
|
|
70151
|
-
|
|
70413
|
+
log31.warn(`Failed to fetch file metadata for post ${formatShortId(post2.id)}: ${err}`);
|
|
70152
70414
|
}
|
|
70153
70415
|
}
|
|
70154
70416
|
}
|
|
@@ -70158,7 +70420,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70158
70420
|
const user = await this.getUser(post2.user_id);
|
|
70159
70421
|
this.emit("direct_message", this.normalizePlatformPost(post2), user);
|
|
70160
70422
|
} catch (err) {
|
|
70161
|
-
|
|
70423
|
+
log31.warn(`Failed to emit direct message: ${err}`);
|
|
70162
70424
|
}
|
|
70163
70425
|
}
|
|
70164
70426
|
MAX_RETRIES = 6;
|
|
@@ -70166,7 +70428,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70166
70428
|
RETRY_DELAY_CAP_MS = 2000;
|
|
70167
70429
|
async api(method, path10, body, retryCount = 0, options) {
|
|
70168
70430
|
const url = `${this.url}/api/v4${path10}`;
|
|
70169
|
-
|
|
70431
|
+
log31.debug(`API ${method} ${path10}`);
|
|
70170
70432
|
const response = await fetch(url, {
|
|
70171
70433
|
method,
|
|
70172
70434
|
headers: {
|
|
@@ -70179,19 +70441,19 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70179
70441
|
const text = await response.text();
|
|
70180
70442
|
if (response.status === 500 && retryCount < this.MAX_RETRIES) {
|
|
70181
70443
|
const delay2 = this.retryDelayMs(retryCount);
|
|
70182
|
-
|
|
70444
|
+
log31.warn(`API ${method} ${path10} failed with 500, retrying in ${delay2}ms (attempt ${retryCount + 1}/${this.MAX_RETRIES})`);
|
|
70183
70445
|
await new Promise((resolve7) => setTimeout(resolve7, delay2));
|
|
70184
70446
|
return this.api(method, path10, body, retryCount + 1, options);
|
|
70185
70447
|
}
|
|
70186
70448
|
const isSilent = options?.silent?.includes(response.status);
|
|
70187
70449
|
if (isSilent) {
|
|
70188
|
-
|
|
70450
|
+
log31.debug(`API ${method} ${path10} failed: ${response.status} (expected)`);
|
|
70189
70451
|
} else {
|
|
70190
|
-
|
|
70452
|
+
log31.warn(`API ${method} ${path10} failed: ${response.status} ${text.substring(0, 100)}`);
|
|
70191
70453
|
}
|
|
70192
70454
|
throw new Error(`Mattermost API error ${response.status}: ${text}`);
|
|
70193
70455
|
}
|
|
70194
|
-
|
|
70456
|
+
log31.debug(`API ${method} ${path10} → ${response.status}`);
|
|
70195
70457
|
return response.json();
|
|
70196
70458
|
}
|
|
70197
70459
|
retryDelayMs(retryCount) {
|
|
@@ -70207,28 +70469,28 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70207
70469
|
async getUser(userId) {
|
|
70208
70470
|
const cached = this.userCache.get(userId);
|
|
70209
70471
|
if (cached) {
|
|
70210
|
-
|
|
70472
|
+
log31.debug(`User ${userId} found in cache: @${cached.username}`);
|
|
70211
70473
|
return this.normalizePlatformUser(cached);
|
|
70212
70474
|
}
|
|
70213
70475
|
try {
|
|
70214
70476
|
const user = await this.api("GET", `/users/${userId}`);
|
|
70215
70477
|
this.userCache.set(userId, user);
|
|
70216
|
-
|
|
70478
|
+
log31.debug(`User ${userId} fetched: @${user.username}`);
|
|
70217
70479
|
return this.normalizePlatformUser(user);
|
|
70218
70480
|
} catch (err) {
|
|
70219
|
-
|
|
70481
|
+
log31.warn(`Failed to get user ${userId}: ${err}`);
|
|
70220
70482
|
return null;
|
|
70221
70483
|
}
|
|
70222
70484
|
}
|
|
70223
70485
|
async getUserByUsername(username) {
|
|
70224
70486
|
try {
|
|
70225
|
-
|
|
70487
|
+
log31.debug(`Looking up user by username: @${username}`);
|
|
70226
70488
|
const user = await this.api("GET", `/users/username/${username}`);
|
|
70227
70489
|
this.userCache.set(user.id, user);
|
|
70228
|
-
|
|
70490
|
+
log31.debug(`User @${username} found: ${user.id}`);
|
|
70229
70491
|
return this.normalizePlatformUser(user);
|
|
70230
70492
|
} catch (err) {
|
|
70231
|
-
|
|
70493
|
+
log31.warn(`User @${username} not found: ${err}`);
|
|
70232
70494
|
return null;
|
|
70233
70495
|
}
|
|
70234
70496
|
}
|
|
@@ -70250,7 +70512,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70250
70512
|
return this.normalizePlatformPost(post2);
|
|
70251
70513
|
}
|
|
70252
70514
|
async addReaction(postId, emojiName) {
|
|
70253
|
-
|
|
70515
|
+
log31.debug(`Adding reaction :${emojiName}: to post ${postId.substring(0, 8)}`);
|
|
70254
70516
|
await this.api("POST", "/reactions", {
|
|
70255
70517
|
user_id: this.botUserId,
|
|
70256
70518
|
post_id: postId,
|
|
@@ -70258,11 +70520,11 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70258
70520
|
});
|
|
70259
70521
|
}
|
|
70260
70522
|
async removeReaction(postId, emojiName) {
|
|
70261
|
-
|
|
70523
|
+
log31.debug(`Removing reaction :${emojiName}: from post ${postId.substring(0, 8)}`);
|
|
70262
70524
|
await this.api("DELETE", `/users/${this.botUserId}/posts/${postId}/reactions/${emojiName}`);
|
|
70263
70525
|
}
|
|
70264
70526
|
async downloadFile(fileId) {
|
|
70265
|
-
|
|
70527
|
+
log31.debug(`Downloading file ${fileId}`);
|
|
70266
70528
|
const url = `${this.url}/api/v4/files/${fileId}`;
|
|
70267
70529
|
const response = await fetch(url, {
|
|
70268
70530
|
headers: {
|
|
@@ -70270,11 +70532,11 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70270
70532
|
}
|
|
70271
70533
|
});
|
|
70272
70534
|
if (!response.ok) {
|
|
70273
|
-
|
|
70535
|
+
log31.warn(`Failed to download file ${fileId}: ${response.status}`);
|
|
70274
70536
|
throw new Error(`Failed to download file ${fileId}: ${response.status}`);
|
|
70275
70537
|
}
|
|
70276
70538
|
const arrayBuffer = await response.arrayBuffer();
|
|
70277
|
-
|
|
70539
|
+
log31.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
|
|
70278
70540
|
return Buffer.from(arrayBuffer);
|
|
70279
70541
|
}
|
|
70280
70542
|
async getFileInfo(fileId) {
|
|
@@ -70296,24 +70558,24 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70296
70558
|
}
|
|
70297
70559
|
async getPost(postId) {
|
|
70298
70560
|
try {
|
|
70299
|
-
|
|
70561
|
+
log31.debug(`Fetching post ${postId.substring(0, 8)}`);
|
|
70300
70562
|
const post2 = await this.api("GET", `/posts/${postId}`);
|
|
70301
70563
|
return this.normalizePlatformPost(post2);
|
|
70302
70564
|
} catch (err) {
|
|
70303
|
-
|
|
70565
|
+
log31.debug(`Post ${postId.substring(0, 8)} not found: ${err}`);
|
|
70304
70566
|
return null;
|
|
70305
70567
|
}
|
|
70306
70568
|
}
|
|
70307
70569
|
async deletePost(postId) {
|
|
70308
|
-
|
|
70570
|
+
log31.debug(`Deleting post ${postId.substring(0, 8)}`);
|
|
70309
70571
|
await this.api("DELETE", `/posts/${postId}`);
|
|
70310
70572
|
}
|
|
70311
70573
|
async pinPost(postId) {
|
|
70312
|
-
|
|
70574
|
+
log31.debug(`Pinning post ${postId.substring(0, 8)}`);
|
|
70313
70575
|
await this.api("POST", `/posts/${postId}/pin`);
|
|
70314
70576
|
}
|
|
70315
70577
|
async unpinPost(postId) {
|
|
70316
|
-
|
|
70578
|
+
log31.debug(`Unpinning post ${postId.substring(0, 8)}`);
|
|
70317
70579
|
try {
|
|
70318
70580
|
await this.api("POST", `/posts/${postId}/unpin`, undefined, 0, { silent: [403, 404] });
|
|
70319
70581
|
} catch (err) {
|
|
@@ -70357,7 +70619,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70357
70619
|
}
|
|
70358
70620
|
return messages;
|
|
70359
70621
|
} catch (err) {
|
|
70360
|
-
|
|
70622
|
+
log31.warn(`Failed to get thread history for ${threadId}: ${err}`);
|
|
70361
70623
|
return [];
|
|
70362
70624
|
}
|
|
70363
70625
|
}
|
|
@@ -70376,7 +70638,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70376
70638
|
posts.sort((a, b) => (a.createAt ?? 0) - (b.createAt ?? 0));
|
|
70377
70639
|
return posts;
|
|
70378
70640
|
} catch (err) {
|
|
70379
|
-
|
|
70641
|
+
log31.warn(`Failed to get channel posts after ${afterPostId}: ${err}`);
|
|
70380
70642
|
return [];
|
|
70381
70643
|
}
|
|
70382
70644
|
}
|
|
@@ -70512,13 +70774,13 @@ class MattermostClient extends BasePlatformClient {
|
|
|
70512
70774
|
if (!this.lastProcessedPostId) {
|
|
70513
70775
|
return;
|
|
70514
70776
|
}
|
|
70515
|
-
|
|
70777
|
+
log31.info(`Recovering missed messages after post ${this.lastProcessedPostId}...`);
|
|
70516
70778
|
const missedPosts = await this.getChannelPostsAfter(this.lastProcessedPostId);
|
|
70517
70779
|
if (missedPosts.length === 0) {
|
|
70518
|
-
|
|
70780
|
+
log31.info("No missed messages to recover");
|
|
70519
70781
|
return;
|
|
70520
70782
|
}
|
|
70521
|
-
|
|
70783
|
+
log31.info(`Recovered ${missedPosts.length} missed message(s)`);
|
|
70522
70784
|
for (const post2 of missedPosts) {
|
|
70523
70785
|
this.lastProcessedPostId = post2.id;
|
|
70524
70786
|
const user = await this.getUser(post2.userId);
|
|
@@ -70578,7 +70840,7 @@ init_logger();
|
|
|
70578
70840
|
// src/platform/slack/upload.ts
|
|
70579
70841
|
init_logger();
|
|
70580
70842
|
import { readFile as readFile4 } from "fs/promises";
|
|
70581
|
-
var
|
|
70843
|
+
var log32 = createLogger("slack-upload");
|
|
70582
70844
|
var DEFAULT_API_URL = "https://slack.com/api";
|
|
70583
70845
|
async function uploadFileSlack(args) {
|
|
70584
70846
|
const { botToken, channelId, threadTs, filePath, filename, caption } = args;
|
|
@@ -70586,7 +70848,7 @@ async function uploadFileSlack(args) {
|
|
|
70586
70848
|
const buffer = await readFile4(filePath);
|
|
70587
70849
|
const params = new URLSearchParams({ filename, length: String(buffer.length) });
|
|
70588
70850
|
const step1Url = `${apiUrl}/files.getUploadURLExternal?${params.toString()}`;
|
|
70589
|
-
|
|
70851
|
+
log32.debug(`GET files.getUploadURLExternal (${buffer.length} bytes, ${filename})`);
|
|
70590
70852
|
const step1Response = await fetch(step1Url, {
|
|
70591
70853
|
method: "GET",
|
|
70592
70854
|
headers: {
|
|
@@ -70604,7 +70866,7 @@ async function uploadFileSlack(args) {
|
|
|
70604
70866
|
const uploadUrl = step1Data.upload_url;
|
|
70605
70867
|
const fileId = step1Data.file_id;
|
|
70606
70868
|
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
70607
|
-
|
|
70869
|
+
log32.debug(`POST <upload_url>`);
|
|
70608
70870
|
const step2Response = await fetch(uploadUrl, {
|
|
70609
70871
|
method: "POST",
|
|
70610
70872
|
headers: {
|
|
@@ -70624,7 +70886,7 @@ async function uploadFileSlack(args) {
|
|
|
70624
70886
|
if (caption !== undefined) {
|
|
70625
70887
|
step3Body.initial_comment = caption;
|
|
70626
70888
|
}
|
|
70627
|
-
|
|
70889
|
+
log32.debug(`POST files.completeUploadExternal (file_id=${fileId}, thread_ts=${threadTs})`);
|
|
70628
70890
|
const step3Response = await fetch(`${apiUrl}/files.completeUploadExternal`, {
|
|
70629
70891
|
method: "POST",
|
|
70630
70892
|
headers: {
|
|
@@ -70642,7 +70904,7 @@ async function uploadFileSlack(args) {
|
|
|
70642
70904
|
throw new Error(`Slack completeUploadExternal error: ${step3Data.error || "unknown"}`);
|
|
70643
70905
|
}
|
|
70644
70906
|
if (!step3Data.ts) {
|
|
70645
|
-
|
|
70907
|
+
log32.warn(`Slack completeUploadExternal returned no ts; using fileId ${fileId} as postId. ` + `Do not use this id for updatePost/addReaction.`);
|
|
70646
70908
|
}
|
|
70647
70909
|
return { fileId, postId: step3Data.ts ?? fileId };
|
|
70648
70910
|
}
|
|
@@ -70717,7 +70979,7 @@ ${code}
|
|
|
70717
70979
|
}
|
|
70718
70980
|
|
|
70719
70981
|
// src/platform/slack/client.ts
|
|
70720
|
-
var
|
|
70982
|
+
var log33 = createLogger("slack");
|
|
70721
70983
|
|
|
70722
70984
|
class SlackClient extends BasePlatformClient {
|
|
70723
70985
|
platformId;
|
|
@@ -70725,6 +70987,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
70725
70987
|
displayName;
|
|
70726
70988
|
directChannelMode;
|
|
70727
70989
|
approvals;
|
|
70990
|
+
ackReaction;
|
|
70728
70991
|
ws = null;
|
|
70729
70992
|
botToken;
|
|
70730
70993
|
appToken;
|
|
@@ -70757,6 +71020,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
70757
71020
|
this.outboundFiles = platformConfig.outboundFiles;
|
|
70758
71021
|
this.directChannelMode = resolveDirectChannelMode(platformConfig.directChannelMode);
|
|
70759
71022
|
this.approvals = platformConfig.approvals;
|
|
71023
|
+
this.ackReaction = normalizeAckReaction(platformConfig.ackReaction, `platforms[${platformConfig.id}].ackReaction`);
|
|
70760
71024
|
}
|
|
70761
71025
|
normalizePlatformUser(slackUser) {
|
|
70762
71026
|
const displayName = slackUser.profile?.display_name || slackUser.profile?.real_name || slackUser.real_name || slackUser.name;
|
|
@@ -70796,13 +71060,13 @@ class SlackClient extends BasePlatformClient {
|
|
|
70796
71060
|
const now = Date.now();
|
|
70797
71061
|
if (now < this.rateLimitRetryAfter) {
|
|
70798
71062
|
const waitTime = this.rateLimitRetryAfter - now;
|
|
70799
|
-
|
|
71063
|
+
log33.debug(`Rate limited, waiting ${waitTime}ms`);
|
|
70800
71064
|
await new Promise((resolve7) => setTimeout(resolve7, waitTime));
|
|
70801
71065
|
}
|
|
70802
71066
|
this.rateLimitDelay = 0;
|
|
70803
71067
|
}
|
|
70804
71068
|
const url = `${this.apiUrl}/${endpoint}`;
|
|
70805
|
-
|
|
71069
|
+
log33.debug(`API ${method} ${endpoint}`);
|
|
70806
71070
|
const headers = {
|
|
70807
71071
|
Authorization: `Bearer ${this.botToken}`,
|
|
70808
71072
|
"Content-Type": "application/json; charset=utf-8"
|
|
@@ -70814,25 +71078,25 @@ class SlackClient extends BasePlatformClient {
|
|
|
70814
71078
|
});
|
|
70815
71079
|
if (response.status === 429) {
|
|
70816
71080
|
if (retryCount >= this.MAX_RATE_LIMIT_RETRIES) {
|
|
70817
|
-
|
|
71081
|
+
log33.error(`Rate limit max retries (${this.MAX_RATE_LIMIT_RETRIES}) exceeded for ${endpoint}`);
|
|
70818
71082
|
throw new Error(`Slack API rate limit exceeded after ${this.MAX_RATE_LIMIT_RETRIES} retries`);
|
|
70819
71083
|
}
|
|
70820
71084
|
const retryAfter = parseInt(response.headers.get("Retry-After") || "5", 10);
|
|
70821
71085
|
this.rateLimitDelay = retryAfter * 1000;
|
|
70822
71086
|
this.rateLimitRetryAfter = Date.now() + this.rateLimitDelay;
|
|
70823
|
-
|
|
71087
|
+
log33.warn(`Rate limited by Slack, retrying after ${retryAfter}s (attempt ${retryCount + 1}/${this.MAX_RATE_LIMIT_RETRIES})`);
|
|
70824
71088
|
await new Promise((resolve7) => setTimeout(resolve7, this.rateLimitDelay));
|
|
70825
71089
|
return this.api(method, endpoint, body, retryCount + 1);
|
|
70826
71090
|
}
|
|
70827
71091
|
if (!response.ok) {
|
|
70828
71092
|
const text = await response.text();
|
|
70829
|
-
|
|
71093
|
+
log33.warn(`API ${method} ${endpoint} failed: ${response.status} ${text.substring(0, 100)}`);
|
|
70830
71094
|
throw new Error(`Slack API error ${response.status}: ${text}`);
|
|
70831
71095
|
}
|
|
70832
71096
|
const data = await response.json();
|
|
70833
71097
|
if (!data.ok) {
|
|
70834
71098
|
if (!expectedErrors.includes(data.error || "")) {
|
|
70835
|
-
|
|
71099
|
+
log33.warn(`API ${method} ${endpoint} error: ${data.error}`);
|
|
70836
71100
|
}
|
|
70837
71101
|
throw new Error(`Slack API error: ${data.error}`);
|
|
70838
71102
|
}
|
|
@@ -70840,7 +71104,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
70840
71104
|
}
|
|
70841
71105
|
async appApi(method, endpoint, body) {
|
|
70842
71106
|
const url = `${this.apiUrl}/${endpoint}`;
|
|
70843
|
-
|
|
71107
|
+
log33.debug(`App API ${method} ${endpoint}`);
|
|
70844
71108
|
const headers = {
|
|
70845
71109
|
Authorization: `Bearer ${this.appToken}`,
|
|
70846
71110
|
"Content-Type": "application/json; charset=utf-8"
|
|
@@ -70903,7 +71167,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
70903
71167
|
this.onConnectionEstablished();
|
|
70904
71168
|
if (this.isReconnecting && this.lastProcessedTs) {
|
|
70905
71169
|
this.recoverMissedMessages().catch((err) => {
|
|
70906
|
-
|
|
71170
|
+
log33.warn(`Failed to recover missed messages: ${err}`);
|
|
70907
71171
|
});
|
|
70908
71172
|
}
|
|
70909
71173
|
doResolve();
|
|
@@ -71000,7 +71264,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
71000
71264
|
this.emit("channel_post", post2, user);
|
|
71001
71265
|
}
|
|
71002
71266
|
}).catch((err) => {
|
|
71003
|
-
|
|
71267
|
+
log33.warn(`Failed to get user for message event: ${err}`);
|
|
71004
71268
|
this.emit("message", post2, null);
|
|
71005
71269
|
});
|
|
71006
71270
|
}
|
|
@@ -71020,7 +71284,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
71020
71284
|
this.getUser(event.user || "").then((user) => {
|
|
71021
71285
|
this.emit("reaction", reaction, user);
|
|
71022
71286
|
}).catch((err) => {
|
|
71023
|
-
|
|
71287
|
+
log33.warn(`Failed to get user for reaction event: ${err}`);
|
|
71024
71288
|
this.emit("reaction", reaction, null);
|
|
71025
71289
|
});
|
|
71026
71290
|
}
|
|
@@ -71040,7 +71304,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
71040
71304
|
this.getUser(event.user || "").then((user) => {
|
|
71041
71305
|
this.emit("reaction_removed", reaction, user);
|
|
71042
71306
|
}).catch((err) => {
|
|
71043
|
-
|
|
71307
|
+
log33.warn(`Failed to get user for reaction_removed event: ${err}`);
|
|
71044
71308
|
this.emit("reaction_removed", reaction, null);
|
|
71045
71309
|
});
|
|
71046
71310
|
}
|
|
@@ -71077,15 +71341,15 @@ class SlackClient extends BasePlatformClient {
|
|
|
71077
71341
|
if (!this.lastProcessedTs) {
|
|
71078
71342
|
return;
|
|
71079
71343
|
}
|
|
71080
|
-
|
|
71344
|
+
log33.info(`Recovering missed messages after ts ${this.lastProcessedTs}...`);
|
|
71081
71345
|
try {
|
|
71082
71346
|
const response = await this.api("GET", `conversations.history?channel=${this.channelId}&oldest=${this.lastProcessedTs}&inclusive=false&limit=100`);
|
|
71083
71347
|
const messages = response.messages || [];
|
|
71084
71348
|
if (messages.length === 0) {
|
|
71085
|
-
|
|
71349
|
+
log33.info("No missed messages to recover");
|
|
71086
71350
|
return;
|
|
71087
71351
|
}
|
|
71088
|
-
|
|
71352
|
+
log33.info(`Recovered ${messages.length} missed message(s)`);
|
|
71089
71353
|
const sortedMessages = messages.sort((a, b) => parseFloat(a.ts) - parseFloat(b.ts));
|
|
71090
71354
|
for (const message of sortedMessages) {
|
|
71091
71355
|
if (message.user === this.botUserId || message.bot_id) {
|
|
@@ -71100,7 +71364,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
71100
71364
|
}
|
|
71101
71365
|
}
|
|
71102
71366
|
} catch (err) {
|
|
71103
|
-
|
|
71367
|
+
log33.warn(`Failed to recover missed messages: ${err}`);
|
|
71104
71368
|
}
|
|
71105
71369
|
}
|
|
71106
71370
|
async fetchBotUser() {
|
|
@@ -71124,17 +71388,17 @@ class SlackClient extends BasePlatformClient {
|
|
|
71124
71388
|
}
|
|
71125
71389
|
const cached = this.userCache.get(userId);
|
|
71126
71390
|
if (cached) {
|
|
71127
|
-
|
|
71391
|
+
log33.debug(`User ${userId} found in cache: @${cached.name}`);
|
|
71128
71392
|
return this.normalizePlatformUser(cached);
|
|
71129
71393
|
}
|
|
71130
71394
|
try {
|
|
71131
71395
|
const response = await this.api("GET", `users.info?user=${userId}`);
|
|
71132
71396
|
this.userCache.set(userId, response.user);
|
|
71133
71397
|
this.usernameToIdCache.set(response.user.name, userId);
|
|
71134
|
-
|
|
71398
|
+
log33.debug(`User ${userId} fetched: @${response.user.name}`);
|
|
71135
71399
|
return this.normalizePlatformUser(response.user);
|
|
71136
71400
|
} catch (err) {
|
|
71137
|
-
|
|
71401
|
+
log33.warn(`Failed to get user ${userId}: ${err}`);
|
|
71138
71402
|
return null;
|
|
71139
71403
|
}
|
|
71140
71404
|
}
|
|
@@ -71144,7 +71408,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
71144
71408
|
return this.getUser(cachedId);
|
|
71145
71409
|
}
|
|
71146
71410
|
try {
|
|
71147
|
-
|
|
71411
|
+
log33.debug(`Looking up user by username: @${username}`);
|
|
71148
71412
|
let cursor;
|
|
71149
71413
|
do {
|
|
71150
71414
|
const params = cursor ? `cursor=${cursor}&limit=200` : "limit=200";
|
|
@@ -71153,16 +71417,16 @@ class SlackClient extends BasePlatformClient {
|
|
|
71153
71417
|
this.userCache.set(user.id, user);
|
|
71154
71418
|
this.usernameToIdCache.set(user.name, user.id);
|
|
71155
71419
|
if (user.name === username) {
|
|
71156
|
-
|
|
71420
|
+
log33.debug(`User @${username} found: ${user.id}`);
|
|
71157
71421
|
return this.normalizePlatformUser(user);
|
|
71158
71422
|
}
|
|
71159
71423
|
}
|
|
71160
71424
|
cursor = response.response_metadata?.next_cursor;
|
|
71161
71425
|
} while (cursor);
|
|
71162
|
-
|
|
71426
|
+
log33.warn(`User @${username} not found`);
|
|
71163
71427
|
return null;
|
|
71164
71428
|
} catch (err) {
|
|
71165
|
-
|
|
71429
|
+
log33.warn(`Failed to lookup user @${username}: ${err}`);
|
|
71166
71430
|
return null;
|
|
71167
71431
|
}
|
|
71168
71432
|
}
|
|
@@ -71250,19 +71514,19 @@ class SlackClient extends BasePlatformClient {
|
|
|
71250
71514
|
}
|
|
71251
71515
|
return null;
|
|
71252
71516
|
} catch (err) {
|
|
71253
|
-
|
|
71517
|
+
log33.debug(`Post ${postId.substring(0, 12)} not found: ${err}`);
|
|
71254
71518
|
return null;
|
|
71255
71519
|
}
|
|
71256
71520
|
}
|
|
71257
71521
|
async deletePost(postId) {
|
|
71258
|
-
|
|
71522
|
+
log33.debug(`Deleting post ${postId.substring(0, 12)}`);
|
|
71259
71523
|
await this.api("POST", "chat.delete", {
|
|
71260
71524
|
channel: this.channelId,
|
|
71261
71525
|
ts: postId
|
|
71262
71526
|
});
|
|
71263
71527
|
}
|
|
71264
71528
|
async pinPost(postId) {
|
|
71265
|
-
|
|
71529
|
+
log33.debug(`Pinning post ${postId.substring(0, 12)}`);
|
|
71266
71530
|
try {
|
|
71267
71531
|
await this.api("POST", "pins.add", {
|
|
71268
71532
|
channel: this.channelId,
|
|
@@ -71270,14 +71534,14 @@ class SlackClient extends BasePlatformClient {
|
|
|
71270
71534
|
}, 0, ["already_pinned"]);
|
|
71271
71535
|
} catch (err) {
|
|
71272
71536
|
if (err instanceof Error && err.message.includes("already_pinned")) {
|
|
71273
|
-
|
|
71537
|
+
log33.debug(`Post ${postId.substring(0, 12)} already pinned`);
|
|
71274
71538
|
return;
|
|
71275
71539
|
}
|
|
71276
71540
|
throw err;
|
|
71277
71541
|
}
|
|
71278
71542
|
}
|
|
71279
71543
|
async unpinPost(postId) {
|
|
71280
|
-
|
|
71544
|
+
log33.debug(`Unpinning post ${postId.substring(0, 12)}`);
|
|
71281
71545
|
try {
|
|
71282
71546
|
await this.api("POST", "pins.remove", {
|
|
71283
71547
|
channel: this.channelId,
|
|
@@ -71285,7 +71549,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
71285
71549
|
}, 0, ["no_pin"]);
|
|
71286
71550
|
} catch (err) {
|
|
71287
71551
|
if (err instanceof Error && err.message.includes("no_pin")) {
|
|
71288
|
-
|
|
71552
|
+
log33.debug(`Post ${postId.substring(0, 12)} was not pinned`);
|
|
71289
71553
|
return;
|
|
71290
71554
|
}
|
|
71291
71555
|
throw err;
|
|
@@ -71303,7 +71567,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
71303
71567
|
if (message.length <= maxLength) {
|
|
71304
71568
|
return message;
|
|
71305
71569
|
}
|
|
71306
|
-
|
|
71570
|
+
log33.warn(`Truncating message from ${message.length} to ~${maxLength} chars`);
|
|
71307
71571
|
return truncateMessageSafely(message, maxLength, "_... (truncated)_");
|
|
71308
71572
|
}
|
|
71309
71573
|
async getThreadHistory(threadId, options) {
|
|
@@ -71330,13 +71594,13 @@ class SlackClient extends BasePlatformClient {
|
|
|
71330
71594
|
}
|
|
71331
71595
|
return messages;
|
|
71332
71596
|
} catch (err) {
|
|
71333
|
-
|
|
71597
|
+
log33.warn(`Failed to get thread history for ${threadId}: ${err}`);
|
|
71334
71598
|
return [];
|
|
71335
71599
|
}
|
|
71336
71600
|
}
|
|
71337
71601
|
async addReaction(postId, emojiName) {
|
|
71338
71602
|
const name = getEmojiName(emojiName);
|
|
71339
|
-
|
|
71603
|
+
log33.debug(`Adding reaction :${name}: to post ${postId.substring(0, 12)}`);
|
|
71340
71604
|
await this.api("POST", "reactions.add", {
|
|
71341
71605
|
channel: this.channelId,
|
|
71342
71606
|
timestamp: postId,
|
|
@@ -71345,7 +71609,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
71345
71609
|
}
|
|
71346
71610
|
async removeReaction(postId, emojiName) {
|
|
71347
71611
|
const name = getEmojiName(emojiName);
|
|
71348
|
-
|
|
71612
|
+
log33.debug(`Removing reaction :${name}: from post ${postId.substring(0, 12)}`);
|
|
71349
71613
|
await this.api("POST", "reactions.remove", {
|
|
71350
71614
|
channel: this.channelId,
|
|
71351
71615
|
timestamp: postId,
|
|
@@ -71371,7 +71635,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
71371
71635
|
}
|
|
71372
71636
|
sendTyping(_threadId) {}
|
|
71373
71637
|
async downloadFile(fileId) {
|
|
71374
|
-
|
|
71638
|
+
log33.debug(`Downloading file ${fileId}`);
|
|
71375
71639
|
const fileInfo = await this.api("GET", `files.info?file=${fileId}`);
|
|
71376
71640
|
const downloadUrl = fileInfo.file.url_private_download || fileInfo.file.url_private;
|
|
71377
71641
|
if (!downloadUrl) {
|
|
@@ -71383,11 +71647,11 @@ class SlackClient extends BasePlatformClient {
|
|
|
71383
71647
|
}
|
|
71384
71648
|
});
|
|
71385
71649
|
if (!response.ok) {
|
|
71386
|
-
|
|
71650
|
+
log33.warn(`Failed to download file ${fileId}: ${response.status}`);
|
|
71387
71651
|
throw new Error(`Failed to download file ${fileId}: ${response.status}`);
|
|
71388
71652
|
}
|
|
71389
71653
|
const arrayBuffer = await response.arrayBuffer();
|
|
71390
|
-
|
|
71654
|
+
log33.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
|
|
71391
71655
|
return Buffer.from(arrayBuffer);
|
|
71392
71656
|
}
|
|
71393
71657
|
async getFileInfo(fileId) {
|
|
@@ -72188,13 +72452,13 @@ import { EventEmitter as EventEmitter4 } from "events";
|
|
|
72188
72452
|
|
|
72189
72453
|
// src/persistence/session-store.ts
|
|
72190
72454
|
init_logger();
|
|
72191
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
72192
|
-
import { homedir as
|
|
72193
|
-
import { join as
|
|
72194
|
-
var
|
|
72455
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8, renameSync as renameSync4, chmodSync as chmodSync7 } from "fs";
|
|
72456
|
+
import { homedir as homedir8 } from "os";
|
|
72457
|
+
import { join as join13 } from "path";
|
|
72458
|
+
var log34 = createLogger("persist");
|
|
72195
72459
|
var STORE_VERSION3 = 2;
|
|
72196
|
-
var DEFAULT_CONFIG_DIR3 =
|
|
72197
|
-
var DEFAULT_SESSIONS_FILE =
|
|
72460
|
+
var DEFAULT_CONFIG_DIR3 = join13(homedir8(), ".config", "claude-threads");
|
|
72461
|
+
var DEFAULT_SESSIONS_FILE = join13(DEFAULT_CONFIG_DIR3, "sessions.json");
|
|
72198
72462
|
|
|
72199
72463
|
class SessionStore {
|
|
72200
72464
|
sessionsFile;
|
|
@@ -72204,25 +72468,25 @@ class SessionStore {
|
|
|
72204
72468
|
const effectivePath = sessionsPath ?? envPath;
|
|
72205
72469
|
if (effectivePath) {
|
|
72206
72470
|
this.sessionsFile = effectivePath;
|
|
72207
|
-
this.configDir =
|
|
72471
|
+
this.configDir = join13(effectivePath, "..");
|
|
72208
72472
|
} else {
|
|
72209
72473
|
this.sessionsFile = DEFAULT_SESSIONS_FILE;
|
|
72210
72474
|
this.configDir = DEFAULT_CONFIG_DIR3;
|
|
72211
72475
|
}
|
|
72212
72476
|
if (!existsSync13(this.configDir)) {
|
|
72213
|
-
|
|
72477
|
+
mkdirSync7(this.configDir, { recursive: true });
|
|
72214
72478
|
}
|
|
72215
72479
|
}
|
|
72216
72480
|
load() {
|
|
72217
72481
|
const sessions = new Map;
|
|
72218
72482
|
if (!existsSync13(this.sessionsFile)) {
|
|
72219
|
-
|
|
72483
|
+
log34.debug("No sessions file found");
|
|
72220
72484
|
return sessions;
|
|
72221
72485
|
}
|
|
72222
72486
|
try {
|
|
72223
72487
|
const data = this.loadRaw();
|
|
72224
72488
|
if (data.version === 1) {
|
|
72225
|
-
|
|
72489
|
+
log34.info("Migrating sessions from v1 to v2 (adding platformId)");
|
|
72226
72490
|
const newSessions = {};
|
|
72227
72491
|
for (const [_oldKey, session] of Object.entries(data.sessions)) {
|
|
72228
72492
|
const v1Session = session;
|
|
@@ -72236,7 +72500,7 @@ class SessionStore {
|
|
|
72236
72500
|
data.version = 2;
|
|
72237
72501
|
this.writeAtomic(data);
|
|
72238
72502
|
} else if (data.version !== STORE_VERSION3) {
|
|
72239
|
-
|
|
72503
|
+
log34.warn(`Sessions file version ${data.version} not supported, starting fresh`);
|
|
72240
72504
|
return sessions;
|
|
72241
72505
|
}
|
|
72242
72506
|
for (const session of Object.values(data.sessions)) {
|
|
@@ -72245,9 +72509,9 @@ class SessionStore {
|
|
|
72245
72509
|
const sessionId = `${session.platformId}:${session.threadId}`;
|
|
72246
72510
|
sessions.set(sessionId, session);
|
|
72247
72511
|
}
|
|
72248
|
-
|
|
72512
|
+
log34.debug(`Loaded ${sessions.size} active session(s)`);
|
|
72249
72513
|
} catch (err) {
|
|
72250
|
-
|
|
72514
|
+
log34.error(`Failed to load sessions: ${err}`);
|
|
72251
72515
|
}
|
|
72252
72516
|
return sessions;
|
|
72253
72517
|
}
|
|
@@ -72256,7 +72520,7 @@ class SessionStore {
|
|
|
72256
72520
|
data.sessions[sessionId] = session;
|
|
72257
72521
|
this.writeAtomic(data);
|
|
72258
72522
|
const shortId = sessionId.substring(0, 20);
|
|
72259
|
-
|
|
72523
|
+
log34.debug(`Saved session ${shortId}...`);
|
|
72260
72524
|
}
|
|
72261
72525
|
remove(sessionId) {
|
|
72262
72526
|
const data = this.loadRaw();
|
|
@@ -72264,7 +72528,7 @@ class SessionStore {
|
|
|
72264
72528
|
delete data.sessions[sessionId];
|
|
72265
72529
|
this.writeAtomic(data);
|
|
72266
72530
|
const shortId = sessionId.substring(0, 20);
|
|
72267
|
-
|
|
72531
|
+
log34.debug(`Removed session ${shortId}...`);
|
|
72268
72532
|
}
|
|
72269
72533
|
}
|
|
72270
72534
|
softDelete(sessionId) {
|
|
@@ -72273,7 +72537,7 @@ class SessionStore {
|
|
|
72273
72537
|
data.sessions[sessionId].cleanedAt = new Date().toISOString();
|
|
72274
72538
|
this.writeAtomic(data);
|
|
72275
72539
|
const shortId = sessionId.substring(0, 20);
|
|
72276
|
-
|
|
72540
|
+
log34.debug(`Soft-deleted session ${shortId}...`);
|
|
72277
72541
|
}
|
|
72278
72542
|
}
|
|
72279
72543
|
cleanStale(maxAgeMs) {
|
|
@@ -72291,7 +72555,7 @@ class SessionStore {
|
|
|
72291
72555
|
}
|
|
72292
72556
|
if (staleIds.length > 0) {
|
|
72293
72557
|
this.writeAtomic(data);
|
|
72294
|
-
|
|
72558
|
+
log34.debug(`Soft-deleted ${staleIds.length} stale session(s)`);
|
|
72295
72559
|
}
|
|
72296
72560
|
return staleIds;
|
|
72297
72561
|
}
|
|
@@ -72310,7 +72574,7 @@ class SessionStore {
|
|
|
72310
72574
|
}
|
|
72311
72575
|
if (removedCount > 0) {
|
|
72312
72576
|
this.writeAtomic(data);
|
|
72313
|
-
|
|
72577
|
+
log34.debug(`Permanently removed ${removedCount} old session(s) from history`);
|
|
72314
72578
|
}
|
|
72315
72579
|
return removedCount;
|
|
72316
72580
|
}
|
|
@@ -72337,7 +72601,7 @@ class SessionStore {
|
|
|
72337
72601
|
clear() {
|
|
72338
72602
|
const data = this.loadRaw();
|
|
72339
72603
|
this.writeAtomic({ version: STORE_VERSION3, sessions: {}, stickyPostIds: data.stickyPostIds });
|
|
72340
|
-
|
|
72604
|
+
log34.debug("Cleared all sessions");
|
|
72341
72605
|
}
|
|
72342
72606
|
saveStickyPostId(platformId, postId) {
|
|
72343
72607
|
const data = this.loadRaw();
|
|
@@ -72346,7 +72610,7 @@ class SessionStore {
|
|
|
72346
72610
|
}
|
|
72347
72611
|
data.stickyPostIds[platformId] = postId;
|
|
72348
72612
|
this.writeAtomic(data);
|
|
72349
|
-
|
|
72613
|
+
log34.debug(`Saved sticky post ID for ${platformId}: ${postId.substring(0, 8)}...`);
|
|
72350
72614
|
}
|
|
72351
72615
|
getStickyPostIds() {
|
|
72352
72616
|
const data = this.loadRaw();
|
|
@@ -72357,7 +72621,7 @@ class SessionStore {
|
|
|
72357
72621
|
if (data.stickyPostIds && data.stickyPostIds[platformId]) {
|
|
72358
72622
|
delete data.stickyPostIds[platformId];
|
|
72359
72623
|
this.writeAtomic(data);
|
|
72360
|
-
|
|
72624
|
+
log34.debug(`Removed sticky post ID for ${platformId}`);
|
|
72361
72625
|
}
|
|
72362
72626
|
}
|
|
72363
72627
|
getPlatformEnabledState() {
|
|
@@ -72375,7 +72639,7 @@ class SessionStore {
|
|
|
72375
72639
|
}
|
|
72376
72640
|
data.platformEnabledState[platformId] = enabled;
|
|
72377
72641
|
this.writeAtomic(data);
|
|
72378
|
-
|
|
72642
|
+
log34.debug(`Set platform ${platformId} enabled state to ${enabled}`);
|
|
72379
72643
|
}
|
|
72380
72644
|
findByThread(platformId, threadId) {
|
|
72381
72645
|
const sessionId = `${platformId}:${threadId}`;
|
|
@@ -72439,13 +72703,13 @@ class SessionStore {
|
|
|
72439
72703
|
const tempFile = `${this.sessionsFile}.tmp`;
|
|
72440
72704
|
writeFileSync8(tempFile, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
72441
72705
|
renameSync4(tempFile, this.sessionsFile);
|
|
72442
|
-
|
|
72706
|
+
chmodSync7(this.sessionsFile, 384);
|
|
72443
72707
|
}
|
|
72444
72708
|
}
|
|
72445
72709
|
|
|
72446
72710
|
// src/routines/scheduler.ts
|
|
72447
72711
|
init_logger();
|
|
72448
|
-
var
|
|
72712
|
+
var log35 = createLogger("routines");
|
|
72449
72713
|
var DEFAULT_INTERVAL_MS2 = 60 * 1000;
|
|
72450
72714
|
var FIRE_WINDOW_MS = 5 * 60 * 1000;
|
|
72451
72715
|
var WEEKDAY_TO_ISO = {
|
|
@@ -72533,11 +72797,11 @@ class RoutineScheduler {
|
|
|
72533
72797
|
if (this.timer)
|
|
72534
72798
|
return;
|
|
72535
72799
|
const safeTick = () => this.tick(new Date).catch((err) => {
|
|
72536
|
-
|
|
72800
|
+
log35.error(`Routine scheduler tick failed: ${err.message}`);
|
|
72537
72801
|
});
|
|
72538
72802
|
this.timer = setInterval(safeTick, this.intervalMs);
|
|
72539
72803
|
safeTick();
|
|
72540
|
-
|
|
72804
|
+
log35.debug(`Routine scheduler started (interval: ${this.intervalMs / 1000}s)`);
|
|
72541
72805
|
}
|
|
72542
72806
|
stop() {
|
|
72543
72807
|
if (this.timer) {
|
|
@@ -72568,7 +72832,7 @@ class RoutineScheduler {
|
|
|
72568
72832
|
try {
|
|
72569
72833
|
status = await this.opts.fireRoutine(platformId, routine);
|
|
72570
72834
|
} catch (err) {
|
|
72571
|
-
|
|
72835
|
+
log35.warn(`Routine "${routine.name}" (${platformId}) failed: ${err.message}`);
|
|
72572
72836
|
status = "failed";
|
|
72573
72837
|
}
|
|
72574
72838
|
try {
|
|
@@ -72592,7 +72856,7 @@ class RoutineScheduler {
|
|
|
72592
72856
|
}
|
|
72593
72857
|
}
|
|
72594
72858
|
} catch (err) {
|
|
72595
|
-
|
|
72859
|
+
log35.error(`Routine "${routine.name}" (${platformId}) bookkeeping failed: ${err.message}`);
|
|
72596
72860
|
}
|
|
72597
72861
|
return status;
|
|
72598
72862
|
}
|
|
@@ -72600,20 +72864,20 @@ class RoutineScheduler {
|
|
|
72600
72864
|
|
|
72601
72865
|
// src/routines/runner.ts
|
|
72602
72866
|
init_logger();
|
|
72603
|
-
var
|
|
72867
|
+
var log36 = createLogger("routines");
|
|
72604
72868
|
async function fireRoutine(routine, platformId, ctx) {
|
|
72605
72869
|
const platforms = ctx.state.platforms;
|
|
72606
72870
|
const platform = platforms.get(platformId);
|
|
72607
72871
|
if (!platform) {
|
|
72608
|
-
|
|
72872
|
+
log36.debug(`Routine "${routine.name}": platform ${platformId} not registered — skipping`);
|
|
72609
72873
|
return "skipped";
|
|
72610
72874
|
}
|
|
72611
72875
|
if (!isAuthorizedForSession({ username: routine.createdBy, platform, sessionAllowedUsers: undefined })) {
|
|
72612
|
-
|
|
72876
|
+
log36.warn(`Routine "${routine.name}": creator @${routine.createdBy} no longer authorized on ${platformId}`);
|
|
72613
72877
|
return "unauthorized";
|
|
72614
72878
|
}
|
|
72615
72879
|
if (ctx.state.sessions.size >= ctx.config.maxSessions) {
|
|
72616
|
-
|
|
72880
|
+
log36.debug(`Routine "${routine.name}": at MAX_SESSIONS — skipping this tick`);
|
|
72617
72881
|
return "skipped";
|
|
72618
72882
|
}
|
|
72619
72883
|
const formatter = platform.getFormatter();
|
|
@@ -72626,7 +72890,7 @@ ${routine.prompt}`,
|
|
|
72626
72890
|
skipWorktreePrompt: true
|
|
72627
72891
|
}, routine.createdBy, undefined, rootPost.id, platformId, ctx);
|
|
72628
72892
|
if (!ctx.state.sessions.has(ctx.ops.getSessionId(platformId, rootPost.id))) {
|
|
72629
|
-
|
|
72893
|
+
log36.debug(`Routine "${routine.name}": startSession declined to start a session — skipping this tick`);
|
|
72630
72894
|
return "skipped";
|
|
72631
72895
|
}
|
|
72632
72896
|
return "ok";
|
|
@@ -72638,7 +72902,7 @@ init_logger();
|
|
|
72638
72902
|
// src/claude/usage-probe.ts
|
|
72639
72903
|
init_spawn();
|
|
72640
72904
|
init_logger();
|
|
72641
|
-
var
|
|
72905
|
+
var log37 = createLogger("usage-probe");
|
|
72642
72906
|
var DEFAULT_USAGE_PROBE_TIMEOUT_MS = 30000;
|
|
72643
72907
|
function parseUsageOutput(text) {
|
|
72644
72908
|
if (!text)
|
|
@@ -72691,12 +72955,12 @@ async function probeAccountUsage(account, opts = {}) {
|
|
|
72691
72955
|
stdio: ["ignore", "pipe", "pipe"]
|
|
72692
72956
|
});
|
|
72693
72957
|
} catch (err) {
|
|
72694
|
-
|
|
72958
|
+
log37.warn(`Failed to spawn /usage probe for "${account.id}": ${err}`);
|
|
72695
72959
|
resolve7(null);
|
|
72696
72960
|
return;
|
|
72697
72961
|
}
|
|
72698
72962
|
const timer = setTimeout(() => {
|
|
72699
|
-
|
|
72963
|
+
log37.warn(`/usage probe for "${account.id}" timed out after ${timeoutMs}ms`);
|
|
72700
72964
|
try {
|
|
72701
72965
|
child.kill("SIGKILL");
|
|
72702
72966
|
} catch {}
|
|
@@ -72708,13 +72972,13 @@ async function probeAccountUsage(account, opts = {}) {
|
|
|
72708
72972
|
});
|
|
72709
72973
|
child.stderr?.on("data", () => {});
|
|
72710
72974
|
child.on("error", (err) => {
|
|
72711
|
-
|
|
72975
|
+
log37.warn(`/usage probe for "${account.id}" errored: ${err}`);
|
|
72712
72976
|
finish(null);
|
|
72713
72977
|
});
|
|
72714
72978
|
child.on("close", () => {
|
|
72715
72979
|
const usage = extractUsage(stdout);
|
|
72716
72980
|
if (!usage) {
|
|
72717
|
-
|
|
72981
|
+
log37.debug(`/usage probe for "${account.id}" returned no parseable usage`);
|
|
72718
72982
|
}
|
|
72719
72983
|
finish(usage);
|
|
72720
72984
|
});
|
|
@@ -72735,7 +72999,7 @@ function extractUsage(stdout) {
|
|
|
72735
72999
|
}
|
|
72736
73000
|
|
|
72737
73001
|
// src/claude/account-pool.ts
|
|
72738
|
-
var
|
|
73002
|
+
var log38 = createLogger("account-pool");
|
|
72739
73003
|
var ACTIVE_SESSION_LOAD_PENALTY = 5;
|
|
72740
73004
|
function hashThreadId(threadId) {
|
|
72741
73005
|
let h = 2166136261;
|
|
@@ -72758,11 +73022,11 @@ class AccountPool {
|
|
|
72758
73022
|
this.accounts = (accounts ?? []).filter((acc) => {
|
|
72759
73023
|
const hasAuth = !!acc.home || !!acc.apiKey;
|
|
72760
73024
|
if (!hasAuth) {
|
|
72761
|
-
|
|
73025
|
+
log38.warn(`Claude account ${acc.id} has neither home nor apiKey — ignoring`);
|
|
72762
73026
|
return false;
|
|
72763
73027
|
}
|
|
72764
73028
|
if (acc.home && acc.apiKey) {
|
|
72765
|
-
|
|
73029
|
+
log38.warn(`Claude account ${acc.id} has both home and apiKey set — must choose one; ignoring`);
|
|
72766
73030
|
return false;
|
|
72767
73031
|
}
|
|
72768
73032
|
return true;
|
|
@@ -72792,7 +73056,7 @@ class AccountPool {
|
|
|
72792
73056
|
this.incrementActive(preferred.id);
|
|
72793
73057
|
return preferred;
|
|
72794
73058
|
}
|
|
72795
|
-
|
|
73059
|
+
log38.warn(`Preferred account "${preferredId}" not in pool — falling back to usage balancing`);
|
|
72796
73060
|
}
|
|
72797
73061
|
const now = Date.now();
|
|
72798
73062
|
const n = this.accounts.length;
|
|
@@ -72806,7 +73070,7 @@ class AccountPool {
|
|
|
72806
73070
|
}
|
|
72807
73071
|
const chosen = this.selectLeastLoaded(now);
|
|
72808
73072
|
if (!chosen) {
|
|
72809
|
-
|
|
73073
|
+
log38.warn(`All ${n} accounts are in rate-limit cooldown`);
|
|
72810
73074
|
return null;
|
|
72811
73075
|
}
|
|
72812
73076
|
this.incrementActive(chosen.id);
|
|
@@ -72853,19 +73117,19 @@ class AccountPool {
|
|
|
72853
73117
|
return;
|
|
72854
73118
|
this.usage.set(accountId, usage);
|
|
72855
73119
|
if (usage) {
|
|
72856
|
-
|
|
73120
|
+
log38.debug(`Account "${accountId}" usage: ${usageLoadScore(usage)}% (load score)`);
|
|
72857
73121
|
}
|
|
72858
73122
|
}
|
|
72859
73123
|
markCooling(accountId, untilEpochMs) {
|
|
72860
73124
|
if (!this.byId.has(accountId)) {
|
|
72861
|
-
|
|
73125
|
+
log38.warn(`markCooling called for unknown account "${accountId}"`);
|
|
72862
73126
|
return;
|
|
72863
73127
|
}
|
|
72864
73128
|
const existing = this.coolingUntil.get(accountId) ?? 0;
|
|
72865
73129
|
if (untilEpochMs > existing) {
|
|
72866
73130
|
this.coolingUntil.set(accountId, untilEpochMs);
|
|
72867
73131
|
const minutes = Math.ceil((untilEpochMs - Date.now()) / 60000);
|
|
72868
|
-
|
|
73132
|
+
log38.info(`Account "${accountId}" cooling for ~${minutes}min`);
|
|
72869
73133
|
}
|
|
72870
73134
|
}
|
|
72871
73135
|
get(accountId) {
|
|
@@ -72894,9 +73158,9 @@ class AccountPool {
|
|
|
72894
73158
|
init_logger();
|
|
72895
73159
|
import { existsSync as existsSync14 } from "fs";
|
|
72896
73160
|
import { readdir, rm as rm3 } from "fs/promises";
|
|
72897
|
-
import { join as
|
|
73161
|
+
import { join as join14 } from "path";
|
|
72898
73162
|
init_worktree();
|
|
72899
|
-
var
|
|
73163
|
+
var log39 = createLogger("cleanup");
|
|
72900
73164
|
var DEFAULT_CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
|
|
72901
73165
|
var MAX_WORKTREE_AGE_MS = 24 * 60 * 60 * 1000;
|
|
72902
73166
|
|
|
@@ -72919,17 +73183,17 @@ class CleanupScheduler {
|
|
|
72919
73183
|
}
|
|
72920
73184
|
start() {
|
|
72921
73185
|
if (this.isRunning) {
|
|
72922
|
-
|
|
73186
|
+
log39.debug("Cleanup scheduler already running");
|
|
72923
73187
|
return;
|
|
72924
73188
|
}
|
|
72925
73189
|
this.isRunning = true;
|
|
72926
|
-
|
|
73190
|
+
log39.info(`Cleanup scheduler started (interval: ${Math.round(this.intervalMs / 60000)}min)`);
|
|
72927
73191
|
this.runCleanup().catch((err) => {
|
|
72928
|
-
|
|
73192
|
+
log39.warn(`Initial cleanup failed: ${err}`);
|
|
72929
73193
|
});
|
|
72930
73194
|
this.timer = setInterval(() => {
|
|
72931
73195
|
this.runCleanup().catch((err) => {
|
|
72932
|
-
|
|
73196
|
+
log39.warn(`Periodic cleanup failed: ${err}`);
|
|
72933
73197
|
});
|
|
72934
73198
|
}, this.intervalMs);
|
|
72935
73199
|
}
|
|
@@ -72939,11 +73203,11 @@ class CleanupScheduler {
|
|
|
72939
73203
|
this.timer = null;
|
|
72940
73204
|
}
|
|
72941
73205
|
this.isRunning = false;
|
|
72942
|
-
|
|
73206
|
+
log39.debug("Cleanup scheduler stopped");
|
|
72943
73207
|
}
|
|
72944
73208
|
async runCleanup() {
|
|
72945
73209
|
const startTime = Date.now();
|
|
72946
|
-
|
|
73210
|
+
log39.debug("Running background cleanup...");
|
|
72947
73211
|
const stats = {
|
|
72948
73212
|
logsDeleted: 0,
|
|
72949
73213
|
worktreesCleaned: 0,
|
|
@@ -72969,9 +73233,9 @@ class CleanupScheduler {
|
|
|
72969
73233
|
const elapsed = Date.now() - startTime;
|
|
72970
73234
|
const totalCleaned = stats.logsDeleted + stats.worktreesCleaned + stats.metadataCleaned;
|
|
72971
73235
|
if (totalCleaned > 0 || stats.errors.length > 0) {
|
|
72972
|
-
|
|
73236
|
+
log39.info(`Cleanup completed in ${elapsed}ms: ` + `${stats.logsDeleted} logs, ${stats.worktreesCleaned} worktrees, ${stats.metadataCleaned} metadata` + (stats.errors.length > 0 ? ` (${stats.errors.length} errors)` : ""));
|
|
72973
73237
|
} else {
|
|
72974
|
-
|
|
73238
|
+
log39.debug(`Cleanup completed in ${elapsed}ms (nothing to clean)`);
|
|
72975
73239
|
}
|
|
72976
73240
|
return stats;
|
|
72977
73241
|
}
|
|
@@ -72984,7 +73248,7 @@ class CleanupScheduler {
|
|
|
72984
73248
|
const deleted = cleanupOldLogs(this.logRetentionDays);
|
|
72985
73249
|
resolve7(deleted);
|
|
72986
73250
|
} catch (err) {
|
|
72987
|
-
|
|
73251
|
+
log39.warn(`Log cleanup error: ${err}`);
|
|
72988
73252
|
resolve7(0);
|
|
72989
73253
|
}
|
|
72990
73254
|
});
|
|
@@ -72993,7 +73257,7 @@ class CleanupScheduler {
|
|
|
72993
73257
|
const worktreesDir = getWorktreesDir();
|
|
72994
73258
|
const result = { cleaned: 0, metadata: 0 };
|
|
72995
73259
|
if (!existsSync14(worktreesDir)) {
|
|
72996
|
-
|
|
73260
|
+
log39.debug("No worktrees directory exists, nothing to clean");
|
|
72997
73261
|
return result;
|
|
72998
73262
|
}
|
|
72999
73263
|
const persisted = this.sessionStore.load();
|
|
@@ -73009,9 +73273,9 @@ class CleanupScheduler {
|
|
|
73009
73273
|
for (const entry of entries) {
|
|
73010
73274
|
if (!entry.isDirectory())
|
|
73011
73275
|
continue;
|
|
73012
|
-
const worktreePath =
|
|
73276
|
+
const worktreePath = join14(worktreesDir, entry.name);
|
|
73013
73277
|
if (activeWorktrees.has(worktreePath)) {
|
|
73014
|
-
|
|
73278
|
+
log39.debug(`Worktree in use by persisted session, skipping: ${entry.name}`);
|
|
73015
73279
|
continue;
|
|
73016
73280
|
}
|
|
73017
73281
|
const meta = await readWorktreeMetadata(worktreePath);
|
|
@@ -73021,7 +73285,7 @@ class CleanupScheduler {
|
|
|
73021
73285
|
const lastActivity = new Date(meta.lastActivityAt).getTime();
|
|
73022
73286
|
const age = now - lastActivity;
|
|
73023
73287
|
if (meta.sessionId && age < this.maxWorktreeAgeMs) {
|
|
73024
|
-
|
|
73288
|
+
log39.debug(`Worktree has active session (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
|
|
73025
73289
|
continue;
|
|
73026
73290
|
}
|
|
73027
73291
|
const merged = age >= this.maxWorktreeAgeMs ? await isBranchMerged(meta.repoRoot, meta.branch).catch(() => false) : false;
|
|
@@ -73032,7 +73296,7 @@ class CleanupScheduler {
|
|
|
73032
73296
|
shouldCleanup = true;
|
|
73033
73297
|
cleanupReason = `inactive for ${Math.round(age / 3600000)}h`;
|
|
73034
73298
|
} else {
|
|
73035
|
-
|
|
73299
|
+
log39.debug(`Worktree recent (${Math.round(age / 60000)}min old), skipping: ${entry.name}`);
|
|
73036
73300
|
continue;
|
|
73037
73301
|
}
|
|
73038
73302
|
} else {
|
|
@@ -73041,7 +73305,7 @@ class CleanupScheduler {
|
|
|
73041
73305
|
}
|
|
73042
73306
|
if (!shouldCleanup)
|
|
73043
73307
|
continue;
|
|
73044
|
-
|
|
73308
|
+
log39.info(`Cleaning worktree (${cleanupReason}): ${entry.name}`);
|
|
73045
73309
|
try {
|
|
73046
73310
|
if (meta?.repoRoot) {
|
|
73047
73311
|
await removeWorktree(meta.repoRoot, worktreePath);
|
|
@@ -73052,19 +73316,19 @@ class CleanupScheduler {
|
|
|
73052
73316
|
await removeWorktreeMetadata(worktreePath);
|
|
73053
73317
|
result.metadata++;
|
|
73054
73318
|
} catch (err) {
|
|
73055
|
-
|
|
73319
|
+
log39.warn(`Failed to clean orphaned worktree ${entry.name}: ${err}`);
|
|
73056
73320
|
try {
|
|
73057
73321
|
await rm3(worktreePath, { recursive: true, force: true });
|
|
73058
73322
|
result.cleaned++;
|
|
73059
73323
|
await removeWorktreeMetadata(worktreePath);
|
|
73060
73324
|
result.metadata++;
|
|
73061
73325
|
} catch (rmErr) {
|
|
73062
|
-
|
|
73326
|
+
log39.error(`Failed to force remove worktree ${entry.name}: ${rmErr}`);
|
|
73063
73327
|
}
|
|
73064
73328
|
}
|
|
73065
73329
|
}
|
|
73066
73330
|
} catch (err) {
|
|
73067
|
-
|
|
73331
|
+
log39.warn(`Failed to scan worktrees directory: ${err}`);
|
|
73068
73332
|
}
|
|
73069
73333
|
return result;
|
|
73070
73334
|
}
|
|
@@ -73072,8 +73336,8 @@ class CleanupScheduler {
|
|
|
73072
73336
|
// src/operations/plugin/handler.ts
|
|
73073
73337
|
init_spawn();
|
|
73074
73338
|
init_logger();
|
|
73075
|
-
var
|
|
73076
|
-
var sessionLog7 = createSessionLog(
|
|
73339
|
+
var log40 = createLogger("plugin");
|
|
73340
|
+
var sessionLog7 = createSessionLog(log40);
|
|
73077
73341
|
async function buildPluginRestartCliOptions(session, ctx) {
|
|
73078
73342
|
const account = session.claudeAccountId ? ctx.ops.getClaudeAccount(session.claudeAccountId) : undefined;
|
|
73079
73343
|
const memoryConfig = ctx.ops.getPlatformMemoryConfig(session.platformId);
|
|
@@ -73115,7 +73379,7 @@ async function runPluginCommand(args, cwd, timeout2 = 60000) {
|
|
|
73115
73379
|
});
|
|
73116
73380
|
proc.on("error", (err) => {
|
|
73117
73381
|
resolve7({ stdout, stderr, exitCode: 1 });
|
|
73118
|
-
|
|
73382
|
+
log40.error(`Plugin command error: ${err.message}`);
|
|
73119
73383
|
});
|
|
73120
73384
|
});
|
|
73121
73385
|
}
|
|
@@ -73137,6 +73401,14 @@ async function handlePluginInstall(session, pluginName, username, ctx) {
|
|
|
73137
73401
|
const formatter = session.platform.getFormatter();
|
|
73138
73402
|
await post(session, "info", `\uD83D\uDCE6 Installing plugin: ${formatter.formatCode(pluginName)}...`);
|
|
73139
73403
|
sessionLog7(session).info(`Installing plugin: ${pluginName} (requested by @${username})`);
|
|
73404
|
+
auditLog(session.platformId, {
|
|
73405
|
+
threadId: session.threadId,
|
|
73406
|
+
sessionId: session.sessionId,
|
|
73407
|
+
actor: username,
|
|
73408
|
+
kind: "command",
|
|
73409
|
+
tool: "plugin install",
|
|
73410
|
+
detail: pluginName
|
|
73411
|
+
});
|
|
73140
73412
|
session.threadLogger?.logCommand("plugin install", pluginName, username);
|
|
73141
73413
|
const result = await runPluginCommand(["install", pluginName], session.workingDir);
|
|
73142
73414
|
if (result.exitCode !== 0) {
|
|
@@ -73160,6 +73432,14 @@ async function handlePluginUninstall(session, pluginName, username, ctx) {
|
|
|
73160
73432
|
const formatter = session.platform.getFormatter();
|
|
73161
73433
|
await post(session, "info", `\uD83D\uDDD1️ Uninstalling plugin: ${formatter.formatCode(pluginName)}...`);
|
|
73162
73434
|
sessionLog7(session).info(`Uninstalling plugin: ${pluginName} (requested by @${username})`);
|
|
73435
|
+
auditLog(session.platformId, {
|
|
73436
|
+
threadId: session.threadId,
|
|
73437
|
+
sessionId: session.sessionId,
|
|
73438
|
+
actor: username,
|
|
73439
|
+
kind: "command",
|
|
73440
|
+
tool: "plugin uninstall",
|
|
73441
|
+
detail: pluginName
|
|
73442
|
+
});
|
|
73163
73443
|
session.threadLogger?.logCommand("plugin uninstall", pluginName, username);
|
|
73164
73444
|
const result = await runPluginCommand(["uninstall", pluginName], session.workingDir);
|
|
73165
73445
|
if (result.exitCode !== 0) {
|
|
@@ -73289,7 +73569,7 @@ class SessionRegistry {
|
|
|
73289
73569
|
// src/session/reaction-router.ts
|
|
73290
73570
|
init_emoji();
|
|
73291
73571
|
init_logger();
|
|
73292
|
-
var
|
|
73572
|
+
var log41 = createLogger("manager");
|
|
73293
73573
|
async function handleReaction(deps, platformId, postId, emojiName, username, action) {
|
|
73294
73574
|
const normalizedEmoji = normalizeEmojiName(emojiName);
|
|
73295
73575
|
if (action === "added" && isResumeEmoji(normalizedEmoji)) {
|
|
@@ -73304,7 +73584,7 @@ async function handleReaction(deps, platformId, postId, emojiName, username, act
|
|
|
73304
73584
|
return;
|
|
73305
73585
|
const ownerScoped = resolveApprovals(session.platform.approvals, isDcmThreadId(session.threadId)) === "owner";
|
|
73306
73586
|
if (!session.sessionAllowedUsers.has(username) && (ownerScoped || !session.platform.isUserAllowed(username))) {
|
|
73307
|
-
|
|
73587
|
+
log41.info(`\uD83D\uDEAB rejected reaction from unauthorized user`, {
|
|
73308
73588
|
event: "reaction.rejected",
|
|
73309
73589
|
platformId,
|
|
73310
73590
|
sessionId: session.sessionId,
|
|
@@ -73342,8 +73622,8 @@ async function tryResumeFromReaction(deps, platformId, postId, username) {
|
|
|
73342
73622
|
return false;
|
|
73343
73623
|
}
|
|
73344
73624
|
const shortId = persistedSession.threadId.substring(0, 8);
|
|
73345
|
-
|
|
73346
|
-
await resumeSession(persistedSession, deps.getContext());
|
|
73625
|
+
log41.info(`\uD83D\uDD04 Resuming session ${shortId}... via emoji reaction by @${username}`);
|
|
73626
|
+
await resumeSession(persistedSession, deps.getContext(), username);
|
|
73347
73627
|
return true;
|
|
73348
73628
|
}
|
|
73349
73629
|
async function dispatch(deps, session, postId, emojiName, username, action) {
|
|
@@ -73372,7 +73652,7 @@ async function dispatch(deps, session, postId, emojiName, username, action) {
|
|
|
73372
73652
|
}
|
|
73373
73653
|
if (session.lastError?.postId === postId && isBugReportEmoji(emojiName)) {
|
|
73374
73654
|
if (session.startedBy === username || session.platform.isUserAllowed(username) || session.sessionAllowedUsers.has(username)) {
|
|
73375
|
-
|
|
73655
|
+
log41.info(`\uD83D\uDC1B @${username} triggered bug report from error reaction`);
|
|
73376
73656
|
await reportBug(session, undefined, username, deps.getContext(), session.lastError);
|
|
73377
73657
|
return;
|
|
73378
73658
|
}
|
|
@@ -73387,7 +73667,7 @@ async function dispatch(deps, session, postId, emojiName, username, action) {
|
|
|
73387
73667
|
|
|
73388
73668
|
// src/session/manager.ts
|
|
73389
73669
|
init_logger();
|
|
73390
|
-
var
|
|
73670
|
+
var log42 = createLogger("manager");
|
|
73391
73671
|
var USAGE_PROBE_TIMEOUT_MS = 1e4;
|
|
73392
73672
|
var USAGE_REFRESH_DEADLINE_MS = 5000;
|
|
73393
73673
|
var USAGE_CACHE_TTL_MS = 15000;
|
|
@@ -73497,7 +73777,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
73497
73777
|
markNeedsBump(platformId);
|
|
73498
73778
|
this.updateStickyMessage();
|
|
73499
73779
|
});
|
|
73500
|
-
|
|
73780
|
+
log42.info(`\uD83D\uDCE1 Platform "${platformId}" registered`);
|
|
73501
73781
|
}
|
|
73502
73782
|
removePlatform(platformId) {
|
|
73503
73783
|
this.platforms.delete(platformId);
|
|
@@ -73517,7 +73797,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
73517
73797
|
if (users) {
|
|
73518
73798
|
users.add(sessionId);
|
|
73519
73799
|
}
|
|
73520
|
-
|
|
73800
|
+
log42.debug(`Registered session ${sessionId.substring(0, 20)} as worktree user for ${worktreePath}`);
|
|
73521
73801
|
}
|
|
73522
73802
|
unregisterWorktreeUser(worktreePath, sessionId) {
|
|
73523
73803
|
const users = this.worktreeUsers.get(worktreePath);
|
|
@@ -73723,7 +74003,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
73723
74003
|
try {
|
|
73724
74004
|
this.persistSessionUnsafe(session);
|
|
73725
74005
|
} catch (err) {
|
|
73726
|
-
|
|
74006
|
+
log42.error(`Failed to persist session ${session.sessionId}: ${err}`);
|
|
73727
74007
|
}
|
|
73728
74008
|
}
|
|
73729
74009
|
persistSessionUnsafe(session) {
|
|
@@ -73839,11 +74119,11 @@ class SessionManager extends EventEmitter4 {
|
|
|
73839
74119
|
}
|
|
73840
74120
|
}
|
|
73841
74121
|
if (sessionsToKill.length === 0) {
|
|
73842
|
-
|
|
74122
|
+
log42.info(`No active sessions to pause for platform ${platformId}`);
|
|
73843
74123
|
await this.updateStickyMessage();
|
|
73844
74124
|
return;
|
|
73845
74125
|
}
|
|
73846
|
-
|
|
74126
|
+
log42.info(`⏸️ Pausing ${sessionsToKill.length} session(s) for platform ${platformId}`);
|
|
73847
74127
|
for (const session of sessionsToKill) {
|
|
73848
74128
|
try {
|
|
73849
74129
|
const fmt = session.platform.getFormatter();
|
|
@@ -73859,9 +74139,9 @@ class SessionManager extends EventEmitter4 {
|
|
|
73859
74139
|
session.claude.kill();
|
|
73860
74140
|
this.registry.unregister(session.sessionId);
|
|
73861
74141
|
this.emitSessionRemove(session.sessionId);
|
|
73862
|
-
|
|
74142
|
+
log42.info(`⏸️ Paused session ${session.threadId.substring(0, 8)}`);
|
|
73863
74143
|
} catch (err) {
|
|
73864
|
-
|
|
74144
|
+
log42.warn(`Failed to pause session ${session.threadId}: ${err}`);
|
|
73865
74145
|
}
|
|
73866
74146
|
}
|
|
73867
74147
|
for (const session of sessionsToKill) {
|
|
@@ -73882,17 +74162,17 @@ class SessionManager extends EventEmitter4 {
|
|
|
73882
74162
|
sessionsToResume.push(state);
|
|
73883
74163
|
}
|
|
73884
74164
|
if (sessionsToResume.length === 0) {
|
|
73885
|
-
|
|
74165
|
+
log42.info(`No paused sessions to resume for platform ${platformId}`);
|
|
73886
74166
|
await this.updateStickyMessage();
|
|
73887
74167
|
return;
|
|
73888
74168
|
}
|
|
73889
|
-
|
|
74169
|
+
log42.info(`▶️ Resuming ${sessionsToResume.length} paused session(s) for platform ${platformId}`);
|
|
73890
74170
|
for (const state of sessionsToResume) {
|
|
73891
74171
|
try {
|
|
73892
74172
|
await resumeSession(state, this.getContext());
|
|
73893
|
-
|
|
74173
|
+
log42.info(`▶️ Resumed session ${state.threadId.substring(0, 8)}`);
|
|
73894
74174
|
} catch (err) {
|
|
73895
|
-
|
|
74175
|
+
log42.warn(`Failed to resume session ${state.threadId}: ${err}`);
|
|
73896
74176
|
}
|
|
73897
74177
|
}
|
|
73898
74178
|
await this.updateStickyMessage();
|
|
@@ -73931,14 +74211,14 @@ class SessionManager extends EventEmitter4 {
|
|
|
73931
74211
|
const sessionTimeoutMs = this.limits.sessionTimeoutMinutes * 60 * 1000;
|
|
73932
74212
|
const staleIds = this.sessionStore.cleanStale(sessionTimeoutMs * 2);
|
|
73933
74213
|
if (staleIds.length > 0) {
|
|
73934
|
-
|
|
74214
|
+
log42.info(`\uD83E\uDDF9 Soft-deleted ${staleIds.length} stale session(s) (kept for history)`);
|
|
73935
74215
|
}
|
|
73936
74216
|
const removedCount = this.sessionStore.cleanHistory();
|
|
73937
74217
|
if (removedCount > 0) {
|
|
73938
|
-
|
|
74218
|
+
log42.info(`\uD83D\uDDD1️ Permanently removed ${removedCount} old session(s) from history`);
|
|
73939
74219
|
}
|
|
73940
74220
|
const persisted = this.sessionStore.load();
|
|
73941
|
-
|
|
74221
|
+
log42.info(`\uD83D\uDCC2 Loaded ${persisted.size} session(s) from persistence`);
|
|
73942
74222
|
const excludePostIdsByPlatform = new Map;
|
|
73943
74223
|
for (const session of persisted.values()) {
|
|
73944
74224
|
const platformId = session.platformId;
|
|
@@ -73958,10 +74238,10 @@ class SessionManager extends EventEmitter4 {
|
|
|
73958
74238
|
const excludePostIds = excludePostIdsByPlatform.get(platform.platformId);
|
|
73959
74239
|
platform.getBotUser().then((botUser) => {
|
|
73960
74240
|
cleanupOldStickyMessages(platform, botUser.id, true, excludePostIds).catch((err) => {
|
|
73961
|
-
|
|
74241
|
+
log42.warn(`Failed to cleanup old sticky messages for ${platform.platformId}: ${err}`);
|
|
73962
74242
|
});
|
|
73963
74243
|
}).catch((err) => {
|
|
73964
|
-
|
|
74244
|
+
log42.warn(`Failed to get bot user for cleanup on ${platform.platformId}: ${err}`);
|
|
73965
74245
|
});
|
|
73966
74246
|
}
|
|
73967
74247
|
if (persisted.size > 0) {
|
|
@@ -73975,10 +74255,10 @@ class SessionManager extends EventEmitter4 {
|
|
|
73975
74255
|
}
|
|
73976
74256
|
}
|
|
73977
74257
|
if (pausedToSkip.length > 0) {
|
|
73978
|
-
|
|
74258
|
+
log42.info(`⏸️ ${pausedToSkip.length} session(s) remain paused (waiting for user message)`);
|
|
73979
74259
|
}
|
|
73980
74260
|
if (activeToResume.length > 0) {
|
|
73981
|
-
|
|
74261
|
+
log42.info(`\uD83D\uDD04 Attempting to resume ${activeToResume.length} active session(s)...`);
|
|
73982
74262
|
for (const state of activeToResume) {
|
|
73983
74263
|
await resumeSession(state, this.getContext());
|
|
73984
74264
|
}
|
|
@@ -74456,7 +74736,7 @@ Mention me to start a session in this worktree.`, threadId);
|
|
|
74456
74736
|
const message = messageBuilder(formatter);
|
|
74457
74737
|
await post(session, "info", message);
|
|
74458
74738
|
} catch (err) {
|
|
74459
|
-
|
|
74739
|
+
log42.warn(`Failed to broadcast to session ${session.threadId}: ${err}`);
|
|
74460
74740
|
}
|
|
74461
74741
|
}
|
|
74462
74742
|
}
|
|
@@ -74475,7 +74755,7 @@ Mention me to start a session in this worktree.`, threadId);
|
|
|
74475
74755
|
session.messageManager?.setPendingUpdatePrompt({ postId: post2.id });
|
|
74476
74756
|
this.registerPost(post2.id, session.threadId);
|
|
74477
74757
|
} catch (err) {
|
|
74478
|
-
|
|
74758
|
+
log42.warn(`Failed to post ask message to ${threadId}: ${err}`);
|
|
74479
74759
|
}
|
|
74480
74760
|
}
|
|
74481
74761
|
}
|
|
@@ -82070,29 +82350,29 @@ function SessionLog({ logs, maxLines = 20 }) {
|
|
|
82070
82350
|
return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
|
|
82071
82351
|
flexDirection: "column",
|
|
82072
82352
|
flexShrink: 0,
|
|
82073
|
-
children: displayLogs.map((
|
|
82353
|
+
children: displayLogs.map((log43) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
|
|
82074
82354
|
flexShrink: 0,
|
|
82075
82355
|
children: [
|
|
82076
82356
|
/* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
|
|
82077
|
-
color: getColorForLevel(
|
|
82357
|
+
color: getColorForLevel(log43.level),
|
|
82078
82358
|
dimColor: true,
|
|
82079
82359
|
wrap: "truncate",
|
|
82080
82360
|
children: [
|
|
82081
82361
|
"[",
|
|
82082
|
-
padComponent(
|
|
82362
|
+
padComponent(log43.component),
|
|
82083
82363
|
"]"
|
|
82084
82364
|
]
|
|
82085
82365
|
}, undefined, true, undefined, this),
|
|
82086
82366
|
/* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
|
|
82087
|
-
color: getColorForLevel(
|
|
82367
|
+
color: getColorForLevel(log43.level),
|
|
82088
82368
|
wrap: "truncate",
|
|
82089
82369
|
children: [
|
|
82090
82370
|
" ",
|
|
82091
|
-
|
|
82371
|
+
log43.message
|
|
82092
82372
|
]
|
|
82093
82373
|
}, undefined, true, undefined, this)
|
|
82094
82374
|
]
|
|
82095
|
-
},
|
|
82375
|
+
}, log43.id, true, undefined, this))
|
|
82096
82376
|
}, undefined, false, undefined, this);
|
|
82097
82377
|
}
|
|
82098
82378
|
// src/ui/components/Footer.tsx
|
|
@@ -82616,7 +82896,7 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
|
|
|
82616
82896
|
const scrollRef = import_react59.default.useRef(null);
|
|
82617
82897
|
const { stdout } = use_stdout_default();
|
|
82618
82898
|
const isDebug = process.env.DEBUG === "1";
|
|
82619
|
-
const displayLogs = logs.filter((
|
|
82899
|
+
const displayLogs = logs.filter((log43) => isDebug || log43.level !== "debug");
|
|
82620
82900
|
const visibleLogs = displayLogs.slice(-Math.max(maxLines * 3, 100));
|
|
82621
82901
|
import_react59.default.useEffect(() => {
|
|
82622
82902
|
const handleResize = () => scrollRef.current?.remeasure();
|
|
@@ -82656,25 +82936,25 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
|
|
|
82656
82936
|
overflow: "hidden",
|
|
82657
82937
|
children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(ScrollView, {
|
|
82658
82938
|
ref: scrollRef,
|
|
82659
|
-
children: visibleLogs.map((
|
|
82939
|
+
children: visibleLogs.map((log43) => /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
|
|
82660
82940
|
children: [
|
|
82661
82941
|
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
|
|
82662
82942
|
dimColor: true,
|
|
82663
82943
|
children: [
|
|
82664
82944
|
"[",
|
|
82665
|
-
padComponent2(
|
|
82945
|
+
padComponent2(log43.component),
|
|
82666
82946
|
"]"
|
|
82667
82947
|
]
|
|
82668
82948
|
}, undefined, true, undefined, this),
|
|
82669
82949
|
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
|
|
82670
|
-
color: getLevelColor(
|
|
82950
|
+
color: getLevelColor(log43.level),
|
|
82671
82951
|
children: [
|
|
82672
82952
|
" ",
|
|
82673
|
-
|
|
82953
|
+
log43.message
|
|
82674
82954
|
]
|
|
82675
82955
|
}, undefined, true, undefined, this)
|
|
82676
82956
|
]
|
|
82677
|
-
},
|
|
82957
|
+
}, log43.id, true, undefined, this))
|
|
82678
82958
|
}, undefined, false, undefined, this)
|
|
82679
82959
|
}, undefined, false, undefined, this);
|
|
82680
82960
|
}
|
|
@@ -83200,10 +83480,10 @@ function useAppState(initialConfig) {
|
|
|
83200
83480
|
});
|
|
83201
83481
|
}, []);
|
|
83202
83482
|
const getLogsForSession = import_react60.useCallback((sessionId) => {
|
|
83203
|
-
return state.logs.filter((
|
|
83483
|
+
return state.logs.filter((log43) => log43.sessionId === sessionId);
|
|
83204
83484
|
}, [state.logs]);
|
|
83205
83485
|
const getGlobalLogs = import_react60.useCallback(() => {
|
|
83206
|
-
return state.logs.filter((
|
|
83486
|
+
return state.logs.filter((log43) => !log43.sessionId);
|
|
83207
83487
|
}, [state.logs]);
|
|
83208
83488
|
const togglePlatformEnabled = import_react60.useCallback((platformId) => {
|
|
83209
83489
|
let newEnabled = false;
|
|
@@ -84006,6 +84286,16 @@ async function startUI(options) {
|
|
|
84006
84286
|
init_logger();
|
|
84007
84287
|
|
|
84008
84288
|
// src/message-handler.ts
|
|
84289
|
+
init_logger();
|
|
84290
|
+
var ackLog = createLogger("ack");
|
|
84291
|
+
function ackReceipt(client, postId) {
|
|
84292
|
+
const emoji = resolveAckReaction(client.ackReaction);
|
|
84293
|
+
if (!emoji)
|
|
84294
|
+
return;
|
|
84295
|
+
Promise.resolve(client.addReaction(postId, emoji)).catch((err) => {
|
|
84296
|
+
ackLog.debug(`ack reaction '${emoji}' failed on ${postId}: ${err}`);
|
|
84297
|
+
});
|
|
84298
|
+
}
|
|
84009
84299
|
async function handleMessage(client, session, post2, user, options) {
|
|
84010
84300
|
const { platformId, logger, onKill } = options;
|
|
84011
84301
|
const dcm = resolveDirectChannelMode(options.directChannelMode);
|
|
@@ -84020,6 +84310,12 @@ async function handleMessage(client, session, post2, user, options) {
|
|
|
84020
84310
|
await client.createPost(`⛔ Only authorized users can use ${formatter.formatCode("!kill")}`, threadRoot);
|
|
84021
84311
|
return;
|
|
84022
84312
|
}
|
|
84313
|
+
auditLog(platformId, {
|
|
84314
|
+
threadId: threadRoot,
|
|
84315
|
+
actor: username,
|
|
84316
|
+
kind: "command",
|
|
84317
|
+
tool: "kill"
|
|
84318
|
+
});
|
|
84023
84319
|
const activeCount = session.registry.getActiveThreadIds().length;
|
|
84024
84320
|
try {
|
|
84025
84321
|
await client.createPost(`\uD83D\uDD34 ${formatter.formatBold("EMERGENCY SHUTDOWN")} initiated by ${formatter.formatUserMention(username)} - killing ${activeCount} active session${activeCount !== 1 ? "s" : ""}`, threadRoot);
|
|
@@ -84103,8 +84399,10 @@ async function handleMessage(client, session, post2, user, options) {
|
|
|
84103
84399
|
return;
|
|
84104
84400
|
}
|
|
84105
84401
|
const files2 = post2.metadata?.files;
|
|
84106
|
-
if (content || files2?.length)
|
|
84402
|
+
if (content || files2?.length) {
|
|
84403
|
+
ackReceipt(client, post2.id);
|
|
84107
84404
|
await session.sendFollowUp(threadRoot, content, files2, username, user?.displayName);
|
|
84405
|
+
}
|
|
84108
84406
|
return;
|
|
84109
84407
|
}
|
|
84110
84408
|
const hasPausedSession = session.registry.getPersistedByThreadId(threadRoot) !== undefined;
|
|
@@ -84121,6 +84419,13 @@ async function handleMessage(client, session, post2, user, options) {
|
|
|
84121
84419
|
if (persistedSession2) {
|
|
84122
84420
|
const allowedUsers = new Set(persistedSession2.sessionAllowedUsers);
|
|
84123
84421
|
if (allowedUsers.has(username) || client.isUserAllowed(username)) {
|
|
84422
|
+
auditLog(platformId, {
|
|
84423
|
+
threadId: threadRoot,
|
|
84424
|
+
actor: username,
|
|
84425
|
+
kind: "command",
|
|
84426
|
+
tool: "stop",
|
|
84427
|
+
detail: "paused session cancelled"
|
|
84428
|
+
});
|
|
84124
84429
|
session.cancelPausedSession(threadRoot);
|
|
84125
84430
|
await client.createPost(`\uD83D\uDED1 ${formatter.formatBold("Session cancelled")} by ${formatter.formatUserMention(username)}`, threadRoot);
|
|
84126
84431
|
}
|
|
@@ -84142,6 +84447,7 @@ async function handleMessage(client, session, post2, user, options) {
|
|
|
84142
84447
|
}
|
|
84143
84448
|
const files2 = post2.metadata?.files;
|
|
84144
84449
|
if (content || files2?.length) {
|
|
84450
|
+
ackReceipt(client, post2.id);
|
|
84145
84451
|
await session.resumePausedSession(threadRoot, content, files2, username);
|
|
84146
84452
|
}
|
|
84147
84453
|
return;
|
|
@@ -84210,6 +84516,7 @@ async function handleMessage(client, session, post2, user, options) {
|
|
|
84210
84516
|
await client.createPost(`Mention me with your request`, threadRoot);
|
|
84211
84517
|
return;
|
|
84212
84518
|
}
|
|
84519
|
+
ackReceipt(client, post2.id);
|
|
84213
84520
|
if (worktreeBranch) {
|
|
84214
84521
|
await session.startSessionWithWorktree({ prompt, files }, worktreeBranch, username, threadRoot, platformId, user?.displayName, post2.id, initialOptions);
|
|
84215
84522
|
return;
|
|
@@ -84233,7 +84540,7 @@ import { EventEmitter as EventEmitter9 } from "events";
|
|
|
84233
84540
|
// src/auto-update/checker.ts
|
|
84234
84541
|
init_logger();
|
|
84235
84542
|
import { EventEmitter as EventEmitter7 } from "events";
|
|
84236
|
-
var
|
|
84543
|
+
var log43 = createLogger("checker");
|
|
84237
84544
|
var PACKAGE_NAME = "claude-threads";
|
|
84238
84545
|
function compareVersions(a, b) {
|
|
84239
84546
|
const partsA = a.replace(/^v/, "").split(".").map(Number);
|
|
@@ -84256,13 +84563,13 @@ async function fetchLatestVersion() {
|
|
|
84256
84563
|
}
|
|
84257
84564
|
});
|
|
84258
84565
|
if (!response.ok) {
|
|
84259
|
-
|
|
84566
|
+
log43.warn(`Failed to fetch latest version: HTTP ${response.status}`);
|
|
84260
84567
|
return null;
|
|
84261
84568
|
}
|
|
84262
84569
|
const data = await response.json();
|
|
84263
84570
|
return data.version ?? null;
|
|
84264
84571
|
} catch (err) {
|
|
84265
|
-
|
|
84572
|
+
log43.warn(`Failed to fetch latest version: ${err}`);
|
|
84266
84573
|
return null;
|
|
84267
84574
|
}
|
|
84268
84575
|
}
|
|
@@ -84279,38 +84586,38 @@ class UpdateChecker extends EventEmitter7 {
|
|
|
84279
84586
|
}
|
|
84280
84587
|
start() {
|
|
84281
84588
|
if (!this.config.enabled) {
|
|
84282
|
-
|
|
84589
|
+
log43.debug("Auto-update disabled, not starting checker");
|
|
84283
84590
|
return;
|
|
84284
84591
|
}
|
|
84285
84592
|
setTimeout(() => {
|
|
84286
84593
|
this.check().catch((err) => {
|
|
84287
|
-
|
|
84594
|
+
log43.warn(`Initial update check failed: ${err}`);
|
|
84288
84595
|
});
|
|
84289
84596
|
}, 5000);
|
|
84290
84597
|
const intervalMs = this.config.checkIntervalMinutes * 60 * 1000;
|
|
84291
84598
|
this.checkInterval = setInterval(() => {
|
|
84292
84599
|
this.check().catch((err) => {
|
|
84293
|
-
|
|
84600
|
+
log43.warn(`Periodic update check failed: ${err}`);
|
|
84294
84601
|
});
|
|
84295
84602
|
}, intervalMs);
|
|
84296
|
-
|
|
84603
|
+
log43.info(`\uD83D\uDD04 Update checker started (every ${this.config.checkIntervalMinutes} minutes)`);
|
|
84297
84604
|
}
|
|
84298
84605
|
stop() {
|
|
84299
84606
|
if (this.checkInterval) {
|
|
84300
84607
|
clearInterval(this.checkInterval);
|
|
84301
84608
|
this.checkInterval = null;
|
|
84302
84609
|
}
|
|
84303
|
-
|
|
84610
|
+
log43.debug("Update checker stopped");
|
|
84304
84611
|
}
|
|
84305
84612
|
async check() {
|
|
84306
84613
|
if (this.isChecking) {
|
|
84307
|
-
|
|
84614
|
+
log43.debug("Check already in progress, skipping");
|
|
84308
84615
|
return this.lastUpdateInfo;
|
|
84309
84616
|
}
|
|
84310
84617
|
this.isChecking = true;
|
|
84311
84618
|
this.emit("check:start");
|
|
84312
84619
|
try {
|
|
84313
|
-
|
|
84620
|
+
log43.debug("Checking for updates...");
|
|
84314
84621
|
const latestVersion2 = await fetchLatestVersion();
|
|
84315
84622
|
if (!latestVersion2) {
|
|
84316
84623
|
this.emit("check:complete", false);
|
|
@@ -84327,18 +84634,18 @@ class UpdateChecker extends EventEmitter7 {
|
|
|
84327
84634
|
detectedAt: new Date
|
|
84328
84635
|
};
|
|
84329
84636
|
if (!this.lastUpdateInfo || this.lastUpdateInfo.latestVersion !== latestVersion2) {
|
|
84330
|
-
|
|
84637
|
+
log43.info(`\uD83C\uDD95 Update available: v${currentVersion} → v${latestVersion2}`);
|
|
84331
84638
|
this.lastUpdateInfo = updateInfo;
|
|
84332
84639
|
this.emit("update", updateInfo);
|
|
84333
84640
|
}
|
|
84334
84641
|
this.emit("check:complete", true);
|
|
84335
84642
|
return updateInfo;
|
|
84336
84643
|
}
|
|
84337
|
-
|
|
84644
|
+
log43.debug(`Up to date (v${currentVersion})`);
|
|
84338
84645
|
this.emit("check:complete", false);
|
|
84339
84646
|
return null;
|
|
84340
84647
|
} catch (err) {
|
|
84341
|
-
|
|
84648
|
+
log43.warn(`Update check failed: ${err}`);
|
|
84342
84649
|
this.emit("check:error", err);
|
|
84343
84650
|
return null;
|
|
84344
84651
|
} finally {
|
|
@@ -84409,7 +84716,7 @@ function isInScheduledWindow(window2) {
|
|
|
84409
84716
|
}
|
|
84410
84717
|
|
|
84411
84718
|
// src/auto-update/scheduler.ts
|
|
84412
|
-
var
|
|
84719
|
+
var log44 = createLogger("scheduler");
|
|
84413
84720
|
|
|
84414
84721
|
class UpdateScheduler extends EventEmitter8 {
|
|
84415
84722
|
config;
|
|
@@ -84433,7 +84740,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
84433
84740
|
scheduleUpdate(updateInfo) {
|
|
84434
84741
|
this.pendingUpdate = updateInfo;
|
|
84435
84742
|
if (this.config.autoRestartMode === "immediate") {
|
|
84436
|
-
|
|
84743
|
+
log44.info("Immediate mode: triggering update now");
|
|
84437
84744
|
this.emit("ready", updateInfo);
|
|
84438
84745
|
return;
|
|
84439
84746
|
}
|
|
@@ -84446,19 +84753,19 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
84446
84753
|
this.scheduledRestartAt = null;
|
|
84447
84754
|
this.askApprovals.clear();
|
|
84448
84755
|
this.askStartTime = null;
|
|
84449
|
-
|
|
84756
|
+
log44.debug("Update schedule cancelled");
|
|
84450
84757
|
}
|
|
84451
84758
|
deferUpdate(minutes) {
|
|
84452
84759
|
const deferUntil = new Date(Date.now() + minutes * 60 * 1000);
|
|
84453
84760
|
this.scheduledRestartAt = null;
|
|
84454
84761
|
this.idleStartTime = null;
|
|
84455
84762
|
this.emit("deferred", deferUntil);
|
|
84456
|
-
|
|
84763
|
+
log44.info(`Update deferred until ${deferUntil.toLocaleTimeString()}`);
|
|
84457
84764
|
return deferUntil;
|
|
84458
84765
|
}
|
|
84459
84766
|
recordAskResponse(threadId, approved) {
|
|
84460
84767
|
this.askApprovals.set(threadId, approved);
|
|
84461
|
-
|
|
84768
|
+
log44.debug(`Thread ${threadId.substring(0, 8)} ${approved ? "approved" : "denied"} update`);
|
|
84462
84769
|
this.checkAskCondition();
|
|
84463
84770
|
}
|
|
84464
84771
|
getScheduledRestartAt() {
|
|
@@ -84479,7 +84786,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
84479
84786
|
return;
|
|
84480
84787
|
this.checkCondition();
|
|
84481
84788
|
this.checkTimer = setInterval(() => this.checkCondition(), 1e4);
|
|
84482
|
-
|
|
84789
|
+
log44.debug(`Started checking for ${this.config.autoRestartMode} condition`);
|
|
84483
84790
|
}
|
|
84484
84791
|
stopChecking() {
|
|
84485
84792
|
if (this.checkTimer) {
|
|
@@ -84510,17 +84817,17 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
84510
84817
|
if (activity.activeSessionCount === 0) {
|
|
84511
84818
|
if (!this.idleStartTime) {
|
|
84512
84819
|
this.idleStartTime = new Date;
|
|
84513
|
-
|
|
84820
|
+
log44.debug("No active sessions, starting idle timer");
|
|
84514
84821
|
}
|
|
84515
84822
|
const idleMs = Date.now() - this.idleStartTime.getTime();
|
|
84516
84823
|
const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
|
|
84517
84824
|
if (idleMs >= requiredMs) {
|
|
84518
|
-
|
|
84825
|
+
log44.info(`Idle for ${this.config.idleTimeoutMinutes} minutes, triggering update`);
|
|
84519
84826
|
this.triggerCountdown();
|
|
84520
84827
|
}
|
|
84521
84828
|
} else {
|
|
84522
84829
|
if (this.idleStartTime) {
|
|
84523
|
-
|
|
84830
|
+
log44.debug("Sessions became active, resetting idle timer");
|
|
84524
84831
|
this.idleStartTime = null;
|
|
84525
84832
|
}
|
|
84526
84833
|
}
|
|
@@ -84531,7 +84838,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
84531
84838
|
const quietMs = Date.now() - activity.lastActivityAt.getTime();
|
|
84532
84839
|
const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
|
|
84533
84840
|
if (quietMs >= requiredMs && !activity.anySessionBusy) {
|
|
84534
|
-
|
|
84841
|
+
log44.info(`Sessions quiet for ${this.config.quietTimeoutMinutes} minutes, triggering update`);
|
|
84535
84842
|
this.triggerCountdown();
|
|
84536
84843
|
}
|
|
84537
84844
|
} else if (activity.activeSessionCount === 0) {
|
|
@@ -84541,7 +84848,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
84541
84848
|
const idleMs = Date.now() - this.idleStartTime.getTime();
|
|
84542
84849
|
const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
|
|
84543
84850
|
if (idleMs >= requiredMs) {
|
|
84544
|
-
|
|
84851
|
+
log44.info("No sessions and quiet timeout reached, triggering update");
|
|
84545
84852
|
this.triggerCountdown();
|
|
84546
84853
|
}
|
|
84547
84854
|
}
|
|
@@ -84552,13 +84859,13 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
84552
84859
|
}
|
|
84553
84860
|
const activity = this.getSessionActivity();
|
|
84554
84861
|
if (activity.activeSessionCount === 0) {
|
|
84555
|
-
|
|
84862
|
+
log44.info("Within scheduled window and no active sessions, triggering update");
|
|
84556
84863
|
this.triggerCountdown();
|
|
84557
84864
|
} else if (activity.lastActivityAt) {
|
|
84558
84865
|
const quietMs = Date.now() - activity.lastActivityAt.getTime();
|
|
84559
84866
|
const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
|
|
84560
84867
|
if (quietMs >= requiredMs && !activity.anySessionBusy) {
|
|
84561
|
-
|
|
84868
|
+
log44.info("Within scheduled window and sessions quiet, triggering update");
|
|
84562
84869
|
this.triggerCountdown();
|
|
84563
84870
|
}
|
|
84564
84871
|
}
|
|
@@ -84566,14 +84873,14 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
84566
84873
|
checkAskCondition() {
|
|
84567
84874
|
const threadIds = this.getActiveThreadIds();
|
|
84568
84875
|
if (threadIds.length === 0) {
|
|
84569
|
-
|
|
84876
|
+
log44.info("No active threads, proceeding with update");
|
|
84570
84877
|
this.triggerCountdown();
|
|
84571
84878
|
return;
|
|
84572
84879
|
}
|
|
84573
84880
|
if (!this.askStartTime && this.pendingUpdate) {
|
|
84574
84881
|
this.askStartTime = new Date;
|
|
84575
84882
|
this.postAskMessage(threadIds, this.pendingUpdate.latestVersion).catch((err) => {
|
|
84576
|
-
|
|
84883
|
+
log44.warn(`Failed to post ask message: ${err}`);
|
|
84577
84884
|
});
|
|
84578
84885
|
return;
|
|
84579
84886
|
}
|
|
@@ -84586,12 +84893,12 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
84586
84893
|
denials++;
|
|
84587
84894
|
}
|
|
84588
84895
|
if (approvals > threadIds.length / 2) {
|
|
84589
|
-
|
|
84896
|
+
log44.info(`Majority approved (${approvals}/${threadIds.length}), triggering update`);
|
|
84590
84897
|
this.triggerCountdown();
|
|
84591
84898
|
return;
|
|
84592
84899
|
}
|
|
84593
84900
|
if (denials > threadIds.length / 2) {
|
|
84594
|
-
|
|
84901
|
+
log44.info(`Majority denied (${denials}/${threadIds.length}), deferring update`);
|
|
84595
84902
|
this.deferUpdate(60);
|
|
84596
84903
|
return;
|
|
84597
84904
|
}
|
|
@@ -84599,7 +84906,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
84599
84906
|
const elapsedMs = Date.now() - this.askStartTime.getTime();
|
|
84600
84907
|
const timeoutMs = this.config.askTimeoutMinutes * 60 * 1000;
|
|
84601
84908
|
if (elapsedMs >= timeoutMs) {
|
|
84602
|
-
|
|
84909
|
+
log44.info(`Ask timeout reached (${this.config.askTimeoutMinutes} min), triggering update`);
|
|
84603
84910
|
this.triggerCountdown();
|
|
84604
84911
|
}
|
|
84605
84912
|
}
|
|
@@ -84619,7 +84926,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
84619
84926
|
this.emit("ready", this.pendingUpdate);
|
|
84620
84927
|
}
|
|
84621
84928
|
}, 1000);
|
|
84622
|
-
|
|
84929
|
+
log44.info("Update countdown started (60 seconds)");
|
|
84623
84930
|
}
|
|
84624
84931
|
stopCountdown() {
|
|
84625
84932
|
if (this.countdownTimer) {
|
|
@@ -84632,27 +84939,27 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
84632
84939
|
// src/auto-update/installer.ts
|
|
84633
84940
|
init_logger();
|
|
84634
84941
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
84635
|
-
import { existsSync as existsSync16, readFileSync as readFileSync12, writeFileSync as writeFileSync9, mkdirSync as
|
|
84942
|
+
import { existsSync as existsSync16, readFileSync as readFileSync12, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8 } from "fs";
|
|
84636
84943
|
import { dirname as dirname9, resolve as resolve7 } from "path";
|
|
84637
|
-
import { homedir as
|
|
84638
|
-
var
|
|
84944
|
+
import { homedir as homedir9 } from "os";
|
|
84945
|
+
var log45 = createLogger("installer");
|
|
84639
84946
|
function detectPackageManager() {
|
|
84640
84947
|
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
84641
84948
|
const originalInstaller = detectOriginalInstaller();
|
|
84642
84949
|
if (originalInstaller) {
|
|
84643
|
-
|
|
84950
|
+
log45.debug(`Detected original installer: ${originalInstaller}`);
|
|
84644
84951
|
if (originalInstaller === "bun") {
|
|
84645
84952
|
const bunCheck2 = spawnSync("bun", ["--version"], { stdio: "ignore" });
|
|
84646
84953
|
if (bunCheck2.status === 0) {
|
|
84647
84954
|
return { cmd: "bun", isBun: true };
|
|
84648
84955
|
}
|
|
84649
|
-
|
|
84956
|
+
log45.warn("Originally installed with bun, but bun not found. Falling back to npm.");
|
|
84650
84957
|
} else {
|
|
84651
84958
|
const npmCheck2 = spawnSync(npmCmd, ["--version"], { stdio: "ignore" });
|
|
84652
84959
|
if (npmCheck2.status === 0) {
|
|
84653
84960
|
return { cmd: npmCmd, isBun: false };
|
|
84654
84961
|
}
|
|
84655
|
-
|
|
84962
|
+
log45.warn("Originally installed with npm, but npm not found. Falling back to bun.");
|
|
84656
84963
|
}
|
|
84657
84964
|
}
|
|
84658
84965
|
const bunCheck = spawnSync("bun", ["--version"], { stdio: "ignore" });
|
|
@@ -84674,7 +84981,7 @@ function normalizePath(p) {
|
|
|
84674
84981
|
function detectOriginalInstaller() {
|
|
84675
84982
|
try {
|
|
84676
84983
|
const scriptPath = normalizePath(process.argv[1] || "");
|
|
84677
|
-
const bunGlobalDir = normalizePath(process.env.BUN_INSTALL || resolve7(
|
|
84984
|
+
const bunGlobalDir = normalizePath(process.env.BUN_INSTALL || resolve7(homedir9(), ".bun"));
|
|
84678
84985
|
if (scriptPath.startsWith(bunGlobalDir)) {
|
|
84679
84986
|
return "bun";
|
|
84680
84987
|
}
|
|
@@ -84694,7 +85001,7 @@ function detectOriginalInstaller() {
|
|
|
84694
85001
|
return null;
|
|
84695
85002
|
}
|
|
84696
85003
|
}
|
|
84697
|
-
var STATE_PATH = resolve7(
|
|
85004
|
+
var STATE_PATH = resolve7(homedir9(), ".config", "claude-threads", UPDATE_STATE_FILENAME);
|
|
84698
85005
|
var PACKAGE_NAME2 = "claude-threads";
|
|
84699
85006
|
function loadUpdateState() {
|
|
84700
85007
|
try {
|
|
@@ -84703,7 +85010,7 @@ function loadUpdateState() {
|
|
|
84703
85010
|
return JSON.parse(content);
|
|
84704
85011
|
}
|
|
84705
85012
|
} catch (err) {
|
|
84706
|
-
|
|
85013
|
+
log45.warn(`Failed to load update state: ${err}`);
|
|
84707
85014
|
}
|
|
84708
85015
|
return {};
|
|
84709
85016
|
}
|
|
@@ -84711,12 +85018,12 @@ function saveUpdateState(state) {
|
|
|
84711
85018
|
try {
|
|
84712
85019
|
const dir = dirname9(STATE_PATH);
|
|
84713
85020
|
if (!existsSync16(dir)) {
|
|
84714
|
-
|
|
85021
|
+
mkdirSync8(dir, { recursive: true });
|
|
84715
85022
|
}
|
|
84716
85023
|
writeFileSync9(STATE_PATH, JSON.stringify(state, null, 2), "utf-8");
|
|
84717
|
-
|
|
85024
|
+
log45.debug("Update state saved");
|
|
84718
85025
|
} catch (err) {
|
|
84719
|
-
|
|
85026
|
+
log45.warn(`Failed to save update state: ${err}`);
|
|
84720
85027
|
}
|
|
84721
85028
|
}
|
|
84722
85029
|
function clearUpdateState() {
|
|
@@ -84725,7 +85032,7 @@ function clearUpdateState() {
|
|
|
84725
85032
|
writeFileSync9(STATE_PATH, "{}", "utf-8");
|
|
84726
85033
|
}
|
|
84727
85034
|
} catch (err) {
|
|
84728
|
-
|
|
85035
|
+
log45.warn(`Failed to clear update state: ${err}`);
|
|
84729
85036
|
}
|
|
84730
85037
|
}
|
|
84731
85038
|
function checkJustUpdated() {
|
|
@@ -84757,11 +85064,11 @@ function clearRuntimeSettings() {
|
|
|
84757
85064
|
}
|
|
84758
85065
|
}
|
|
84759
85066
|
async function installVersion(version) {
|
|
84760
|
-
|
|
85067
|
+
log45.info(`\uD83D\uDCE6 Installing ${PACKAGE_NAME2}@${version}...`);
|
|
84761
85068
|
const pm = detectPackageManager();
|
|
84762
85069
|
if (!pm) {
|
|
84763
85070
|
const error = "Neither bun nor npm found in PATH. Cannot install update.";
|
|
84764
|
-
|
|
85071
|
+
log45.error(`❌ ${error}`);
|
|
84765
85072
|
return { success: false, error };
|
|
84766
85073
|
}
|
|
84767
85074
|
saveUpdateState({
|
|
@@ -84773,7 +85080,7 @@ async function installVersion(version) {
|
|
|
84773
85080
|
return new Promise((resolve8) => {
|
|
84774
85081
|
const { cmd, isBun: isBun3 } = pm;
|
|
84775
85082
|
const args = ["install", "-g", `${PACKAGE_NAME2}@${version}`];
|
|
84776
|
-
|
|
85083
|
+
log45.debug(`Using ${isBun3 ? "bun" : "npm"} for installation`);
|
|
84777
85084
|
const child = spawn4(cmd, args, {
|
|
84778
85085
|
stdio: ["ignore", "pipe", "pipe"],
|
|
84779
85086
|
env: {
|
|
@@ -84791,7 +85098,7 @@ async function installVersion(version) {
|
|
|
84791
85098
|
});
|
|
84792
85099
|
child.on("close", (code) => {
|
|
84793
85100
|
if (code === 0) {
|
|
84794
|
-
|
|
85101
|
+
log45.info(`✅ Successfully installed ${PACKAGE_NAME2}@${version}`);
|
|
84795
85102
|
saveUpdateState({
|
|
84796
85103
|
previousVersion: VERSION,
|
|
84797
85104
|
targetVersion: version,
|
|
@@ -84801,20 +85108,20 @@ async function installVersion(version) {
|
|
|
84801
85108
|
resolve8({ success: true });
|
|
84802
85109
|
} else {
|
|
84803
85110
|
const errorMsg = stderr || stdout || `Exit code: ${code}`;
|
|
84804
|
-
|
|
85111
|
+
log45.error(`❌ Installation failed: ${errorMsg}`);
|
|
84805
85112
|
clearUpdateState();
|
|
84806
85113
|
resolve8({ success: false, error: errorMsg });
|
|
84807
85114
|
}
|
|
84808
85115
|
});
|
|
84809
85116
|
child.on("error", (err) => {
|
|
84810
|
-
|
|
85117
|
+
log45.error(`❌ Failed to spawn npm: ${err}`);
|
|
84811
85118
|
clearUpdateState();
|
|
84812
85119
|
resolve8({ success: false, error: err.message });
|
|
84813
85120
|
});
|
|
84814
85121
|
setTimeout(() => {
|
|
84815
85122
|
if (child.exitCode === null) {
|
|
84816
85123
|
child.kill();
|
|
84817
|
-
|
|
85124
|
+
log45.error("❌ Installation timed out");
|
|
84818
85125
|
clearUpdateState();
|
|
84819
85126
|
resolve8({ success: false, error: "Installation timed out" });
|
|
84820
85127
|
}
|
|
@@ -84859,8 +85166,8 @@ class UpdateInstaller {
|
|
|
84859
85166
|
init_logger();
|
|
84860
85167
|
import { spawn as spawn5 } from "child_process";
|
|
84861
85168
|
import { existsSync as existsSync17, statSync as statSync4 } from "fs";
|
|
84862
|
-
import { delimiter, join as
|
|
84863
|
-
var
|
|
85169
|
+
import { delimiter, join as join15 } from "path";
|
|
85170
|
+
var log46 = createLogger("respawn");
|
|
84864
85171
|
function decideRespawn(env5 = process.env, isTTY = !!process.stdout.isTTY) {
|
|
84865
85172
|
if (env5.CLAUDE_THREADS_BIN) {
|
|
84866
85173
|
return { kind: "exit-for-supervisor", supervisor: "claude-threads-daemon" };
|
|
@@ -84885,16 +85192,16 @@ function resolveClaudeThreadsBin(_env = process.env, _existsSync = existsSync17,
|
|
|
84885
85192
|
const path10 = _env.PATH || _env.Path || "";
|
|
84886
85193
|
const dirs = path10.split(delimiter).filter(Boolean);
|
|
84887
85194
|
const home = _env.HOME || _env.USERPROFILE;
|
|
84888
|
-
const bunRoot = _env.BUN_INSTALL || (home ?
|
|
85195
|
+
const bunRoot = _env.BUN_INSTALL || (home ? join15(home, ".bun") : null);
|
|
84889
85196
|
if (bunRoot) {
|
|
84890
|
-
const bunBin =
|
|
85197
|
+
const bunBin = join15(bunRoot, "bin");
|
|
84891
85198
|
if (!dirs.includes(bunBin)) {
|
|
84892
85199
|
dirs.push(bunBin);
|
|
84893
85200
|
}
|
|
84894
85201
|
}
|
|
84895
85202
|
for (const dir of dirs) {
|
|
84896
85203
|
for (const name of names) {
|
|
84897
|
-
const candidate =
|
|
85204
|
+
const candidate = join15(dir, name);
|
|
84898
85205
|
if (_existsSync(candidate) && _isFileExecutable(candidate)) {
|
|
84899
85206
|
return candidate;
|
|
84900
85207
|
}
|
|
@@ -84916,7 +85223,7 @@ function isFileExecutable(path10) {
|
|
|
84916
85223
|
}
|
|
84917
85224
|
function spawnReplacement(argv = process.argv.slice(2), binPath = resolveClaudeThreadsBin()) {
|
|
84918
85225
|
if (!binPath) {
|
|
84919
|
-
|
|
85226
|
+
log46.error("Could not resolve claude-threads on PATH; self-respawn aborted");
|
|
84920
85227
|
return false;
|
|
84921
85228
|
}
|
|
84922
85229
|
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
@@ -84937,23 +85244,23 @@ function spawnReplacement(argv = process.argv.slice(2), binPath = resolveClaudeT
|
|
|
84937
85244
|
shell: useShell
|
|
84938
85245
|
});
|
|
84939
85246
|
} catch (err) {
|
|
84940
|
-
|
|
85247
|
+
log46.error(`spawn() threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
84941
85248
|
return false;
|
|
84942
85249
|
}
|
|
84943
85250
|
child.once("error", (err) => {
|
|
84944
|
-
|
|
85251
|
+
log46.error(`Replacement process error: ${err.message}`);
|
|
84945
85252
|
});
|
|
84946
85253
|
if (child.pid === undefined) {
|
|
84947
|
-
|
|
85254
|
+
log46.error("Spawn returned no pid (binary likely not executable)");
|
|
84948
85255
|
return false;
|
|
84949
85256
|
}
|
|
84950
85257
|
child.unref();
|
|
84951
|
-
|
|
85258
|
+
log46.info(`Spawned replacement pid=${child.pid} from ${binPath}`);
|
|
84952
85259
|
return true;
|
|
84953
85260
|
}
|
|
84954
85261
|
|
|
84955
85262
|
// src/auto-update/manager.ts
|
|
84956
|
-
var
|
|
85263
|
+
var log47 = createLogger("updater");
|
|
84957
85264
|
|
|
84958
85265
|
class AutoUpdateManager extends EventEmitter9 {
|
|
84959
85266
|
config;
|
|
@@ -84976,23 +85283,23 @@ class AutoUpdateManager extends EventEmitter9 {
|
|
|
84976
85283
|
}
|
|
84977
85284
|
start() {
|
|
84978
85285
|
if (!this.config.enabled) {
|
|
84979
|
-
|
|
85286
|
+
log47.info("Auto-update is disabled");
|
|
84980
85287
|
return;
|
|
84981
85288
|
}
|
|
84982
85289
|
const updateResult = this.installer.checkJustUpdated();
|
|
84983
85290
|
if (updateResult) {
|
|
84984
|
-
|
|
85291
|
+
log47.info(`\uD83C\uDF89 Updated from v${updateResult.previousVersion} to v${updateResult.currentVersion}`);
|
|
84985
85292
|
this.callbacks.broadcastUpdate((fmt) => `\uD83C\uDF89 ${fmt.formatBold("Bot updated")} from v${updateResult.previousVersion} to v${updateResult.currentVersion}`).catch((err) => {
|
|
84986
|
-
|
|
85293
|
+
log47.warn(`Failed to broadcast update notification: ${err}`);
|
|
84987
85294
|
});
|
|
84988
85295
|
}
|
|
84989
85296
|
this.checker.start();
|
|
84990
|
-
|
|
85297
|
+
log47.info(`\uD83D\uDD04 Auto-update manager started (mode: ${this.config.autoRestartMode})`);
|
|
84991
85298
|
}
|
|
84992
85299
|
stop() {
|
|
84993
85300
|
this.checker.stop();
|
|
84994
85301
|
this.scheduler.stop();
|
|
84995
|
-
|
|
85302
|
+
log47.debug("Auto-update manager stopped");
|
|
84996
85303
|
}
|
|
84997
85304
|
getState() {
|
|
84998
85305
|
return { ...this.state };
|
|
@@ -85006,10 +85313,10 @@ class AutoUpdateManager extends EventEmitter9 {
|
|
|
85006
85313
|
async forceUpdate() {
|
|
85007
85314
|
const updateInfo = this.state.updateInfo || await this.checker.check();
|
|
85008
85315
|
if (!updateInfo) {
|
|
85009
|
-
|
|
85316
|
+
log47.info("No update available");
|
|
85010
85317
|
return;
|
|
85011
85318
|
}
|
|
85012
|
-
|
|
85319
|
+
log47.info("Forcing immediate update");
|
|
85013
85320
|
await this.performUpdate(updateInfo);
|
|
85014
85321
|
}
|
|
85015
85322
|
deferUpdate(minutes = 60) {
|
|
@@ -85075,11 +85382,11 @@ class AutoUpdateManager extends EventEmitter9 {
|
|
|
85075
85382
|
await this.callbacks.prepareForRestart();
|
|
85076
85383
|
} catch (err) {
|
|
85077
85384
|
const reason = err instanceof Error ? err.message : String(err);
|
|
85078
|
-
|
|
85385
|
+
log47.error(`prepareForRestart failed: ${reason}`);
|
|
85079
85386
|
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(() => {});
|
|
85080
85387
|
process.exit(1);
|
|
85081
85388
|
}
|
|
85082
|
-
|
|
85389
|
+
log47.info(`\uD83D\uDD04 Restarting for update to v${updateInfo.latestVersion}`);
|
|
85083
85390
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
85084
85391
|
process.stdout.write("\x1B[?25h");
|
|
85085
85392
|
if (decision.kind === "self-respawn") {
|
|
@@ -85088,14 +85395,14 @@ class AutoUpdateManager extends EventEmitter9 {
|
|
|
85088
85395
|
if (ok) {
|
|
85089
85396
|
process.exit(0);
|
|
85090
85397
|
}
|
|
85091
|
-
|
|
85398
|
+
log47.error("Self-respawn launch failed after binary resolution succeeded");
|
|
85092
85399
|
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(() => {});
|
|
85093
85400
|
} else {
|
|
85094
|
-
|
|
85401
|
+
log47.error("claude-threads not found on PATH; manual restart required");
|
|
85095
85402
|
}
|
|
85096
85403
|
process.exit(0);
|
|
85097
85404
|
}
|
|
85098
|
-
|
|
85405
|
+
log47.debug(`Restart handled by supervisor: ${decision.supervisor}`);
|
|
85099
85406
|
process.exit(RESTART_EXIT_CODE);
|
|
85100
85407
|
} else {
|
|
85101
85408
|
const errorMsg = result.error ?? "Unknown error";
|
|
@@ -85489,6 +85796,7 @@ async function startWithoutDaemon() {
|
|
|
85489
85796
|
});
|
|
85490
85797
|
const client = createPlatformClient(platformConfig);
|
|
85491
85798
|
platforms.set(platformConfig.id, client);
|
|
85799
|
+
configureAuditLog(platformConfig.id, resolveAuditLogEnabled(platformConfig.auditLog, `platforms[${platformConfig.id}].auditLog`));
|
|
85492
85800
|
session.addPlatform(platformConfig.id, client, {
|
|
85493
85801
|
sessionHeader: resolveOverheadVisibility(platformConfig.sessionHeader, `platforms[${platformConfig.id}].sessionHeader`),
|
|
85494
85802
|
stickyMessage: resolveOverheadVisibility(platformConfig.stickyMessage, `platforms[${platformConfig.id}].stickyMessage`)
|
|
@@ -85509,6 +85817,7 @@ async function startWithoutDaemon() {
|
|
|
85509
85817
|
platformType: "mattermost",
|
|
85510
85818
|
enabled: true
|
|
85511
85819
|
});
|
|
85820
|
+
configureAuditLog(dmConfig.id, resolveAuditLogEnabled(dmConfig.auditLog, `dm[${dmConfig.id}].auditLog`));
|
|
85512
85821
|
session.addPlatform(dmConfig.id, dmClient, {
|
|
85513
85822
|
sessionHeader: resolveOverheadVisibility(dmConfig.sessionHeader, `dm[${dmConfig.id}].sessionHeader`),
|
|
85514
85823
|
stickyMessage: "hidden"
|
|
@@ -85553,10 +85862,10 @@ async function startWithoutDaemon() {
|
|
|
85553
85862
|
enabled: false
|
|
85554
85863
|
});
|
|
85555
85864
|
}
|
|
85556
|
-
const
|
|
85557
|
-
const disabledCount = platforms.size -
|
|
85558
|
-
ui.addLog({ level: "info", component: "init", message: `Connecting ${
|
|
85559
|
-
const connectionResults = await Promise.allSettled(
|
|
85865
|
+
const enabledPlatforms2 = Array.from(platforms.entries()).filter(([id]) => platformEnabledState.get(id) ?? true);
|
|
85866
|
+
const disabledCount = platforms.size - enabledPlatforms2.length;
|
|
85867
|
+
ui.addLog({ level: "info", component: "init", message: `Connecting ${enabledPlatforms2.length} platform(s)...${disabledCount > 0 ? ` (${disabledCount} disabled)` : ""}` });
|
|
85868
|
+
const connectionResults = await Promise.allSettled(enabledPlatforms2.map(async ([id, client]) => {
|
|
85560
85869
|
ui.addLog({ level: "debug", component: "init", message: `Connecting to ${id}...` });
|
|
85561
85870
|
try {
|
|
85562
85871
|
await client.connect();
|