claude-threads 1.33.0 → 1.33.1
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 +7 -0
- package/dist/index.js +811 -755
- package/dist/mcp/mcp-server.js +165 -144
- package/docs/CONFIGURATION.md +8 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4336,10 +4336,10 @@ __export(exports_worktree, {
|
|
|
4336
4336
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4337
4337
|
import * as path from "path";
|
|
4338
4338
|
import * as fs from "fs/promises";
|
|
4339
|
-
import { homedir as
|
|
4339
|
+
import { homedir as homedir5 } from "os";
|
|
4340
4340
|
async function execGit(args, cwd) {
|
|
4341
4341
|
const cmd = `git ${args.join(" ")}`;
|
|
4342
|
-
|
|
4342
|
+
log9.debug(`Executing: ${cmd}`);
|
|
4343
4343
|
return new Promise((resolve6, reject) => {
|
|
4344
4344
|
const proc = crossSpawn("git", args, { cwd });
|
|
4345
4345
|
let stdout = "";
|
|
@@ -4352,15 +4352,15 @@ async function execGit(args, cwd) {
|
|
|
4352
4352
|
});
|
|
4353
4353
|
proc.on("close", (code) => {
|
|
4354
4354
|
if (code === 0) {
|
|
4355
|
-
|
|
4355
|
+
log9.debug(`${cmd} → success`);
|
|
4356
4356
|
resolve6(stdout.trim());
|
|
4357
4357
|
} else {
|
|
4358
|
-
|
|
4358
|
+
log9.debug(`${cmd} → failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
|
|
4359
4359
|
reject(new Error(`git ${args.join(" ")} failed: ${stderr || stdout}`));
|
|
4360
4360
|
}
|
|
4361
4361
|
});
|
|
4362
4362
|
proc.on("error", (err) => {
|
|
4363
|
-
|
|
4363
|
+
log9.warn(`${cmd} → error: ${err}`);
|
|
4364
4364
|
reject(err);
|
|
4365
4365
|
});
|
|
4366
4366
|
});
|
|
@@ -4370,7 +4370,7 @@ async function isGitRepository(dir) {
|
|
|
4370
4370
|
await execGit(["rev-parse", "--git-dir"], dir);
|
|
4371
4371
|
return true;
|
|
4372
4372
|
} catch (err) {
|
|
4373
|
-
|
|
4373
|
+
log9.debug(`Not a git repository: ${dir} (${err})`);
|
|
4374
4374
|
return false;
|
|
4375
4375
|
}
|
|
4376
4376
|
}
|
|
@@ -4520,7 +4520,7 @@ async function detectWorktreeInfo(workingDir) {
|
|
|
4520
4520
|
const branchOutput = await execGit(["rev-parse", "--abbrev-ref", "HEAD"], workingDir);
|
|
4521
4521
|
const branch = branchOutput?.trim();
|
|
4522
4522
|
if (!branch || branch === "HEAD") {
|
|
4523
|
-
|
|
4523
|
+
log9.debug(`Could not detect branch for worktree at ${workingDir}`);
|
|
4524
4524
|
return null;
|
|
4525
4525
|
}
|
|
4526
4526
|
const toplevel = (await execGit(["rev-parse", "--show-toplevel"], workingDir))?.trim();
|
|
@@ -4528,45 +4528,45 @@ async function detectWorktreeInfo(workingDir) {
|
|
|
4528
4528
|
return null;
|
|
4529
4529
|
}
|
|
4530
4530
|
const repoRoot = await getMainRepositoryRoot(workingDir);
|
|
4531
|
-
|
|
4531
|
+
log9.debug(`Detected worktree: path=${workingDir}, branch=${branch}, repoRoot=${repoRoot}`);
|
|
4532
4532
|
return {
|
|
4533
4533
|
worktreePath: toplevel,
|
|
4534
4534
|
branch,
|
|
4535
4535
|
repoRoot: repoRoot || toplevel
|
|
4536
4536
|
};
|
|
4537
4537
|
} catch (err) {
|
|
4538
|
-
|
|
4538
|
+
log9.debug(`Failed to detect worktree info for ${workingDir}: ${err}`);
|
|
4539
4539
|
return null;
|
|
4540
4540
|
}
|
|
4541
4541
|
}
|
|
4542
4542
|
async function createWorktree(repoRoot, branch, targetDir) {
|
|
4543
|
-
|
|
4543
|
+
log9.info(`Creating worktree for branch '${branch}' at ${targetDir}`);
|
|
4544
4544
|
const parentDir = path.dirname(targetDir);
|
|
4545
|
-
|
|
4545
|
+
log9.debug(`Creating parent directory: ${parentDir}`);
|
|
4546
4546
|
await fs.mkdir(parentDir, { recursive: true });
|
|
4547
4547
|
const exists = await branchExists(repoRoot, branch);
|
|
4548
4548
|
if (exists) {
|
|
4549
|
-
|
|
4549
|
+
log9.debug(`Branch '${branch}' exists, adding worktree`);
|
|
4550
4550
|
await execGit(["worktree", "add", "--", targetDir, branch], repoRoot);
|
|
4551
4551
|
} else {
|
|
4552
|
-
|
|
4552
|
+
log9.debug(`Branch '${branch}' does not exist, creating with worktree`);
|
|
4553
4553
|
await execGit(["worktree", "add", "-b", branch, "--", targetDir], repoRoot);
|
|
4554
4554
|
}
|
|
4555
|
-
|
|
4555
|
+
log9.info(`Worktree created successfully: ${targetDir}`);
|
|
4556
4556
|
return targetDir;
|
|
4557
4557
|
}
|
|
4558
4558
|
async function removeWorktree(repoRoot, worktreePath) {
|
|
4559
|
-
|
|
4559
|
+
log9.info(`Removing worktree: ${worktreePath}`);
|
|
4560
4560
|
try {
|
|
4561
4561
|
await execGit(["worktree", "remove", worktreePath], repoRoot);
|
|
4562
|
-
|
|
4562
|
+
log9.debug("Worktree removed cleanly");
|
|
4563
4563
|
} catch (err) {
|
|
4564
|
-
|
|
4564
|
+
log9.debug(`Clean remove failed (${err}), trying force remove`);
|
|
4565
4565
|
await execGit(["worktree", "remove", "--force", worktreePath], repoRoot);
|
|
4566
4566
|
}
|
|
4567
|
-
|
|
4567
|
+
log9.debug("Pruning stale worktree references");
|
|
4568
4568
|
await execGit(["worktree", "prune"], repoRoot);
|
|
4569
|
-
|
|
4569
|
+
log9.info("Worktree removed and pruned successfully");
|
|
4570
4570
|
}
|
|
4571
4571
|
async function findWorktreeByBranch(repoRoot, branch) {
|
|
4572
4572
|
const worktrees = await listWorktrees(repoRoot);
|
|
@@ -4609,14 +4609,14 @@ async function writeMetadataStore(store) {
|
|
|
4609
4609
|
await fs.writeFile(METADATA_STORE_PATH, JSON.stringify(store, null, 2), { encoding: "utf-8", mode: 384 });
|
|
4610
4610
|
await fs.chmod(METADATA_STORE_PATH, 384);
|
|
4611
4611
|
} catch (err) {
|
|
4612
|
-
|
|
4612
|
+
log9.warn(`Failed to write worktree metadata store: ${err}`);
|
|
4613
4613
|
}
|
|
4614
4614
|
}
|
|
4615
4615
|
async function writeWorktreeMetadata(worktreePath, metadata) {
|
|
4616
4616
|
const store = await readMetadataStore();
|
|
4617
4617
|
store[worktreePath] = metadata;
|
|
4618
4618
|
await writeMetadataStore(store);
|
|
4619
|
-
|
|
4619
|
+
log9.debug(`Wrote worktree metadata for: ${worktreePath}`);
|
|
4620
4620
|
}
|
|
4621
4621
|
async function readWorktreeMetadata(worktreePath) {
|
|
4622
4622
|
const store = await readMetadataStore();
|
|
@@ -4639,16 +4639,16 @@ async function removeWorktreeMetadata(worktreePath) {
|
|
|
4639
4639
|
if (store[worktreePath]) {
|
|
4640
4640
|
delete store[worktreePath];
|
|
4641
4641
|
await writeMetadataStore(store);
|
|
4642
|
-
|
|
4642
|
+
log9.debug(`Removed worktree metadata for: ${worktreePath}`);
|
|
4643
4643
|
}
|
|
4644
4644
|
}
|
|
4645
|
-
var
|
|
4645
|
+
var log9, WORKTREES_DIR, METADATA_STORE_PATH;
|
|
4646
4646
|
var init_worktree = __esm(() => {
|
|
4647
4647
|
init_spawn();
|
|
4648
4648
|
init_logger();
|
|
4649
|
-
|
|
4650
|
-
WORKTREES_DIR = path.join(
|
|
4651
|
-
METADATA_STORE_PATH = path.join(
|
|
4649
|
+
log9 = createLogger("git-wt");
|
|
4650
|
+
WORKTREES_DIR = path.join(homedir5(), ".claude-threads", "worktrees");
|
|
4651
|
+
METADATA_STORE_PATH = path.join(homedir5(), ".claude-threads", "worktree-metadata.json");
|
|
4652
4652
|
});
|
|
4653
4653
|
|
|
4654
4654
|
// node_modules/graceful-fs/polyfills.js
|
|
@@ -6555,7 +6555,7 @@ var require_minimist = __commonJS((exports, module) => {
|
|
|
6555
6555
|
// node_modules/rc/index.js
|
|
6556
6556
|
var require_rc = __commonJS((exports, module) => {
|
|
6557
6557
|
var cc = require_utils();
|
|
6558
|
-
var
|
|
6558
|
+
var join9 = __require("path").join;
|
|
6559
6559
|
var deepExtend = require_deep_extend();
|
|
6560
6560
|
var etc = "/etc";
|
|
6561
6561
|
var win = process.platform === "win32";
|
|
@@ -6581,15 +6581,15 @@ var require_rc = __commonJS((exports, module) => {
|
|
|
6581
6581
|
}
|
|
6582
6582
|
if (!win)
|
|
6583
6583
|
[
|
|
6584
|
-
|
|
6585
|
-
|
|
6584
|
+
join9(etc, name, "config"),
|
|
6585
|
+
join9(etc, name + "rc")
|
|
6586
6586
|
].forEach(addConfigFile);
|
|
6587
6587
|
if (home)
|
|
6588
6588
|
[
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6589
|
+
join9(home, ".config", name, "config"),
|
|
6590
|
+
join9(home, ".config", name),
|
|
6591
|
+
join9(home, "." + name, "config"),
|
|
6592
|
+
join9(home, "." + name + "rc")
|
|
6593
6593
|
].forEach(addConfigFile);
|
|
6594
6594
|
addConfigFile(cc.find("." + name + "rc"));
|
|
6595
6595
|
if (env3.config)
|
|
@@ -10982,7 +10982,7 @@ async function quickQuery(options) {
|
|
|
10982
10982
|
if (systemPrompt) {
|
|
10983
10983
|
args.push("--system-prompt", systemPrompt);
|
|
10984
10984
|
}
|
|
10985
|
-
|
|
10985
|
+
log19.debug(`Quick query: model=${model}, timeout=${timeout2}ms, prompt="${prompt.substring(0, 50)}..."`);
|
|
10986
10986
|
return new Promise((resolve6) => {
|
|
10987
10987
|
let stdout = "";
|
|
10988
10988
|
let stderr = "";
|
|
@@ -10996,7 +10996,7 @@ async function quickQuery(options) {
|
|
|
10996
10996
|
if (!resolved) {
|
|
10997
10997
|
resolved = true;
|
|
10998
10998
|
proc.kill("SIGTERM");
|
|
10999
|
-
|
|
10999
|
+
log19.debug(`Quick query timed out after ${timeout2}ms`);
|
|
11000
11000
|
resolve6({
|
|
11001
11001
|
success: false,
|
|
11002
11002
|
error: "timeout",
|
|
@@ -11014,7 +11014,7 @@ async function quickQuery(options) {
|
|
|
11014
11014
|
if (!resolved) {
|
|
11015
11015
|
resolved = true;
|
|
11016
11016
|
clearTimeout(timeoutId);
|
|
11017
|
-
|
|
11017
|
+
log19.debug(`Quick query error: ${err.message}`);
|
|
11018
11018
|
resolve6({
|
|
11019
11019
|
success: false,
|
|
11020
11020
|
error: err.message,
|
|
@@ -11028,14 +11028,14 @@ async function quickQuery(options) {
|
|
|
11028
11028
|
clearTimeout(timeoutId);
|
|
11029
11029
|
const durationMs = Date.now() - startTime;
|
|
11030
11030
|
if (code === 0 && stdout.trim()) {
|
|
11031
|
-
|
|
11031
|
+
log19.debug(`Quick query success: ${durationMs}ms, ${stdout.length} chars`);
|
|
11032
11032
|
resolve6({
|
|
11033
11033
|
success: true,
|
|
11034
11034
|
response: stdout.trim(),
|
|
11035
11035
|
durationMs
|
|
11036
11036
|
});
|
|
11037
11037
|
} else {
|
|
11038
|
-
|
|
11038
|
+
log19.debug(`Quick query failed: code=${code}, stderr=${stderr.substring(0, 100)}`);
|
|
11039
11039
|
resolve6({
|
|
11040
11040
|
success: false,
|
|
11041
11041
|
error: stderr || `exit code ${code}`,
|
|
@@ -11045,17 +11045,17 @@ async function quickQuery(options) {
|
|
|
11045
11045
|
}
|
|
11046
11046
|
});
|
|
11047
11047
|
proc.stdin?.on("error", (err) => {
|
|
11048
|
-
|
|
11048
|
+
log19.debug(`quickQuery: stdin write failed (${err.code ?? err.message})`);
|
|
11049
11049
|
});
|
|
11050
11050
|
proc.stdin?.end(prompt);
|
|
11051
11051
|
});
|
|
11052
11052
|
}
|
|
11053
|
-
var
|
|
11053
|
+
var log19;
|
|
11054
11054
|
var init_quick_query = __esm(() => {
|
|
11055
11055
|
init_spawn();
|
|
11056
11056
|
init_version_check();
|
|
11057
11057
|
init_logger();
|
|
11058
|
-
|
|
11058
|
+
log19 = createLogger("query");
|
|
11059
11059
|
});
|
|
11060
11060
|
|
|
11061
11061
|
// node_modules/kleur/index.js
|
|
@@ -52332,9 +52332,13 @@ function buildClaudeChildEnv(parentEnv, account, opts) {
|
|
|
52332
52332
|
env.USERPROFILE = account.home;
|
|
52333
52333
|
delete env.ANTHROPIC_API_KEY;
|
|
52334
52334
|
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
52335
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
52336
|
+
delete env.CLAUDE_CONFIG_DIR;
|
|
52337
|
+
delete env.CLAUDE_SECURESTORAGE_CONFIG_DIR;
|
|
52335
52338
|
} else if (account?.apiKey) {
|
|
52336
52339
|
env.ANTHROPIC_API_KEY = account.apiKey;
|
|
52337
52340
|
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
52341
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
52338
52342
|
}
|
|
52339
52343
|
return env;
|
|
52340
52344
|
}
|
|
@@ -52835,13 +52839,349 @@ class ClaudeCli extends EventEmitter {
|
|
|
52835
52839
|
}
|
|
52836
52840
|
}
|
|
52837
52841
|
|
|
52838
|
-
// src/persistence/
|
|
52842
|
+
// src/persistence/session-store.ts
|
|
52843
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3 } from "fs";
|
|
52844
|
+
|
|
52845
|
+
// src/persistence/atomic-file.ts
|
|
52846
|
+
import { chmodSync as chmodSync3, renameSync, writeFileSync as writeFileSync3 } from "fs";
|
|
52847
|
+
|
|
52848
|
+
class SerialQueue {
|
|
52849
|
+
tail = Promise.resolve();
|
|
52850
|
+
run(fn) {
|
|
52851
|
+
const next = this.tail.then(fn, fn);
|
|
52852
|
+
this.tail = next.catch(() => {
|
|
52853
|
+
return;
|
|
52854
|
+
});
|
|
52855
|
+
return next;
|
|
52856
|
+
}
|
|
52857
|
+
}
|
|
52858
|
+
function writeFileAtomic(file, content) {
|
|
52859
|
+
const tempFile = `${file}.tmp`;
|
|
52860
|
+
writeFileSync3(tempFile, content, { encoding: "utf-8", mode: 384 });
|
|
52861
|
+
renameSync(tempFile, file);
|
|
52862
|
+
chmodSync3(file, 384);
|
|
52863
|
+
}
|
|
52864
|
+
|
|
52865
|
+
// src/persistence/session-store.ts
|
|
52839
52866
|
init_logger();
|
|
52840
|
-
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";
|
|
52841
52867
|
import { homedir as homedir3 } from "os";
|
|
52842
|
-
import { join as join5
|
|
52843
|
-
|
|
52844
|
-
|
|
52868
|
+
import { join as join5 } from "path";
|
|
52869
|
+
|
|
52870
|
+
// src/sponsor.ts
|
|
52871
|
+
var SPONSOR_URL = "https://github.com/sponsors/axolotl-systems";
|
|
52872
|
+
var SESSION_MILESTONES = [100, 250, 500, 1000, 2500, 5000, 1e4];
|
|
52873
|
+
var MILESTONE_VISIBLE_MS = 24 * 60 * 60 * 1000;
|
|
52874
|
+
function milestoneReached(totalSessions) {
|
|
52875
|
+
return SESSION_MILESTONES.includes(totalSessions) ? totalSessions : null;
|
|
52876
|
+
}
|
|
52877
|
+
function milestoneStillFresh(reachedAtIso, nowMs) {
|
|
52878
|
+
const reachedAt = Date.parse(reachedAtIso);
|
|
52879
|
+
if (Number.isNaN(reachedAt))
|
|
52880
|
+
return false;
|
|
52881
|
+
const age = nowMs - reachedAt;
|
|
52882
|
+
return age >= 0 && age < MILESTONE_VISIBLE_MS;
|
|
52883
|
+
}
|
|
52884
|
+
function formatSponsorFooter(formatter) {
|
|
52885
|
+
return formatter.formatItalic(`♥ Support claude-threads: ${formatter.formatLink("github.com/sponsors/axolotl-systems", SPONSOR_URL)}`);
|
|
52886
|
+
}
|
|
52887
|
+
function formatMilestoneLine(formatter, milestone) {
|
|
52888
|
+
return `\uD83C\uDF89 ${formatter.formatBold(`Session #${milestone}`)} on this instance — claude-threads is free & open source ${formatter.formatLink("♥ sponsor", SPONSOR_URL)}`;
|
|
52889
|
+
}
|
|
52890
|
+
|
|
52891
|
+
// src/persistence/session-store.ts
|
|
52892
|
+
var log4 = createLogger("persist");
|
|
52893
|
+
function resolveEndReason(session) {
|
|
52894
|
+
if (!session.cleanedAt)
|
|
52895
|
+
return;
|
|
52896
|
+
return session.endReason ?? "stopped";
|
|
52897
|
+
}
|
|
52898
|
+
function isRevivable(session) {
|
|
52899
|
+
return resolveEndReason(session) !== "stopped";
|
|
52900
|
+
}
|
|
52901
|
+
var STORE_VERSION = 2;
|
|
52902
|
+
var DEFAULT_CONFIG_DIR = join5(homedir3(), ".config", "claude-threads");
|
|
52903
|
+
var DEFAULT_SESSIONS_FILE = join5(DEFAULT_CONFIG_DIR, "sessions.json");
|
|
52904
|
+
|
|
52905
|
+
class SessionStore {
|
|
52906
|
+
sessionsFile;
|
|
52907
|
+
configDir;
|
|
52908
|
+
constructor(sessionsPath) {
|
|
52909
|
+
const envPath = process.env.CLAUDE_THREADS_SESSIONS_PATH;
|
|
52910
|
+
const effectivePath = sessionsPath ?? envPath;
|
|
52911
|
+
if (effectivePath) {
|
|
52912
|
+
this.sessionsFile = effectivePath;
|
|
52913
|
+
this.configDir = join5(effectivePath, "..");
|
|
52914
|
+
} else {
|
|
52915
|
+
this.sessionsFile = DEFAULT_SESSIONS_FILE;
|
|
52916
|
+
this.configDir = DEFAULT_CONFIG_DIR;
|
|
52917
|
+
}
|
|
52918
|
+
if (!existsSync4(this.configDir)) {
|
|
52919
|
+
mkdirSync3(this.configDir, { recursive: true });
|
|
52920
|
+
}
|
|
52921
|
+
}
|
|
52922
|
+
load() {
|
|
52923
|
+
const sessions = new Map;
|
|
52924
|
+
if (!existsSync4(this.sessionsFile)) {
|
|
52925
|
+
log4.debug("No sessions file found");
|
|
52926
|
+
return sessions;
|
|
52927
|
+
}
|
|
52928
|
+
try {
|
|
52929
|
+
const data = this.loadRaw();
|
|
52930
|
+
if (data.version === 1) {
|
|
52931
|
+
log4.info("Migrating sessions from v1 to v2 (adding platformId)");
|
|
52932
|
+
const newSessions = {};
|
|
52933
|
+
for (const [_oldKey, session] of Object.entries(data.sessions)) {
|
|
52934
|
+
const v1Session = session;
|
|
52935
|
+
if (!v1Session.platformId) {
|
|
52936
|
+
v1Session.platformId = "default";
|
|
52937
|
+
}
|
|
52938
|
+
const newKey = `${v1Session.platformId}:${v1Session.threadId}`;
|
|
52939
|
+
newSessions[newKey] = v1Session;
|
|
52940
|
+
}
|
|
52941
|
+
data.sessions = newSessions;
|
|
52942
|
+
data.version = 2;
|
|
52943
|
+
this.writeAtomic(data);
|
|
52944
|
+
} else if (data.version !== STORE_VERSION) {
|
|
52945
|
+
log4.warn(`Sessions file version ${data.version} not supported, starting fresh`);
|
|
52946
|
+
return sessions;
|
|
52947
|
+
}
|
|
52948
|
+
for (const session of Object.values(data.sessions)) {
|
|
52949
|
+
if (session.cleanedAt)
|
|
52950
|
+
continue;
|
|
52951
|
+
const sessionId = `${session.platformId}:${session.threadId}`;
|
|
52952
|
+
sessions.set(sessionId, session);
|
|
52953
|
+
}
|
|
52954
|
+
log4.debug(`Loaded ${sessions.size} active session(s)`);
|
|
52955
|
+
} catch (err) {
|
|
52956
|
+
log4.error(`Failed to load sessions: ${err}`);
|
|
52957
|
+
}
|
|
52958
|
+
return sessions;
|
|
52959
|
+
}
|
|
52960
|
+
save(sessionId, session) {
|
|
52961
|
+
const data = this.loadRaw();
|
|
52962
|
+
data.sessions[sessionId] = session;
|
|
52963
|
+
this.writeAtomic(data);
|
|
52964
|
+
const shortId = sessionId.substring(0, 20);
|
|
52965
|
+
log4.debug(`Saved session ${shortId}...`);
|
|
52966
|
+
}
|
|
52967
|
+
remove(sessionId) {
|
|
52968
|
+
const data = this.loadRaw();
|
|
52969
|
+
if (data.sessions[sessionId]) {
|
|
52970
|
+
delete data.sessions[sessionId];
|
|
52971
|
+
this.writeAtomic(data);
|
|
52972
|
+
const shortId = sessionId.substring(0, 20);
|
|
52973
|
+
log4.debug(`Removed session ${shortId}...`);
|
|
52974
|
+
}
|
|
52975
|
+
}
|
|
52976
|
+
softDelete(sessionId, reason) {
|
|
52977
|
+
const data = this.loadRaw();
|
|
52978
|
+
if (data.sessions[sessionId]) {
|
|
52979
|
+
data.sessions[sessionId].cleanedAt = new Date().toISOString();
|
|
52980
|
+
data.sessions[sessionId].endReason = reason;
|
|
52981
|
+
this.writeAtomic(data);
|
|
52982
|
+
const shortId = sessionId.substring(0, 20);
|
|
52983
|
+
log4.debug(`Soft-deleted session ${shortId}...`);
|
|
52984
|
+
}
|
|
52985
|
+
}
|
|
52986
|
+
cleanStale(maxAgeMs) {
|
|
52987
|
+
const data = this.loadRaw();
|
|
52988
|
+
const now = Date.now();
|
|
52989
|
+
const staleIds = [];
|
|
52990
|
+
for (const [sessionId, session] of Object.entries(data.sessions)) {
|
|
52991
|
+
if (session.cleanedAt)
|
|
52992
|
+
continue;
|
|
52993
|
+
if (session.threadId.startsWith("dcm:"))
|
|
52994
|
+
continue;
|
|
52995
|
+
const lastActivity = new Date(session.lastActivityAt).getTime();
|
|
52996
|
+
if (now - lastActivity > maxAgeMs) {
|
|
52997
|
+
staleIds.push(sessionId);
|
|
52998
|
+
session.cleanedAt = new Date().toISOString();
|
|
52999
|
+
session.endReason = "stale";
|
|
53000
|
+
}
|
|
53001
|
+
}
|
|
53002
|
+
if (staleIds.length > 0) {
|
|
53003
|
+
this.writeAtomic(data);
|
|
53004
|
+
log4.debug(`Soft-deleted ${staleIds.length} stale session(s)`);
|
|
53005
|
+
}
|
|
53006
|
+
return staleIds;
|
|
53007
|
+
}
|
|
53008
|
+
cleanHistory(historyRetentionMs = 3 * 24 * 60 * 60 * 1000) {
|
|
53009
|
+
const data = this.loadRaw();
|
|
53010
|
+
const now = Date.now();
|
|
53011
|
+
let removedCount = 0;
|
|
53012
|
+
for (const [sessionId, session] of Object.entries(data.sessions)) {
|
|
53013
|
+
if (!session.cleanedAt)
|
|
53014
|
+
continue;
|
|
53015
|
+
const cleanedTime = new Date(session.cleanedAt).getTime();
|
|
53016
|
+
if (now - cleanedTime > historyRetentionMs) {
|
|
53017
|
+
delete data.sessions[sessionId];
|
|
53018
|
+
removedCount++;
|
|
53019
|
+
}
|
|
53020
|
+
}
|
|
53021
|
+
if (removedCount > 0) {
|
|
53022
|
+
this.writeAtomic(data);
|
|
53023
|
+
log4.debug(`Permanently removed ${removedCount} old session(s) from history`);
|
|
53024
|
+
}
|
|
53025
|
+
return removedCount;
|
|
53026
|
+
}
|
|
53027
|
+
getHistory(platformId, activeSessions) {
|
|
53028
|
+
const data = this.loadRaw();
|
|
53029
|
+
const historySessions = [];
|
|
53030
|
+
for (const [sessionId, session] of Object.entries(data.sessions)) {
|
|
53031
|
+
if (session.platformId !== platformId)
|
|
53032
|
+
continue;
|
|
53033
|
+
if (session.cleanedAt) {
|
|
53034
|
+
historySessions.push(session);
|
|
53035
|
+
continue;
|
|
53036
|
+
}
|
|
53037
|
+
if (session.lifecyclePostId && activeSessions && !activeSessions.has(sessionId)) {
|
|
53038
|
+
historySessions.push(session);
|
|
53039
|
+
}
|
|
53040
|
+
}
|
|
53041
|
+
return historySessions.sort((a, b) => {
|
|
53042
|
+
const aTime = new Date(a.cleanedAt || a.lastActivityAt).getTime();
|
|
53043
|
+
const bTime = new Date(b.cleanedAt || b.lastActivityAt).getTime();
|
|
53044
|
+
return bTime - aTime;
|
|
53045
|
+
});
|
|
53046
|
+
}
|
|
53047
|
+
clear() {
|
|
53048
|
+
const data = this.loadRaw();
|
|
53049
|
+
this.writeAtomic({ version: STORE_VERSION, sessions: {}, stickyPostIds: data.stickyPostIds });
|
|
53050
|
+
log4.debug("Cleared all sessions");
|
|
53051
|
+
}
|
|
53052
|
+
saveStickyPostId(platformId, postId) {
|
|
53053
|
+
const data = this.loadRaw();
|
|
53054
|
+
if (!data.stickyPostIds) {
|
|
53055
|
+
data.stickyPostIds = {};
|
|
53056
|
+
}
|
|
53057
|
+
data.stickyPostIds[platformId] = postId;
|
|
53058
|
+
this.writeAtomic(data);
|
|
53059
|
+
log4.debug(`Saved sticky post ID for ${platformId}: ${postId.substring(0, 8)}...`);
|
|
53060
|
+
}
|
|
53061
|
+
getStickyPostIds() {
|
|
53062
|
+
const data = this.loadRaw();
|
|
53063
|
+
return new Map(Object.entries(data.stickyPostIds || {}));
|
|
53064
|
+
}
|
|
53065
|
+
removeStickyPostId(platformId) {
|
|
53066
|
+
const data = this.loadRaw();
|
|
53067
|
+
if (data.stickyPostIds && data.stickyPostIds[platformId]) {
|
|
53068
|
+
delete data.stickyPostIds[platformId];
|
|
53069
|
+
this.writeAtomic(data);
|
|
53070
|
+
log4.debug(`Removed sticky post ID for ${platformId}`);
|
|
53071
|
+
}
|
|
53072
|
+
}
|
|
53073
|
+
getPlatformEnabledState() {
|
|
53074
|
+
const data = this.loadRaw();
|
|
53075
|
+
return new Map(Object.entries(data.platformEnabledState || {}));
|
|
53076
|
+
}
|
|
53077
|
+
isPlatformEnabled(platformId) {
|
|
53078
|
+
const data = this.loadRaw();
|
|
53079
|
+
return data.platformEnabledState?.[platformId] ?? true;
|
|
53080
|
+
}
|
|
53081
|
+
setPlatformEnabled(platformId, enabled) {
|
|
53082
|
+
const data = this.loadRaw();
|
|
53083
|
+
if (!data.platformEnabledState) {
|
|
53084
|
+
data.platformEnabledState = {};
|
|
53085
|
+
}
|
|
53086
|
+
data.platformEnabledState[platformId] = enabled;
|
|
53087
|
+
this.writeAtomic(data);
|
|
53088
|
+
log4.debug(`Set platform ${platformId} enabled state to ${enabled}`);
|
|
53089
|
+
}
|
|
53090
|
+
findByThread(platformId, threadId) {
|
|
53091
|
+
const sessionId = `${platformId}:${threadId}`;
|
|
53092
|
+
const data = this.loadRaw();
|
|
53093
|
+
return data.sessions[sessionId];
|
|
53094
|
+
}
|
|
53095
|
+
findByThreadIdAnyState(threadId, platformId) {
|
|
53096
|
+
const data = this.loadRaw();
|
|
53097
|
+
for (const session of Object.values(data.sessions)) {
|
|
53098
|
+
if (session.threadId !== threadId)
|
|
53099
|
+
continue;
|
|
53100
|
+
if (platformId !== undefined && session.platformId !== platformId)
|
|
53101
|
+
continue;
|
|
53102
|
+
return session;
|
|
53103
|
+
}
|
|
53104
|
+
return;
|
|
53105
|
+
}
|
|
53106
|
+
findByPostId(platformId, postId) {
|
|
53107
|
+
const data = this.loadRaw();
|
|
53108
|
+
for (const session of Object.values(data.sessions)) {
|
|
53109
|
+
if (session.platformId !== platformId)
|
|
53110
|
+
continue;
|
|
53111
|
+
if (session.lifecyclePostId === postId || session.sessionStartPostId === postId) {
|
|
53112
|
+
return session;
|
|
53113
|
+
}
|
|
53114
|
+
}
|
|
53115
|
+
return;
|
|
53116
|
+
}
|
|
53117
|
+
getStats() {
|
|
53118
|
+
const data = this.loadRaw();
|
|
53119
|
+
return data.stats ?? { totalSessionsStarted: 0 };
|
|
53120
|
+
}
|
|
53121
|
+
recordSessionStarted() {
|
|
53122
|
+
const data = this.loadRaw();
|
|
53123
|
+
const stats = data.stats ?? { totalSessionsStarted: 0 };
|
|
53124
|
+
stats.totalSessionsStarted = (stats.totalSessionsStarted ?? 0) + 1;
|
|
53125
|
+
const milestone = milestoneReached(stats.totalSessionsStarted);
|
|
53126
|
+
if (milestone) {
|
|
53127
|
+
stats.milestone = { n: milestone, reachedAt: new Date().toISOString() };
|
|
53128
|
+
}
|
|
53129
|
+
data.stats = stats;
|
|
53130
|
+
this.writeAtomic(data);
|
|
53131
|
+
return stats.totalSessionsStarted;
|
|
53132
|
+
}
|
|
53133
|
+
lastReadDegraded = false;
|
|
53134
|
+
loadRaw() {
|
|
53135
|
+
if (!existsSync4(this.sessionsFile)) {
|
|
53136
|
+
this.lastReadDegraded = false;
|
|
53137
|
+
return { version: STORE_VERSION, sessions: {} };
|
|
53138
|
+
}
|
|
53139
|
+
try {
|
|
53140
|
+
const raw = readFileSync3(this.sessionsFile, "utf-8");
|
|
53141
|
+
if (raw.trim() === "") {
|
|
53142
|
+
this.lastReadDegraded = false;
|
|
53143
|
+
return { version: STORE_VERSION, sessions: {} };
|
|
53144
|
+
}
|
|
53145
|
+
const data = JSON.parse(raw);
|
|
53146
|
+
if (!data || typeof data !== "object") {
|
|
53147
|
+
this.lastReadDegraded = true;
|
|
53148
|
+
return { version: STORE_VERSION, sessions: {} };
|
|
53149
|
+
}
|
|
53150
|
+
if (data.sessions === undefined || data.sessions === null) {
|
|
53151
|
+
this.lastReadDegraded = false;
|
|
53152
|
+
data.sessions = {};
|
|
53153
|
+
} else if (typeof data.sessions !== "object") {
|
|
53154
|
+
this.lastReadDegraded = true;
|
|
53155
|
+
data.sessions = {};
|
|
53156
|
+
} else {
|
|
53157
|
+
this.lastReadDegraded = false;
|
|
53158
|
+
}
|
|
53159
|
+
if (!data.version) {
|
|
53160
|
+
data.version = STORE_VERSION;
|
|
53161
|
+
}
|
|
53162
|
+
return data;
|
|
53163
|
+
} catch (err) {
|
|
53164
|
+
log4.warn(`Failed to read ${this.sessionsFile}: ${err.message} — reads degrade to empty`);
|
|
53165
|
+
this.lastReadDegraded = true;
|
|
53166
|
+
return { version: STORE_VERSION, sessions: {} };
|
|
53167
|
+
}
|
|
53168
|
+
}
|
|
53169
|
+
writeAtomic(data) {
|
|
53170
|
+
if (this.lastReadDegraded) {
|
|
53171
|
+
log4.error(`Refusing to write ${this.sessionsFile}: the last read of the existing file was degraded — writing would destroy persisted sessions`);
|
|
53172
|
+
return;
|
|
53173
|
+
}
|
|
53174
|
+
writeFileAtomic(this.sessionsFile, JSON.stringify(data, null, 2));
|
|
53175
|
+
}
|
|
53176
|
+
}
|
|
53177
|
+
|
|
53178
|
+
// src/persistence/thread-logger.ts
|
|
53179
|
+
init_logger();
|
|
53180
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, appendFileSync, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync2, rmdirSync, readFileSync as readFileSync4, chmodSync as chmodSync4 } from "fs";
|
|
53181
|
+
import { homedir as homedir4 } from "os";
|
|
53182
|
+
import { join as join6, dirname as dirname3 } from "path";
|
|
53183
|
+
var log5 = createLogger("thread-log");
|
|
53184
|
+
var LOGS_BASE_DIR = join6(homedir4(), ".claude-threads", "logs");
|
|
52845
53185
|
|
|
52846
53186
|
class ThreadLoggerImpl {
|
|
52847
53187
|
platformId;
|
|
@@ -52861,16 +53201,16 @@ class ThreadLoggerImpl {
|
|
|
52861
53201
|
this.enabled = options?.enabled ?? true;
|
|
52862
53202
|
this.bufferSize = options?.bufferSize ?? 10;
|
|
52863
53203
|
this.flushIntervalMs = options?.flushIntervalMs ?? 1000;
|
|
52864
|
-
this.logPath =
|
|
53204
|
+
this.logPath = join6(LOGS_BASE_DIR, platformId, `${claudeSessionId}.jsonl`);
|
|
52865
53205
|
if (this.enabled) {
|
|
52866
53206
|
const dir = dirname3(this.logPath);
|
|
52867
|
-
if (!
|
|
52868
|
-
|
|
53207
|
+
if (!existsSync5(dir)) {
|
|
53208
|
+
mkdirSync4(dir, { recursive: true });
|
|
52869
53209
|
}
|
|
52870
53210
|
this.flushTimer = setInterval(() => {
|
|
52871
53211
|
this.flushSync();
|
|
52872
53212
|
}, this.flushIntervalMs);
|
|
52873
|
-
|
|
53213
|
+
log5.debug(`Thread logger initialized: ${this.logPath}`);
|
|
52874
53214
|
}
|
|
52875
53215
|
}
|
|
52876
53216
|
isEnabled() {
|
|
@@ -52984,7 +53324,7 @@ class ThreadLoggerImpl {
|
|
|
52984
53324
|
this.flushTimer = null;
|
|
52985
53325
|
}
|
|
52986
53326
|
this.flushSync();
|
|
52987
|
-
|
|
53327
|
+
log5.debug(`Thread logger closed: ${this.logPath}`);
|
|
52988
53328
|
}
|
|
52989
53329
|
addEntry(entry) {
|
|
52990
53330
|
this.buffer.push(entry);
|
|
@@ -52999,14 +53339,14 @@ class ThreadLoggerImpl {
|
|
|
52999
53339
|
const lines = this.buffer.map((entry) => JSON.stringify(entry)).join(`
|
|
53000
53340
|
`) + `
|
|
53001
53341
|
`;
|
|
53002
|
-
const isNewFile = !
|
|
53342
|
+
const isNewFile = !existsSync5(this.logPath);
|
|
53003
53343
|
appendFileSync(this.logPath, lines, { encoding: "utf8", mode: 384 });
|
|
53004
53344
|
if (isNewFile) {
|
|
53005
|
-
|
|
53345
|
+
chmodSync4(this.logPath, 384);
|
|
53006
53346
|
}
|
|
53007
53347
|
this.buffer = [];
|
|
53008
53348
|
} catch (err) {
|
|
53009
|
-
|
|
53349
|
+
log5.error(`Failed to flush thread log: ${err}`);
|
|
53010
53350
|
}
|
|
53011
53351
|
}
|
|
53012
53352
|
}
|
|
@@ -53037,13 +53377,13 @@ function createThreadLogger(platformId, threadId, claudeSessionId, options) {
|
|
|
53037
53377
|
function cleanupOldLogs(retentionDays = 30) {
|
|
53038
53378
|
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
|
53039
53379
|
let deletedCount = 0;
|
|
53040
|
-
if (!
|
|
53380
|
+
if (!existsSync5(LOGS_BASE_DIR)) {
|
|
53041
53381
|
return 0;
|
|
53042
53382
|
}
|
|
53043
53383
|
try {
|
|
53044
53384
|
const platformDirs = readdirSync2(LOGS_BASE_DIR);
|
|
53045
53385
|
for (const platformId of platformDirs) {
|
|
53046
|
-
const platformDir =
|
|
53386
|
+
const platformDir = join6(LOGS_BASE_DIR, platformId);
|
|
53047
53387
|
const stat = statSync2(platformDir);
|
|
53048
53388
|
if (!stat.isDirectory())
|
|
53049
53389
|
continue;
|
|
@@ -53051,49 +53391,49 @@ function cleanupOldLogs(retentionDays = 30) {
|
|
|
53051
53391
|
for (const file of logFiles) {
|
|
53052
53392
|
if (!file.endsWith(".jsonl"))
|
|
53053
53393
|
continue;
|
|
53054
|
-
const filePath =
|
|
53394
|
+
const filePath = join6(platformDir, file);
|
|
53055
53395
|
try {
|
|
53056
53396
|
const fileStat = statSync2(filePath);
|
|
53057
53397
|
if (fileStat.mtimeMs < cutoffMs) {
|
|
53058
53398
|
unlinkSync2(filePath);
|
|
53059
53399
|
deletedCount++;
|
|
53060
|
-
|
|
53400
|
+
log5.debug(`Deleted old log file: ${filePath}`);
|
|
53061
53401
|
}
|
|
53062
53402
|
} catch (err) {
|
|
53063
|
-
|
|
53403
|
+
log5.warn(`Failed to check/delete log file ${filePath}: ${err}`);
|
|
53064
53404
|
}
|
|
53065
53405
|
}
|
|
53066
53406
|
try {
|
|
53067
53407
|
const remaining = readdirSync2(platformDir);
|
|
53068
53408
|
if (remaining.length === 0) {
|
|
53069
53409
|
rmdirSync(platformDir);
|
|
53070
|
-
|
|
53410
|
+
log5.debug(`Removed empty platform log directory: ${platformDir}`);
|
|
53071
53411
|
}
|
|
53072
53412
|
} catch {}
|
|
53073
53413
|
}
|
|
53074
53414
|
if (deletedCount > 0) {
|
|
53075
|
-
|
|
53415
|
+
log5.info(`Cleaned up ${deletedCount} old log file(s)`);
|
|
53076
53416
|
}
|
|
53077
53417
|
} catch (err) {
|
|
53078
|
-
|
|
53418
|
+
log5.error(`Failed to clean up old logs: ${err}`);
|
|
53079
53419
|
}
|
|
53080
53420
|
return deletedCount;
|
|
53081
53421
|
}
|
|
53082
53422
|
function getLogFilePath(platformId, sessionId) {
|
|
53083
|
-
return
|
|
53423
|
+
return join6(LOGS_BASE_DIR, platformId, `${sessionId}.jsonl`);
|
|
53084
53424
|
}
|
|
53085
53425
|
function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
|
|
53086
53426
|
const logPath = getLogFilePath(platformId, sessionId);
|
|
53087
|
-
|
|
53088
|
-
if (!
|
|
53089
|
-
|
|
53427
|
+
log5.debug(`Reading log entries from: ${logPath}`);
|
|
53428
|
+
if (!existsSync5(logPath)) {
|
|
53429
|
+
log5.debug(`Log file does not exist: ${logPath}`);
|
|
53090
53430
|
return [];
|
|
53091
53431
|
}
|
|
53092
53432
|
try {
|
|
53093
|
-
const content =
|
|
53433
|
+
const content = readFileSync4(logPath, "utf8");
|
|
53094
53434
|
const lines = content.trim().split(`
|
|
53095
53435
|
`);
|
|
53096
|
-
|
|
53436
|
+
log5.debug(`Log file has ${lines.length} lines`);
|
|
53097
53437
|
const recentLines = lines.slice(-maxLines);
|
|
53098
53438
|
const entries = [];
|
|
53099
53439
|
for (const line of recentLines) {
|
|
@@ -53103,16 +53443,16 @@ function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
|
|
|
53103
53443
|
entries.push(JSON.parse(line));
|
|
53104
53444
|
} catch {}
|
|
53105
53445
|
}
|
|
53106
|
-
|
|
53446
|
+
log5.debug(`Parsed ${entries.length} log entries`);
|
|
53107
53447
|
return entries;
|
|
53108
53448
|
} catch (err) {
|
|
53109
|
-
|
|
53449
|
+
log5.error(`Failed to read log file: ${err}`);
|
|
53110
53450
|
return [];
|
|
53111
53451
|
}
|
|
53112
53452
|
}
|
|
53113
53453
|
|
|
53114
53454
|
// src/version.ts
|
|
53115
|
-
import { readFileSync as
|
|
53455
|
+
import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
|
|
53116
53456
|
import { dirname as dirname4, resolve as resolve3 } from "path";
|
|
53117
53457
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
53118
53458
|
var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
|
|
@@ -53123,9 +53463,9 @@ function loadPackageJson() {
|
|
|
53123
53463
|
resolve3(process.cwd(), "package.json")
|
|
53124
53464
|
];
|
|
53125
53465
|
for (const candidate of candidates) {
|
|
53126
|
-
if (
|
|
53466
|
+
if (existsSync6(candidate)) {
|
|
53127
53467
|
try {
|
|
53128
|
-
const pkg = JSON.parse(
|
|
53468
|
+
const pkg = JSON.parse(readFileSync5(candidate, "utf-8"));
|
|
53129
53469
|
if (pkg.name === "claude-threads") {
|
|
53130
53470
|
return { version: pkg.version, name: pkg.name };
|
|
53131
53471
|
}
|
|
@@ -53536,27 +53876,6 @@ function parseCommandWithRemainder(text) {
|
|
|
53536
53876
|
}
|
|
53537
53877
|
return null;
|
|
53538
53878
|
}
|
|
53539
|
-
// src/sponsor.ts
|
|
53540
|
-
var SPONSOR_URL = "https://github.com/sponsors/axolotl-systems";
|
|
53541
|
-
var SESSION_MILESTONES = [100, 250, 500, 1000, 2500, 5000, 1e4];
|
|
53542
|
-
var MILESTONE_VISIBLE_MS = 24 * 60 * 60 * 1000;
|
|
53543
|
-
function milestoneReached(totalSessions) {
|
|
53544
|
-
return SESSION_MILESTONES.includes(totalSessions) ? totalSessions : null;
|
|
53545
|
-
}
|
|
53546
|
-
function milestoneStillFresh(reachedAtIso, nowMs) {
|
|
53547
|
-
const reachedAt = Date.parse(reachedAtIso);
|
|
53548
|
-
if (Number.isNaN(reachedAt))
|
|
53549
|
-
return false;
|
|
53550
|
-
const age = nowMs - reachedAt;
|
|
53551
|
-
return age >= 0 && age < MILESTONE_VISIBLE_MS;
|
|
53552
|
-
}
|
|
53553
|
-
function formatSponsorFooter(formatter) {
|
|
53554
|
-
return formatter.formatItalic(`♥ Support claude-threads: ${formatter.formatLink("github.com/sponsors/axolotl-systems", SPONSOR_URL)}`);
|
|
53555
|
-
}
|
|
53556
|
-
function formatMilestoneLine(formatter, milestone) {
|
|
53557
|
-
return `\uD83C\uDF89 ${formatter.formatBold(`Session #${milestone}`)} on this instance — claude-threads is free & open source ${formatter.formatLink("♥ sponsor", SPONSOR_URL)}`;
|
|
53558
|
-
}
|
|
53559
|
-
|
|
53560
53879
|
// src/commands/help-generator.ts
|
|
53561
53880
|
function formatCommandRows(cmd, code) {
|
|
53562
53881
|
const rows = [];
|
|
@@ -53594,7 +53913,7 @@ ${formatter.formatBold("Reactions:")}
|
|
|
53594
53913
|
}
|
|
53595
53914
|
|
|
53596
53915
|
// src/changelog.ts
|
|
53597
|
-
import { readFileSync as
|
|
53916
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
|
|
53598
53917
|
import { dirname as dirname5, resolve as resolve4 } from "path";
|
|
53599
53918
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
53600
53919
|
var __dirname3 = dirname5(fileURLToPath3(import.meta.url));
|
|
@@ -53605,7 +53924,7 @@ function getReleaseNotes(version) {
|
|
|
53605
53924
|
];
|
|
53606
53925
|
let changelogPath = null;
|
|
53607
53926
|
for (const p of possiblePaths) {
|
|
53608
|
-
if (
|
|
53927
|
+
if (existsSync7(p)) {
|
|
53609
53928
|
changelogPath = p;
|
|
53610
53929
|
break;
|
|
53611
53930
|
}
|
|
@@ -53614,7 +53933,7 @@ function getReleaseNotes(version) {
|
|
|
53614
53933
|
return null;
|
|
53615
53934
|
}
|
|
53616
53935
|
try {
|
|
53617
|
-
const content =
|
|
53936
|
+
const content = readFileSync6(changelogPath, "utf-8");
|
|
53618
53937
|
return parseChangelog(content, version);
|
|
53619
53938
|
} catch {
|
|
53620
53939
|
return null;
|
|
@@ -54122,7 +54441,7 @@ async function handleDynamicSlashCommand(command, args, ctx) {
|
|
|
54122
54441
|
}
|
|
54123
54442
|
// src/commands/system-prompt-generator.ts
|
|
54124
54443
|
init_logger();
|
|
54125
|
-
var
|
|
54444
|
+
var log6 = createLogger("system-prompt");
|
|
54126
54445
|
function formatUserCommand(cmd) {
|
|
54127
54446
|
const cmdStr = cmd.args ? `\`!${cmd.command} ${cmd.args}\`` : `\`!${cmd.command}\``;
|
|
54128
54447
|
const description = cmd.description;
|
|
@@ -54161,7 +54480,7 @@ async function resolveCollaborators(platform, platformId, ownerUsername, allowed
|
|
|
54161
54480
|
continue;
|
|
54162
54481
|
const email = githubEmailsStore.get(platformId, username);
|
|
54163
54482
|
if (!email) {
|
|
54164
|
-
|
|
54483
|
+
log6.debug(`Collaborator @${username} has no registered GitHub noreply email — skipping`);
|
|
54165
54484
|
continue;
|
|
54166
54485
|
}
|
|
54167
54486
|
let name = username;
|
|
@@ -54170,7 +54489,7 @@ async function resolveCollaborators(platform, platformId, ownerUsername, allowed
|
|
|
54170
54489
|
if (user)
|
|
54171
54490
|
name = user.displayName || user.username;
|
|
54172
54491
|
} catch (err) {
|
|
54173
|
-
|
|
54492
|
+
log6.debug(`Display name lookup failed for @${username}: ${err.message}`);
|
|
54174
54493
|
}
|
|
54175
54494
|
resolved.push({ username, name, email });
|
|
54176
54495
|
}
|
|
@@ -54300,12 +54619,12 @@ ${avoidCommands.map((c) => `- \`!${c.command}\` - ${c.reason}`).join(`
|
|
|
54300
54619
|
}
|
|
54301
54620
|
// src/session/lifecycle.ts
|
|
54302
54621
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
54303
|
-
import { existsSync as
|
|
54622
|
+
import { existsSync as existsSync12 } from "fs";
|
|
54304
54623
|
|
|
54305
54624
|
// src/utils/keep-alive.ts
|
|
54306
54625
|
init_logger();
|
|
54307
54626
|
import { spawn } from "child_process";
|
|
54308
|
-
var
|
|
54627
|
+
var log7 = createLogger("keepalive");
|
|
54309
54628
|
function keepAliveSpawnSpec(platform, parentPid) {
|
|
54310
54629
|
switch (platform) {
|
|
54311
54630
|
case "darwin":
|
|
@@ -54361,7 +54680,7 @@ class KeepAliveManager {
|
|
|
54361
54680
|
if (!enabled && this.keepAliveProcess) {
|
|
54362
54681
|
this.stopKeepAlive();
|
|
54363
54682
|
}
|
|
54364
|
-
|
|
54683
|
+
log7.debug(`Keep-alive ${enabled ? "enabled" : "disabled"}`);
|
|
54365
54684
|
}
|
|
54366
54685
|
isEnabled() {
|
|
54367
54686
|
return this.enabled;
|
|
@@ -54371,7 +54690,7 @@ class KeepAliveManager {
|
|
|
54371
54690
|
}
|
|
54372
54691
|
sessionStarted() {
|
|
54373
54692
|
this.activeSessionCount++;
|
|
54374
|
-
|
|
54693
|
+
log7.debug(`Session started (${this.activeSessionCount} active)`);
|
|
54375
54694
|
if (this.activeSessionCount === 1) {
|
|
54376
54695
|
this.startKeepAlive();
|
|
54377
54696
|
}
|
|
@@ -54380,7 +54699,7 @@ class KeepAliveManager {
|
|
|
54380
54699
|
if (this.activeSessionCount > 0) {
|
|
54381
54700
|
this.activeSessionCount--;
|
|
54382
54701
|
}
|
|
54383
|
-
|
|
54702
|
+
log7.debug(`Session ended (${this.activeSessionCount} active)`);
|
|
54384
54703
|
if (this.activeSessionCount === 0) {
|
|
54385
54704
|
this.stopKeepAlive();
|
|
54386
54705
|
}
|
|
@@ -54394,11 +54713,11 @@ class KeepAliveManager {
|
|
|
54394
54713
|
}
|
|
54395
54714
|
startKeepAlive() {
|
|
54396
54715
|
if (!this.enabled) {
|
|
54397
|
-
|
|
54716
|
+
log7.debug("Keep-alive disabled, skipping");
|
|
54398
54717
|
return;
|
|
54399
54718
|
}
|
|
54400
54719
|
if (this.keepAliveProcess) {
|
|
54401
|
-
|
|
54720
|
+
log7.debug("Keep-alive already running");
|
|
54402
54721
|
return;
|
|
54403
54722
|
}
|
|
54404
54723
|
switch (this.platform) {
|
|
@@ -54412,12 +54731,12 @@ class KeepAliveManager {
|
|
|
54412
54731
|
this.startWindowsKeepAlive();
|
|
54413
54732
|
break;
|
|
54414
54733
|
default:
|
|
54415
|
-
|
|
54734
|
+
log7.warn(`Keep-alive not supported on ${this.platform}`);
|
|
54416
54735
|
}
|
|
54417
54736
|
}
|
|
54418
54737
|
stopKeepAlive() {
|
|
54419
54738
|
if (this.keepAliveProcess) {
|
|
54420
|
-
|
|
54739
|
+
log7.debug("Stopping keep-alive");
|
|
54421
54740
|
this.keepAliveProcess.kill();
|
|
54422
54741
|
this.keepAliveProcess = null;
|
|
54423
54742
|
}
|
|
@@ -54432,18 +54751,18 @@ class KeepAliveManager {
|
|
|
54432
54751
|
detached: false
|
|
54433
54752
|
});
|
|
54434
54753
|
this.keepAliveProcess.on("error", (err) => {
|
|
54435
|
-
|
|
54754
|
+
log7.error(`Failed to start caffeinate: ${err.message}`);
|
|
54436
54755
|
this.keepAliveProcess = null;
|
|
54437
54756
|
});
|
|
54438
54757
|
this.keepAliveProcess.on("exit", (code) => {
|
|
54439
54758
|
if (code !== null && code !== 0 && this.activeSessionCount > 0) {
|
|
54440
|
-
|
|
54759
|
+
log7.debug(`caffeinate exited with code ${code}`);
|
|
54441
54760
|
}
|
|
54442
54761
|
this.keepAliveProcess = null;
|
|
54443
54762
|
});
|
|
54444
|
-
|
|
54763
|
+
log7.info("Sleep prevention active (caffeinate)");
|
|
54445
54764
|
} catch (err) {
|
|
54446
|
-
|
|
54765
|
+
log7.error(`Failed to start caffeinate: ${err}`);
|
|
54447
54766
|
}
|
|
54448
54767
|
}
|
|
54449
54768
|
startLinuxKeepAlive() {
|
|
@@ -54456,19 +54775,19 @@ class KeepAliveManager {
|
|
|
54456
54775
|
detached: false
|
|
54457
54776
|
});
|
|
54458
54777
|
this.keepAliveProcess.on("error", (err) => {
|
|
54459
|
-
|
|
54778
|
+
log7.debug(`systemd-inhibit not available: ${err.message}`);
|
|
54460
54779
|
this.keepAliveProcess = null;
|
|
54461
54780
|
this.startLinuxKeepAliveFallback();
|
|
54462
54781
|
});
|
|
54463
54782
|
this.keepAliveProcess.on("exit", (code) => {
|
|
54464
54783
|
if (code !== null && code !== 0 && this.activeSessionCount > 0) {
|
|
54465
|
-
|
|
54784
|
+
log7.debug(`systemd-inhibit exited with code ${code}`);
|
|
54466
54785
|
}
|
|
54467
54786
|
this.keepAliveProcess = null;
|
|
54468
54787
|
});
|
|
54469
|
-
|
|
54788
|
+
log7.info("Sleep prevention active (systemd-inhibit)");
|
|
54470
54789
|
} catch (err) {
|
|
54471
|
-
|
|
54790
|
+
log7.debug(`Failed to start systemd-inhibit: ${err}`);
|
|
54472
54791
|
this.startLinuxKeepAliveFallback();
|
|
54473
54792
|
}
|
|
54474
54793
|
}
|
|
@@ -54479,15 +54798,15 @@ class KeepAliveManager {
|
|
|
54479
54798
|
detached: false
|
|
54480
54799
|
});
|
|
54481
54800
|
this.keepAliveProcess.on("error", (err) => {
|
|
54482
|
-
|
|
54801
|
+
log7.warn(`Linux keep-alive fallback not available: ${err.message}`);
|
|
54483
54802
|
this.keepAliveProcess = null;
|
|
54484
54803
|
});
|
|
54485
54804
|
this.keepAliveProcess.on("exit", () => {
|
|
54486
54805
|
this.keepAliveProcess = null;
|
|
54487
54806
|
});
|
|
54488
|
-
|
|
54807
|
+
log7.info("Sleep prevention active (xdg-screensaver)");
|
|
54489
54808
|
} catch (err) {
|
|
54490
|
-
|
|
54809
|
+
log7.warn(`Linux keep-alive not available: ${err}`);
|
|
54491
54810
|
}
|
|
54492
54811
|
}
|
|
54493
54812
|
startWindowsKeepAlive() {
|
|
@@ -54499,18 +54818,18 @@ class KeepAliveManager {
|
|
|
54499
54818
|
windowsHide: true
|
|
54500
54819
|
});
|
|
54501
54820
|
this.keepAliveProcess.on("error", (err) => {
|
|
54502
|
-
|
|
54821
|
+
log7.warn(`Windows keep-alive not available: ${err.message}`);
|
|
54503
54822
|
this.keepAliveProcess = null;
|
|
54504
54823
|
});
|
|
54505
54824
|
this.keepAliveProcess.on("exit", (code) => {
|
|
54506
54825
|
if (code !== null && code !== 0 && this.activeSessionCount > 0) {
|
|
54507
|
-
|
|
54826
|
+
log7.debug(`PowerShell keep-alive exited with code ${code}`);
|
|
54508
54827
|
}
|
|
54509
54828
|
this.keepAliveProcess = null;
|
|
54510
54829
|
});
|
|
54511
|
-
|
|
54830
|
+
log7.info("Sleep prevention active (SetThreadExecutionState)");
|
|
54512
54831
|
} catch (err) {
|
|
54513
|
-
|
|
54832
|
+
log7.warn(`Windows keep-alive not available: ${err}`);
|
|
54514
54833
|
}
|
|
54515
54834
|
}
|
|
54516
54835
|
}
|
|
@@ -54585,7 +54904,7 @@ function singleLine(text) {
|
|
|
54585
54904
|
}
|
|
54586
54905
|
|
|
54587
54906
|
// src/utils/error-handler/index.ts
|
|
54588
|
-
var
|
|
54907
|
+
var log8 = createLogger("error");
|
|
54589
54908
|
|
|
54590
54909
|
class SessionError extends Error {
|
|
54591
54910
|
sessionId;
|
|
@@ -54611,19 +54930,19 @@ async function handleError(error, context, severity = "recoverable") {
|
|
|
54611
54930
|
const sessionPart = sessionId ? ` (${formatShortId(sessionId)})` : "";
|
|
54612
54931
|
const logMessage = `${context.action}${sessionPart}: ${message}`;
|
|
54613
54932
|
if (severity === "recoverable") {
|
|
54614
|
-
|
|
54933
|
+
log8.warn(logMessage);
|
|
54615
54934
|
} else {
|
|
54616
|
-
|
|
54935
|
+
log8.error(logMessage, error instanceof Error ? error : undefined);
|
|
54617
54936
|
}
|
|
54618
54937
|
if (context.details) {
|
|
54619
|
-
|
|
54938
|
+
log8.debugJson("Error details", context.details);
|
|
54620
54939
|
}
|
|
54621
54940
|
if (context.notifyUser && context.session) {
|
|
54622
54941
|
try {
|
|
54623
54942
|
const fmt = context.session.platform.getFormatter();
|
|
54624
54943
|
await context.session.platform.createPost(`⚠️ ${fmt.formatBold("Error")}: ${context.action} failed - ${message}`, context.session.threadId);
|
|
54625
54944
|
} catch (notifyError) {
|
|
54626
|
-
|
|
54945
|
+
log8.warn(`Could not notify user: ${notifyError}`);
|
|
54627
54946
|
}
|
|
54628
54947
|
}
|
|
54629
54948
|
if (severity === "session-fatal" || severity === "system-fatal") {
|
|
@@ -54650,7 +54969,7 @@ async function logAndNotify(error, context) {
|
|
|
54650
54969
|
}
|
|
54651
54970
|
function logSilentError(context, error) {
|
|
54652
54971
|
const message = error instanceof Error ? error.message : String(error);
|
|
54653
|
-
|
|
54972
|
+
log8.debug(`[${context}] Silently caught: ${message}`);
|
|
54654
54973
|
}
|
|
54655
54974
|
|
|
54656
54975
|
// src/session/lifecycle.ts
|
|
@@ -54670,8 +54989,8 @@ function createSessionLog(baseLog) {
|
|
|
54670
54989
|
init_logger();
|
|
54671
54990
|
init_emoji();
|
|
54672
54991
|
init_worktree();
|
|
54673
|
-
var
|
|
54674
|
-
var sessionLog = createSessionLog(
|
|
54992
|
+
var log10 = createLogger("helpers");
|
|
54993
|
+
var sessionLog = createSessionLog(log10);
|
|
54675
54994
|
var POST_TYPES = {
|
|
54676
54995
|
info: "",
|
|
54677
54996
|
success: "✅",
|
|
@@ -54759,7 +55078,7 @@ function updateLastMessage(session, post2) {
|
|
|
54759
55078
|
init_logger();
|
|
54760
55079
|
import { lstat, mkdir as mkdir2, mkdtemp, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
54761
55080
|
import { tmpdir as tmpdir3 } from "os";
|
|
54762
|
-
import { join as
|
|
55081
|
+
import { join as join8 } from "path";
|
|
54763
55082
|
|
|
54764
55083
|
// src/utils/safe-filename.ts
|
|
54765
55084
|
import { basename as basename2 } from "path";
|
|
@@ -54800,13 +55119,13 @@ function formatBytes(bytes) {
|
|
|
54800
55119
|
}
|
|
54801
55120
|
|
|
54802
55121
|
// src/operations/streaming/handler.ts
|
|
54803
|
-
var
|
|
55122
|
+
var log11 = createLogger("streaming");
|
|
54804
55123
|
var UPLOAD_ROOT_DIR = "claude-threads-uploads";
|
|
54805
55124
|
function safeIdSegment(id) {
|
|
54806
55125
|
return id.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
54807
55126
|
}
|
|
54808
55127
|
function getSessionUploadDir(platformId, threadId) {
|
|
54809
|
-
return
|
|
55128
|
+
return join8(tmpdir3(), UPLOAD_ROOT_DIR, `${safeIdSegment(platformId)}-${safeIdSegment(threadId)}`);
|
|
54810
55129
|
}
|
|
54811
55130
|
async function cleanupSessionUploads(platformId, threadId) {
|
|
54812
55131
|
if (!platformId || !threadId)
|
|
@@ -54815,7 +55134,7 @@ async function cleanupSessionUploads(platformId, threadId) {
|
|
|
54815
55134
|
try {
|
|
54816
55135
|
await rm2(dir, { recursive: true, force: true });
|
|
54817
55136
|
} catch (err) {
|
|
54818
|
-
|
|
55137
|
+
log11.debug(`Upload cleanup for ${platformId}:${threadId} failed (ignored): ${err}`);
|
|
54819
55138
|
}
|
|
54820
55139
|
}
|
|
54821
55140
|
function sanitizeForPrompt(value) {
|
|
@@ -54836,16 +55155,16 @@ async function saveFilesToUploadDir(platform, uploadDir, files, debug = false) {
|
|
|
54836
55155
|
for (const file of files) {
|
|
54837
55156
|
skipped.push({ name: file.name, reason: "Refusing to write under symlinked upload directory" });
|
|
54838
55157
|
}
|
|
54839
|
-
|
|
55158
|
+
log11.error(`Upload dir is a symlink, refusing all writes: ${uploadDir}`);
|
|
54840
55159
|
return { saved, skipped };
|
|
54841
55160
|
}
|
|
54842
|
-
const messageDir = await mkdtemp(
|
|
55161
|
+
const messageDir = await mkdtemp(join8(uploadDir, `${Date.now().toString(36)}-`));
|
|
54843
55162
|
const usedNames = new Set;
|
|
54844
55163
|
for (const file of files) {
|
|
54845
55164
|
try {
|
|
54846
55165
|
const buffer = await platform.downloadFile(file.id);
|
|
54847
55166
|
const safeName = dedupeFilename(sanitizeFilename(file.name), usedNames);
|
|
54848
|
-
const absolutePath =
|
|
55167
|
+
const absolutePath = join8(messageDir, safeName);
|
|
54849
55168
|
await writeFile2(absolutePath, buffer, { mode: 384, flag: "wx" });
|
|
54850
55169
|
saved.push({
|
|
54851
55170
|
originalName: file.name,
|
|
@@ -54854,11 +55173,11 @@ async function saveFilesToUploadDir(platform, uploadDir, files, debug = false) {
|
|
|
54854
55173
|
size: buffer.length
|
|
54855
55174
|
});
|
|
54856
55175
|
if (debug) {
|
|
54857
|
-
|
|
55176
|
+
log11.debug(`Saved ${file.name} → ${absolutePath} (${formatBytes(buffer.length)})`);
|
|
54858
55177
|
}
|
|
54859
55178
|
} catch (err) {
|
|
54860
55179
|
const message = err instanceof Error ? err.message : String(err);
|
|
54861
|
-
|
|
55180
|
+
log11.error(`Failed to save uploaded file ${file.name}: ${message}`);
|
|
54862
55181
|
skipped.push({
|
|
54863
55182
|
name: file.name,
|
|
54864
55183
|
reason: `Download failed: ${message}`
|
|
@@ -54954,7 +55273,7 @@ function buildRestartCliOptions(session, ctx) {
|
|
|
54954
55273
|
// src/operations/commands/handler.ts
|
|
54955
55274
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
54956
55275
|
import { resolve as resolve6 } from "path";
|
|
54957
|
-
import { existsSync as
|
|
55276
|
+
import { existsSync as existsSync11, statSync as statSync4 } from "fs";
|
|
54958
55277
|
|
|
54959
55278
|
// node_modules/update-notifier/update-notifier.js
|
|
54960
55279
|
import process10 from "node:process";
|
|
@@ -55299,9 +55618,9 @@ node_default(Temp.purgeSyncAll);
|
|
|
55299
55618
|
var temp_default = Temp;
|
|
55300
55619
|
|
|
55301
55620
|
// node_modules/atomically/dist/index.js
|
|
55302
|
-
function
|
|
55621
|
+
function writeFileSync4(filePath, data, options = DEFAULT_WRITE_OPTIONS) {
|
|
55303
55622
|
if (isString(options))
|
|
55304
|
-
return
|
|
55623
|
+
return writeFileSync4(filePath, data, { encoding: options });
|
|
55305
55624
|
const timeout = options.timeout ?? DEFAULT_TIMEOUT_SYNC;
|
|
55306
55625
|
const retryOptions = { timeout };
|
|
55307
55626
|
let tempDisposer = null;
|
|
@@ -55629,7 +55948,7 @@ class Configstore {
|
|
|
55629
55948
|
}
|
|
55630
55949
|
if (error.name === "SyntaxError") {
|
|
55631
55950
|
if (this._clearInvalidConfig) {
|
|
55632
|
-
|
|
55951
|
+
writeFileSync4(this._path, "", writeFileOptions);
|
|
55633
55952
|
return {};
|
|
55634
55953
|
}
|
|
55635
55954
|
throw error;
|
|
@@ -55641,7 +55960,7 @@ class Configstore {
|
|
|
55641
55960
|
set all(value) {
|
|
55642
55961
|
try {
|
|
55643
55962
|
import_graceful_fs.default.mkdirSync(path5.dirname(this._path), mkdirOptions);
|
|
55644
|
-
|
|
55963
|
+
writeFileSync4(this._path, JSON.stringify(value, undefined, "\t"), writeFileOptions);
|
|
55645
55964
|
} catch (error) {
|
|
55646
55965
|
handlePermissionError(error);
|
|
55647
55966
|
}
|
|
@@ -58369,9 +58688,9 @@ init_emoji();
|
|
|
58369
58688
|
|
|
58370
58689
|
// src/operations/bug-report/handler.ts
|
|
58371
58690
|
import { execSync as execSync2 } from "child_process";
|
|
58372
|
-
import { writeFileSync as
|
|
58691
|
+
import { writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
58373
58692
|
import { tmpdir as tmpdir4 } from "os";
|
|
58374
|
-
import { join as
|
|
58693
|
+
import { join as join9 } from "path";
|
|
58375
58694
|
|
|
58376
58695
|
// node_modules/@redactpii/node/lib/index.mjs
|
|
58377
58696
|
class Redactor {
|
|
@@ -58952,9 +59271,9 @@ async function createGitHubIssue(title, body, workingDir) {
|
|
|
58952
59271
|
if (!ghStatus.installed || !ghStatus.authenticated) {
|
|
58953
59272
|
throw new Error(ghStatus.error);
|
|
58954
59273
|
}
|
|
58955
|
-
const bodyFile =
|
|
59274
|
+
const bodyFile = join9(tmpdir4(), `bug-body-${Date.now()}.md`);
|
|
58956
59275
|
try {
|
|
58957
|
-
|
|
59276
|
+
writeFileSync5(bodyFile, body, "utf-8");
|
|
58958
59277
|
const cmd = `gh issue create --repo "${GITHUB_REPO}" --title "${escapeShell(title)}" --body-file "${bodyFile}"`;
|
|
58959
59278
|
const result = execSync2(cmd, {
|
|
58960
59279
|
cwd: workingDir,
|
|
@@ -59099,8 +59418,8 @@ function formatUptime(startedAt) {
|
|
|
59099
59418
|
|
|
59100
59419
|
// src/operations/commands/guards.ts
|
|
59101
59420
|
init_logger();
|
|
59102
|
-
var
|
|
59103
|
-
var sessionLog2 = createSessionLog(
|
|
59421
|
+
var log12 = createLogger("commands");
|
|
59422
|
+
var sessionLog2 = createSessionLog(log12);
|
|
59104
59423
|
function auditCommand(session, command, detail, username) {
|
|
59105
59424
|
auditLog(session.platformId, {
|
|
59106
59425
|
threadId: session.threadId,
|
|
@@ -63026,7 +63345,7 @@ class BugReportExecutor extends BaseExecutor {
|
|
|
63026
63345
|
// src/operations/executors/worktree-prompt.ts
|
|
63027
63346
|
init_emoji();
|
|
63028
63347
|
init_logger();
|
|
63029
|
-
var
|
|
63348
|
+
var log13 = createLogger("wt-prompt");
|
|
63030
63349
|
// src/operations/message-manager.ts
|
|
63031
63350
|
init_logger();
|
|
63032
63351
|
|
|
@@ -63106,7 +63425,7 @@ function formatRelativeTime(date) {
|
|
|
63106
63425
|
return `${diffMin} min ago`;
|
|
63107
63426
|
}
|
|
63108
63427
|
// src/operations/message-manager.ts
|
|
63109
|
-
var
|
|
63428
|
+
var log14 = createLogger("msg-mgr");
|
|
63110
63429
|
|
|
63111
63430
|
class MessageManager {
|
|
63112
63431
|
platform;
|
|
@@ -63199,7 +63518,7 @@ class MessageManager {
|
|
|
63199
63518
|
});
|
|
63200
63519
|
}
|
|
63201
63520
|
async handleEvent(event) {
|
|
63202
|
-
const logger =
|
|
63521
|
+
const logger = log14.forSession(this.sessionId);
|
|
63203
63522
|
const transformCtx = {
|
|
63204
63523
|
sessionId: this.sessionId,
|
|
63205
63524
|
formatter: this.platform.getFormatter(),
|
|
@@ -63253,7 +63572,7 @@ class MessageManager {
|
|
|
63253
63572
|
}
|
|
63254
63573
|
}
|
|
63255
63574
|
async executeOperation(op) {
|
|
63256
|
-
const logger =
|
|
63575
|
+
const logger = log14.forSession(this.sessionId);
|
|
63257
63576
|
const ctx = this.getExecutorContext();
|
|
63258
63577
|
try {
|
|
63259
63578
|
if (isContentOp(op)) {
|
|
@@ -63321,7 +63640,7 @@ class MessageManager {
|
|
|
63321
63640
|
threadId: this.threadId,
|
|
63322
63641
|
platform: this.platform,
|
|
63323
63642
|
formatter: this.platform.getFormatter(),
|
|
63324
|
-
logger:
|
|
63643
|
+
logger: log14.forSession(this.sessionId),
|
|
63325
63644
|
postTracker: this.postTracker,
|
|
63326
63645
|
contentBreaker: this.contentBreaker,
|
|
63327
63646
|
threadLogger: this.session.threadLogger,
|
|
@@ -63550,13 +63869,13 @@ class MessageManager {
|
|
|
63550
63869
|
return this.systemExecutor.postSuccess(message, this.getExecutorContext());
|
|
63551
63870
|
}
|
|
63552
63871
|
async prepareForUserMessage() {
|
|
63553
|
-
const logger =
|
|
63872
|
+
const logger = log14.forSession(this.sessionId);
|
|
63554
63873
|
logger.debug("Preparing for new user message");
|
|
63555
63874
|
await this.closeCurrentPost();
|
|
63556
63875
|
await this.bumpTaskList();
|
|
63557
63876
|
}
|
|
63558
63877
|
async handleUserMessage(message, files, username, displayName) {
|
|
63559
|
-
const logger =
|
|
63878
|
+
const logger = log14.forSession(this.sessionId);
|
|
63560
63879
|
if (!this.session.claude.isRunning()) {
|
|
63561
63880
|
logger.debug("Claude not running, ignoring user message");
|
|
63562
63881
|
return false;
|
|
@@ -63599,7 +63918,7 @@ class MessageManager {
|
|
|
63599
63918
|
];
|
|
63600
63919
|
}
|
|
63601
63920
|
async handleReaction(postId, emoji, user, action) {
|
|
63602
|
-
const logger =
|
|
63921
|
+
const logger = log14.forSession(this.sessionId);
|
|
63603
63922
|
const ctx = this.getExecutorContext();
|
|
63604
63923
|
logger.debug(`Routing reaction: postId=${postId}, emoji=${emoji}, user=${user}, action=${action}`);
|
|
63605
63924
|
for (const { name, executor } of this.reactionDispatchList()) {
|
|
@@ -63697,7 +64016,7 @@ class MessageManager {
|
|
|
63697
64016
|
}
|
|
63698
64017
|
// src/operations/sticky-message/handler.ts
|
|
63699
64018
|
init_logger();
|
|
63700
|
-
var
|
|
64019
|
+
var log15 = createLogger("sticky");
|
|
63701
64020
|
var botStartedAt = new Date;
|
|
63702
64021
|
function getPendingPrompts(session) {
|
|
63703
64022
|
const prompts = [];
|
|
@@ -63772,21 +64091,21 @@ function initialize(store) {
|
|
|
63772
64091
|
stickyPostIds.set(platformId, postId);
|
|
63773
64092
|
}
|
|
63774
64093
|
if (persistedIds.size > 0) {
|
|
63775
|
-
|
|
64094
|
+
log15.info(`\uD83D\uDCCC Restored ${persistedIds.size} sticky post ID(s) from persistence`);
|
|
63776
64095
|
}
|
|
63777
64096
|
}
|
|
63778
64097
|
function setPlatformPaused(platformId, paused) {
|
|
63779
64098
|
if (paused) {
|
|
63780
64099
|
pausedPlatforms.set(platformId, true);
|
|
63781
|
-
|
|
64100
|
+
log15.debug(`Platform ${platformId} marked as paused`);
|
|
63782
64101
|
} else {
|
|
63783
64102
|
pausedPlatforms.delete(platformId);
|
|
63784
|
-
|
|
64103
|
+
log15.debug(`Platform ${platformId} marked as active`);
|
|
63785
64104
|
}
|
|
63786
64105
|
}
|
|
63787
64106
|
function setShuttingDown(shuttingDown) {
|
|
63788
64107
|
isShuttingDown = shuttingDown;
|
|
63789
|
-
|
|
64108
|
+
log15.debug(`Bot shutdown state: ${shuttingDown}`);
|
|
63790
64109
|
}
|
|
63791
64110
|
function getTaskContent(session) {
|
|
63792
64111
|
const taskState = session.messageManager?.getTaskListState();
|
|
@@ -63844,7 +64163,7 @@ function formatHistoryEntry(session, formatter, getThreadLink) {
|
|
|
63844
64163
|
const topic = getHistorySessionTopic(session, formatter);
|
|
63845
64164
|
const threadLink = formatter.formatLink(topic, getThreadLink(session.threadId));
|
|
63846
64165
|
const displayName = session.startedByDisplayName || session.startedBy;
|
|
63847
|
-
const isTimedOut =
|
|
64166
|
+
const isTimedOut = isRevivable(session) && session.lifecyclePostId;
|
|
63848
64167
|
const lastActivity = new Date(session.lastActivityAt);
|
|
63849
64168
|
const time = formatRelativeTimeShort(lastActivity);
|
|
63850
64169
|
const prStr = session.pullRequestUrl ? ` · ${formatPullRequestLink(session.pullRequestUrl, formatter)}` : "";
|
|
@@ -64119,12 +64438,12 @@ async function validateLastMessageIds(platform, sessions) {
|
|
|
64119
64438
|
try {
|
|
64120
64439
|
const post2 = await platform.getPost(lastMessageId);
|
|
64121
64440
|
if (!post2) {
|
|
64122
|
-
|
|
64441
|
+
log15.debug(`lastMessageId ${lastMessageId.substring(0, 8)} for session ${session.sessionId} was deleted, clearing`);
|
|
64123
64442
|
session.lastMessageId = undefined;
|
|
64124
64443
|
session.lastMessageTs = undefined;
|
|
64125
64444
|
}
|
|
64126
64445
|
} catch (err) {
|
|
64127
|
-
|
|
64446
|
+
log15.debug(`Failed to validate lastMessageId for session ${session.sessionId}, clearing: ${err}`);
|
|
64128
64447
|
session.lastMessageId = undefined;
|
|
64129
64448
|
session.lastMessageTs = undefined;
|
|
64130
64449
|
}
|
|
@@ -64141,7 +64460,7 @@ async function updateStickyMessageImpl(platform, sessions, config) {
|
|
|
64141
64460
|
hiddenCleanupDone.add(platform.platformId);
|
|
64142
64461
|
const existing = stickyPostIds.get(platform.platformId);
|
|
64143
64462
|
if (existing) {
|
|
64144
|
-
|
|
64463
|
+
log15.info(`sticky[${platform.platformId}] hidden mode: removing leftover ${formatShortId(existing)}`);
|
|
64145
64464
|
try {
|
|
64146
64465
|
await platform.unpinPost(existing);
|
|
64147
64466
|
} catch {}
|
|
@@ -64161,63 +64480,63 @@ async function updateStickyMessageImpl(platform, sessions, config) {
|
|
|
64161
64480
|
return;
|
|
64162
64481
|
}
|
|
64163
64482
|
const platformSessions = [...sessions.values()].filter((s) => s.platformId === platform.platformId);
|
|
64164
|
-
|
|
64483
|
+
log15.debug(`updateStickyMessage for ${platform.platformId}, ${platformSessions.length} sessions`);
|
|
64165
64484
|
for (const s of platformSessions) {
|
|
64166
|
-
|
|
64485
|
+
log15.debug(` - ${s.sessionId}: title="${s.sessionTitle}" firstPrompt="${s.firstPrompt?.substring(0, 30)}..."`);
|
|
64167
64486
|
}
|
|
64168
64487
|
await validateLastMessageIds(platform, platformSessions);
|
|
64169
64488
|
const formatter = platform.getFormatter();
|
|
64170
64489
|
const content = await buildStickyMessage(sessions, platform.platformId, config, formatter, (threadId) => platform.getThreadLink(threadId));
|
|
64171
64490
|
const existingPostId = stickyPostIds.get(platform.platformId);
|
|
64172
64491
|
const shouldBump = needsBump.get(platform.platformId) ?? false;
|
|
64173
|
-
|
|
64492
|
+
log15.debug(`existingPostId: ${existingPostId || "(none)"}, needsBump: ${shouldBump}`);
|
|
64174
64493
|
try {
|
|
64175
64494
|
if (existingPostId && !shouldBump) {
|
|
64176
|
-
|
|
64495
|
+
log15.debug(`Updating existing post in place...`);
|
|
64177
64496
|
try {
|
|
64178
64497
|
await platform.updatePost(existingPostId, content);
|
|
64179
64498
|
try {
|
|
64180
64499
|
await platform.pinPost(existingPostId);
|
|
64181
|
-
|
|
64500
|
+
log15.debug(`Re-pinned post`);
|
|
64182
64501
|
} catch (pinErr) {
|
|
64183
|
-
|
|
64502
|
+
log15.debug(`Re-pin failed (might already be pinned): ${pinErr}`);
|
|
64184
64503
|
}
|
|
64185
|
-
|
|
64504
|
+
log15.debug(`Updated successfully`);
|
|
64186
64505
|
return;
|
|
64187
64506
|
} catch (err) {
|
|
64188
|
-
|
|
64507
|
+
log15.debug(`Update failed, will create new: ${err}`);
|
|
64189
64508
|
}
|
|
64190
64509
|
}
|
|
64191
64510
|
needsBump.set(platform.platformId, false);
|
|
64192
64511
|
if (existingPostId) {
|
|
64193
|
-
|
|
64512
|
+
log15.debug(`Unpinning and deleting existing post ${existingPostId.substring(0, 8)}...`);
|
|
64194
64513
|
try {
|
|
64195
64514
|
await platform.unpinPost(existingPostId);
|
|
64196
|
-
|
|
64515
|
+
log15.debug(`Unpinned successfully`);
|
|
64197
64516
|
} catch (err) {
|
|
64198
|
-
|
|
64517
|
+
log15.debug(`Unpin failed (probably already unpinned): ${err}`);
|
|
64199
64518
|
}
|
|
64200
64519
|
try {
|
|
64201
64520
|
await platform.deletePost(existingPostId);
|
|
64202
|
-
|
|
64521
|
+
log15.debug(`Deleted successfully`);
|
|
64203
64522
|
} catch (err) {
|
|
64204
|
-
|
|
64523
|
+
log15.debug(`Delete failed (probably already deleted): ${err}`);
|
|
64205
64524
|
}
|
|
64206
64525
|
stickyPostIds.delete(platform.platformId);
|
|
64207
64526
|
}
|
|
64208
|
-
|
|
64527
|
+
log15.debug(`Creating new post...`);
|
|
64209
64528
|
const post2 = await platform.createPost(content);
|
|
64210
64529
|
stickyPostIds.set(platform.platformId, post2.id);
|
|
64211
64530
|
try {
|
|
64212
64531
|
await platform.pinPost(post2.id);
|
|
64213
|
-
|
|
64532
|
+
log15.debug(`Pinned post successfully`);
|
|
64214
64533
|
} catch (err) {
|
|
64215
|
-
|
|
64534
|
+
log15.debug(`Failed to pin post: ${err}`);
|
|
64216
64535
|
}
|
|
64217
64536
|
if (sessionStore) {
|
|
64218
64537
|
sessionStore.saveStickyPostId(platform.platformId, post2.id);
|
|
64219
64538
|
}
|
|
64220
|
-
|
|
64539
|
+
log15.info(`\uD83D\uDCCC Created sticky message for ${platform.platformId}: ${formatShortId(post2.id)}`);
|
|
64221
64540
|
const excludePostIds = new Set;
|
|
64222
64541
|
if (sessionStore) {
|
|
64223
64542
|
for (const session of sessionStore.load().values()) {
|
|
@@ -64233,10 +64552,10 @@ async function updateStickyMessageImpl(platform, sessions, config) {
|
|
|
64233
64552
|
}
|
|
64234
64553
|
const botUser = await platform.getBotUser();
|
|
64235
64554
|
cleanupOldStickyMessages(platform, botUser.id, false, excludePostIds).catch((err) => {
|
|
64236
|
-
|
|
64555
|
+
log15.debug(`Background cleanup failed: ${err}`);
|
|
64237
64556
|
});
|
|
64238
64557
|
} catch (err) {
|
|
64239
|
-
|
|
64558
|
+
log15.error(`Failed to update sticky message for ${platform.platformId}`, err instanceof Error ? err : undefined);
|
|
64240
64559
|
}
|
|
64241
64560
|
}
|
|
64242
64561
|
async function updateAllStickyMessages(platforms, sessions, config, overheadByPlatform) {
|
|
@@ -64264,7 +64583,7 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
|
|
|
64264
64583
|
if (!forceRun) {
|
|
64265
64584
|
const lastRun = lastCleanupTime.get(platformId) || 0;
|
|
64266
64585
|
if (now - lastRun < CLEANUP_THROTTLE_MS) {
|
|
64267
|
-
|
|
64586
|
+
log15.debug(`Cleanup throttled for ${platformId} (last run ${Math.round((now - lastRun) / 1000)}s ago)`);
|
|
64268
64587
|
return;
|
|
64269
64588
|
}
|
|
64270
64589
|
}
|
|
@@ -64274,69 +64593,47 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
|
|
|
64274
64593
|
const pinnedPostIds = await platform.getPinnedPosts();
|
|
64275
64594
|
const recentPinnedIds = pinnedPostIds.filter((id) => id !== currentStickyId && !excludePostIds?.has(id) && isRecentPost(id));
|
|
64276
64595
|
if (recentPinnedIds.length === 0) {
|
|
64277
|
-
|
|
64596
|
+
log15.debug(`No recent pinned posts to check (${pinnedPostIds.length} total, current: ${currentStickyId?.substring(0, 8) || "(none)"})`);
|
|
64278
64597
|
return;
|
|
64279
64598
|
}
|
|
64280
|
-
|
|
64599
|
+
log15.debug(`Checking ${recentPinnedIds.length} recent pinned posts (of ${pinnedPostIds.length} total)`);
|
|
64281
64600
|
for (const postId of recentPinnedIds) {
|
|
64282
64601
|
try {
|
|
64283
64602
|
const post2 = await platform.getPost(postId);
|
|
64284
64603
|
if (!post2)
|
|
64285
64604
|
continue;
|
|
64286
64605
|
if (post2.userId === botUserId) {
|
|
64287
|
-
|
|
64606
|
+
log15.debug(`Cleaning up old sticky: ${postId.substring(0, 8)}...`);
|
|
64288
64607
|
try {
|
|
64289
64608
|
await platform.unpinPost(postId);
|
|
64290
64609
|
await platform.deletePost(postId);
|
|
64291
|
-
|
|
64610
|
+
log15.info(`\uD83E\uDDF9 Cleaned up old sticky message: ${postId.substring(0, 8)}...`);
|
|
64292
64611
|
} catch (err) {
|
|
64293
|
-
|
|
64612
|
+
log15.debug(`Failed to cleanup ${postId}: ${err}`);
|
|
64294
64613
|
}
|
|
64295
64614
|
}
|
|
64296
64615
|
} catch (err) {
|
|
64297
|
-
|
|
64616
|
+
log15.debug(`Could not check post ${postId}: ${err}`);
|
|
64298
64617
|
}
|
|
64299
64618
|
}
|
|
64300
64619
|
} catch (err) {
|
|
64301
|
-
|
|
64620
|
+
log15.error(`Failed to cleanup old sticky messages`, err instanceof Error ? err : undefined);
|
|
64302
64621
|
}
|
|
64303
64622
|
}
|
|
64304
64623
|
// src/memory/store.ts
|
|
64305
64624
|
import { createHash } from "crypto";
|
|
64306
64625
|
import {
|
|
64307
|
-
existsSync as
|
|
64308
|
-
mkdirSync as
|
|
64309
|
-
readFileSync as
|
|
64626
|
+
existsSync as existsSync8,
|
|
64627
|
+
mkdirSync as mkdirSync5,
|
|
64628
|
+
readFileSync as readFileSync7,
|
|
64310
64629
|
realpathSync
|
|
64311
64630
|
} from "fs";
|
|
64312
|
-
import { homedir as
|
|
64313
|
-
import { basename as basename3, dirname as dirname7, join as
|
|
64314
|
-
|
|
64315
|
-
// src/persistence/atomic-file.ts
|
|
64316
|
-
import { chmodSync as chmodSync4, renameSync, writeFileSync as writeFileSync5 } from "fs";
|
|
64317
|
-
|
|
64318
|
-
class SerialQueue {
|
|
64319
|
-
tail = Promise.resolve();
|
|
64320
|
-
run(fn) {
|
|
64321
|
-
const next = this.tail.then(fn, fn);
|
|
64322
|
-
this.tail = next.catch(() => {
|
|
64323
|
-
return;
|
|
64324
|
-
});
|
|
64325
|
-
return next;
|
|
64326
|
-
}
|
|
64327
|
-
}
|
|
64328
|
-
function writeFileAtomic(file, content) {
|
|
64329
|
-
const tempFile = `${file}.tmp`;
|
|
64330
|
-
writeFileSync5(tempFile, content, { encoding: "utf-8", mode: 384 });
|
|
64331
|
-
renameSync(tempFile, file);
|
|
64332
|
-
chmodSync4(file, 384);
|
|
64333
|
-
}
|
|
64334
|
-
|
|
64335
|
-
// src/memory/store.ts
|
|
64631
|
+
import { homedir as homedir6 } from "os";
|
|
64632
|
+
import { basename as basename3, dirname as dirname7, join as join10, sep as sep2 } from "path";
|
|
64336
64633
|
init_logger();
|
|
64337
64634
|
init_worktree();
|
|
64338
|
-
var
|
|
64339
|
-
var DEFAULT_ROOT =
|
|
64635
|
+
var log16 = createLogger("memory");
|
|
64636
|
+
var DEFAULT_ROOT = join10(homedir6(), ".config", "claude-threads", "memory");
|
|
64340
64637
|
var CHANNEL_BLOCK_MAX_LINES = 200;
|
|
64341
64638
|
var CHANNEL_BLOCK_MAX_BYTES = 25 * 1024;
|
|
64342
64639
|
var CHANNEL_FILE_MAX_ENTRIES = 400;
|
|
@@ -64401,10 +64698,10 @@ class MemoryStore {
|
|
|
64401
64698
|
return this.root;
|
|
64402
64699
|
}
|
|
64403
64700
|
channelMemoryPath(platformId) {
|
|
64404
|
-
return
|
|
64701
|
+
return join10(this.root, platformSegment(platformId), "channel", "MEMORY.md");
|
|
64405
64702
|
}
|
|
64406
64703
|
repoMemoryDir(platformId, repoKey) {
|
|
64407
|
-
const dir =
|
|
64704
|
+
const dir = join10(this.root, platformSegment(platformId), "repos", repoKey);
|
|
64408
64705
|
this.ensureDir(dir);
|
|
64409
64706
|
return dir;
|
|
64410
64707
|
}
|
|
@@ -64451,7 +64748,7 @@ class MemoryStore {
|
|
|
64451
64748
|
if (result.added.length > 0) {
|
|
64452
64749
|
this.enforceFileCap(lines);
|
|
64453
64750
|
this.writeLines(platformId, lines);
|
|
64454
|
-
|
|
64751
|
+
log16.debug(`Channel memory for ${platformId}: +${result.added.length} entries` + (result.duplicates.length ? ` (${result.duplicates.length} duplicates skipped)` : ""));
|
|
64455
64752
|
}
|
|
64456
64753
|
return result;
|
|
64457
64754
|
});
|
|
@@ -64490,14 +64787,14 @@ class MemoryStore {
|
|
|
64490
64787
|
}
|
|
64491
64788
|
lines.splice(target.lineIndex, 1);
|
|
64492
64789
|
this.writeLines(platformId, lines);
|
|
64493
|
-
|
|
64790
|
+
log16.debug(`Channel memory for ${platformId}: removed one entry`);
|
|
64494
64791
|
return { ok: true, removed: target.entry };
|
|
64495
64792
|
});
|
|
64496
64793
|
}
|
|
64497
64794
|
clearChannel(platformId) {
|
|
64498
64795
|
return this.runExclusive(platformId, () => {
|
|
64499
64796
|
this.writeLines(platformId, []);
|
|
64500
|
-
|
|
64797
|
+
log16.debug(`Channel memory for ${platformId}: cleared`);
|
|
64501
64798
|
});
|
|
64502
64799
|
}
|
|
64503
64800
|
buildChannelMemoryBlock(platformId) {
|
|
@@ -64505,7 +64802,7 @@ class MemoryStore {
|
|
|
64505
64802
|
try {
|
|
64506
64803
|
lines = this.loadLines(platformId);
|
|
64507
64804
|
} catch (err) {
|
|
64508
|
-
|
|
64805
|
+
log16.warn(`Failed to read channel memory for ${platformId}: ${err.message}`);
|
|
64509
64806
|
return null;
|
|
64510
64807
|
}
|
|
64511
64808
|
if (lines.length === 0)
|
|
@@ -64538,9 +64835,9 @@ _(older entries omitted — \`!memory\` shows all)_` : rendered;
|
|
|
64538
64835
|
}
|
|
64539
64836
|
loadLines(platformId) {
|
|
64540
64837
|
const file = this.channelMemoryPath(platformId);
|
|
64541
|
-
if (!
|
|
64838
|
+
if (!existsSync8(file))
|
|
64542
64839
|
return [];
|
|
64543
|
-
const raw =
|
|
64840
|
+
const raw = readFileSync7(file, "utf-8");
|
|
64544
64841
|
const lines = [];
|
|
64545
64842
|
for (const line of raw.split(`
|
|
64546
64843
|
`)) {
|
|
@@ -64580,8 +64877,8 @@ _(older entries omitted — \`!memory\` shows all)_` : rendered;
|
|
|
64580
64877
|
writeFileAtomic(file, content);
|
|
64581
64878
|
}
|
|
64582
64879
|
ensureDir(dir) {
|
|
64583
|
-
if (!
|
|
64584
|
-
|
|
64880
|
+
if (!existsSync8(dir)) {
|
|
64881
|
+
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
64585
64882
|
}
|
|
64586
64883
|
}
|
|
64587
64884
|
}
|
|
@@ -64592,15 +64889,15 @@ async function resolveSessionMemory(memoryStore, memoryConfig, platformId, worki
|
|
|
64592
64889
|
const repoKey = await resolveRepoKey(workingDir, worktreeRepoRoot);
|
|
64593
64890
|
return { autoMemoryDir: memoryStore.repoMemoryDir(platformId, repoKey) };
|
|
64594
64891
|
} catch (err) {
|
|
64595
|
-
|
|
64892
|
+
log16.warn(`Failed to resolve repo memory dir for ${platformId}: ${err.message}`);
|
|
64596
64893
|
return null;
|
|
64597
64894
|
}
|
|
64598
64895
|
}
|
|
64599
64896
|
|
|
64600
64897
|
// src/operations/commands/memory.ts
|
|
64601
64898
|
init_logger();
|
|
64602
|
-
var
|
|
64603
|
-
var sessionLog3 = createSessionLog(
|
|
64899
|
+
var log17 = createLogger("commands");
|
|
64900
|
+
var sessionLog3 = createSessionLog(log17);
|
|
64604
64901
|
async function requireChannelMemory(session, ctx) {
|
|
64605
64902
|
const memoryConfig = ctx.ops.getPlatformMemoryConfig(session.platformId);
|
|
64606
64903
|
if (memoryConfig.enabled && memoryConfig.channelLayer)
|
|
@@ -64723,15 +65020,15 @@ init_emoji();
|
|
|
64723
65020
|
|
|
64724
65021
|
// src/persistence/routines-store.ts
|
|
64725
65022
|
init_logger();
|
|
64726
|
-
import { join as
|
|
65023
|
+
import { join as join12 } from "path";
|
|
64727
65024
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
64728
65025
|
|
|
64729
65026
|
// src/persistence/platform-list-store.ts
|
|
64730
|
-
import { existsSync as
|
|
64731
|
-
import { homedir as
|
|
64732
|
-
import { join as
|
|
64733
|
-
var STORES_CONFIG_DIR =
|
|
64734
|
-
var
|
|
65027
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
|
|
65028
|
+
import { homedir as homedir7 } from "os";
|
|
65029
|
+
import { join as join11 } from "path";
|
|
65030
|
+
var STORES_CONFIG_DIR = join11(homedir7(), ".config", "claude-threads");
|
|
65031
|
+
var STORE_VERSION2 = 1;
|
|
64735
65032
|
|
|
64736
65033
|
class PlatformListStore {
|
|
64737
65034
|
file;
|
|
@@ -64743,12 +65040,12 @@ class PlatformListStore {
|
|
|
64743
65040
|
this.collectionKey = collectionKey;
|
|
64744
65041
|
if (filePath) {
|
|
64745
65042
|
this.file = filePath;
|
|
64746
|
-
this.configDir =
|
|
65043
|
+
this.configDir = join11(filePath, "..");
|
|
64747
65044
|
} else {
|
|
64748
65045
|
this.file = defaultFile;
|
|
64749
65046
|
this.configDir = STORES_CONFIG_DIR;
|
|
64750
65047
|
}
|
|
64751
|
-
|
|
65048
|
+
mkdirSync6(this.configDir, { recursive: true, mode: 448 });
|
|
64752
65049
|
}
|
|
64753
65050
|
list(platformId) {
|
|
64754
65051
|
return structuredClone(this.loadRaw().items[platformId] ?? []);
|
|
@@ -64804,18 +65101,18 @@ class PlatformListStore {
|
|
|
64804
65101
|
return this.queue.run(fn);
|
|
64805
65102
|
}
|
|
64806
65103
|
loadRaw(forWrite = false) {
|
|
64807
|
-
if (!
|
|
65104
|
+
if (!existsSync9(this.file)) {
|
|
64808
65105
|
this.cache = null;
|
|
64809
|
-
return { version:
|
|
65106
|
+
return { version: STORE_VERSION2, items: {} };
|
|
64810
65107
|
}
|
|
64811
65108
|
try {
|
|
64812
65109
|
const stat = statSync3(this.file);
|
|
64813
65110
|
if (this.cache && this.cache.mtimeMs === stat.mtimeMs && this.cache.size === stat.size) {
|
|
64814
65111
|
return this.cache.data;
|
|
64815
65112
|
}
|
|
64816
|
-
const raw =
|
|
65113
|
+
const raw = readFileSync8(this.file, "utf-8");
|
|
64817
65114
|
if (raw.trim() === "") {
|
|
64818
|
-
const data2 = { version:
|
|
65115
|
+
const data2 = { version: STORE_VERSION2, items: {} };
|
|
64819
65116
|
this.cache = { mtimeMs: stat.mtimeMs, size: stat.size, data: data2 };
|
|
64820
65117
|
return data2;
|
|
64821
65118
|
}
|
|
@@ -64828,14 +65125,14 @@ class PlatformListStore {
|
|
|
64828
65125
|
throw new Error(`refusing to write over unreadable ${this.file}: ${problem}`);
|
|
64829
65126
|
}
|
|
64830
65127
|
this.warn(`Failed to read ${this.file}: ${problem} — starting empty`);
|
|
64831
|
-
return { version:
|
|
65128
|
+
return { version: STORE_VERSION2, items: {} };
|
|
64832
65129
|
}
|
|
64833
65130
|
const items = rawItems ?? {};
|
|
64834
65131
|
for (const list of Object.values(items)) {
|
|
64835
65132
|
for (const item of list)
|
|
64836
65133
|
this.applyItemDefaults(item);
|
|
64837
65134
|
}
|
|
64838
|
-
const data = { version: parsed?.version ??
|
|
65135
|
+
const data = { version: parsed?.version ?? STORE_VERSION2, items };
|
|
64839
65136
|
this.cache = { mtimeMs: stat.mtimeMs, size: stat.size, data };
|
|
64840
65137
|
return data;
|
|
64841
65138
|
} catch (err) {
|
|
@@ -64844,7 +65141,7 @@ class PlatformListStore {
|
|
|
64844
65141
|
throw new Error(`refusing to write over unreadable ${this.file}: ${err.message}`, { cause: err });
|
|
64845
65142
|
}
|
|
64846
65143
|
this.warn(`Failed to read ${this.file}: ${err.message} — starting empty`);
|
|
64847
|
-
return { version:
|
|
65144
|
+
return { version: STORE_VERSION2, items: {} };
|
|
64848
65145
|
}
|
|
64849
65146
|
}
|
|
64850
65147
|
writeAtomic(data) {
|
|
@@ -64867,8 +65164,8 @@ class PlatformListStore {
|
|
|
64867
65164
|
}
|
|
64868
65165
|
|
|
64869
65166
|
// src/persistence/routines-store.ts
|
|
64870
|
-
var
|
|
64871
|
-
var DEFAULT_FILE =
|
|
65167
|
+
var log18 = createLogger("routines");
|
|
65168
|
+
var DEFAULT_FILE = join12(STORES_CONFIG_DIR, "routines.yaml");
|
|
64872
65169
|
var MAX_CONSECUTIVE_FAILURES = 3;
|
|
64873
65170
|
var DEFAULT_MAX_ROUTINES = 10;
|
|
64874
65171
|
var SCHEDULE_PRESETS = ["hourly", "daily", "weekdays", "weekly"];
|
|
@@ -64928,10 +65225,10 @@ class RoutinesStore extends PlatformListStore {
|
|
|
64928
65225
|
r.requireApproval = r.requireApproval ?? true;
|
|
64929
65226
|
}
|
|
64930
65227
|
warn(message) {
|
|
64931
|
-
|
|
65228
|
+
log18.warn(message);
|
|
64932
65229
|
}
|
|
64933
65230
|
onRemoved(platformId, routine) {
|
|
64934
|
-
|
|
65231
|
+
log18.info(`Routine "${routine.name}" removed from ${platformId}`);
|
|
64935
65232
|
}
|
|
64936
65233
|
async add(platformId, routine, maxRoutines = DEFAULT_MAX_ROUTINES) {
|
|
64937
65234
|
const result = await this.addItem(platformId, maxRoutines, "routine", () => {
|
|
@@ -64955,7 +65252,7 @@ class RoutinesStore extends PlatformListStore {
|
|
|
64955
65252
|
});
|
|
64956
65253
|
if (!result.ok)
|
|
64957
65254
|
return result;
|
|
64958
|
-
|
|
65255
|
+
log18.info(`Routine "${result.item.name}" created on ${platformId} by @${result.item.createdBy}`);
|
|
64959
65256
|
return { ok: true, routine: result.item };
|
|
64960
65257
|
}
|
|
64961
65258
|
update(platformId, id, patch) {
|
|
@@ -64995,7 +65292,7 @@ async function parseJsonViaHaiku(opts) {
|
|
|
64995
65292
|
}
|
|
64996
65293
|
|
|
64997
65294
|
// src/routines/parser.ts
|
|
64998
|
-
var
|
|
65295
|
+
var log20 = createLogger("routines");
|
|
64999
65296
|
var PARSE_TIMEOUT_MS = 15000;
|
|
65000
65297
|
function hostTimezone() {
|
|
65001
65298
|
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
@@ -65049,7 +65346,7 @@ function parseRoutineRequest(request, defaultTimezone = hostTimezone()) {
|
|
|
65049
65346
|
return parseJsonViaHaiku({
|
|
65050
65347
|
prompt: buildParsePrompt(request, defaultTimezone),
|
|
65051
65348
|
timeoutMs: PARSE_TIMEOUT_MS,
|
|
65052
|
-
logDebug: (m) =>
|
|
65349
|
+
logDebug: (m) => log20.debug(`Routine parse: ${m}`),
|
|
65053
65350
|
unusableMessage: 'could not understand the schedule — try e.g. "every weekday at 9:00, <task>"',
|
|
65054
65351
|
validate: (raw) => validateParsedRoutine(raw, defaultTimezone)
|
|
65055
65352
|
});
|
|
@@ -65057,10 +65354,10 @@ function parseRoutineRequest(request, defaultTimezone = hostTimezone()) {
|
|
|
65057
65354
|
|
|
65058
65355
|
// src/persistence/watches-store.ts
|
|
65059
65356
|
init_logger();
|
|
65060
|
-
import { join as
|
|
65357
|
+
import { join as join13 } from "path";
|
|
65061
65358
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
65062
|
-
var
|
|
65063
|
-
var DEFAULT_FILE2 =
|
|
65359
|
+
var log21 = createLogger("watches");
|
|
65360
|
+
var DEFAULT_FILE2 = join13(STORES_CONFIG_DIR, "watches.yaml");
|
|
65064
65361
|
var MAX_CONSECUTIVE_WATCH_FAILURES = 3;
|
|
65065
65362
|
var DEFAULT_MAX_WATCHES = 10;
|
|
65066
65363
|
var MIN_KEYWORDS = 1;
|
|
@@ -65086,10 +65383,10 @@ class WatchesStore extends PlatformListStore {
|
|
|
65086
65383
|
w.keywords = Array.isArray(w.keywords) ? w.keywords.filter((k) => typeof k === "string").map((k) => singleLine(k).toLowerCase()).filter((k) => k.length > 0) : [];
|
|
65087
65384
|
}
|
|
65088
65385
|
warn(message) {
|
|
65089
|
-
|
|
65386
|
+
log21.warn(message);
|
|
65090
65387
|
}
|
|
65091
65388
|
onRemoved(platformId, watch) {
|
|
65092
|
-
|
|
65389
|
+
log21.info(`Watch "${watch.name}" removed from ${platformId}`);
|
|
65093
65390
|
}
|
|
65094
65391
|
async add(platformId, watch, maxWatches = DEFAULT_MAX_WATCHES) {
|
|
65095
65392
|
const result = await this.addItem(platformId, maxWatches, "watch", () => {
|
|
@@ -65116,7 +65413,7 @@ class WatchesStore extends PlatformListStore {
|
|
|
65116
65413
|
});
|
|
65117
65414
|
if (!result.ok)
|
|
65118
65415
|
return result;
|
|
65119
|
-
|
|
65416
|
+
log21.info(`Watch "${result.item.name}" created on ${platformId} by @${result.item.createdBy}`);
|
|
65120
65417
|
return { ok: true, watch: result.item };
|
|
65121
65418
|
}
|
|
65122
65419
|
update(platformId, id, patch) {
|
|
@@ -65126,7 +65423,7 @@ class WatchesStore extends PlatformListStore {
|
|
|
65126
65423
|
|
|
65127
65424
|
// src/watches/parser.ts
|
|
65128
65425
|
init_logger();
|
|
65129
|
-
var
|
|
65426
|
+
var log22 = createLogger("watches");
|
|
65130
65427
|
var PARSE_TIMEOUT_MS2 = 30000;
|
|
65131
65428
|
function buildWatchParsePrompt(request) {
|
|
65132
65429
|
return `Parse this event-trigger ("watch") request from a chat user into JSON.
|
|
@@ -65161,7 +65458,7 @@ function parseWatchRequest(request) {
|
|
|
65161
65458
|
return parseJsonViaHaiku({
|
|
65162
65459
|
prompt: buildWatchParsePrompt(request),
|
|
65163
65460
|
timeoutMs: PARSE_TIMEOUT_MS2,
|
|
65164
|
-
logDebug: (m) =>
|
|
65461
|
+
logDebug: (m) => log22.debug(`Watch parse: ${m}`),
|
|
65165
65462
|
unusableMessage: "the parsing model returned an unusable answer — try rephrasing",
|
|
65166
65463
|
validate: validateParsedWatch
|
|
65167
65464
|
});
|
|
@@ -65169,8 +65466,8 @@ function parseWatchRequest(request) {
|
|
|
65169
65466
|
|
|
65170
65467
|
// src/operations/commands/automation.ts
|
|
65171
65468
|
init_logger();
|
|
65172
|
-
var
|
|
65173
|
-
var sessionLog4 = createSessionLog(
|
|
65469
|
+
var log23 = createLogger("commands");
|
|
65470
|
+
var sessionLog4 = createSessionLog(log23);
|
|
65174
65471
|
async function refuseInDirectChannelMode(session, message) {
|
|
65175
65472
|
if (!session.platform.directChannelMode?.enabled)
|
|
65176
65473
|
return false;
|
|
@@ -65424,7 +65721,7 @@ init_logger();
|
|
|
65424
65721
|
import { exec as exec3 } from "child_process";
|
|
65425
65722
|
import { promisify as promisify3 } from "util";
|
|
65426
65723
|
var execAsync2 = promisify3(exec3);
|
|
65427
|
-
var
|
|
65724
|
+
var log24 = createLogger("branch");
|
|
65428
65725
|
var SUGGESTION_TIMEOUT = 15000;
|
|
65429
65726
|
var MAX_SUGGESTIONS = 3;
|
|
65430
65727
|
async function getCurrentBranch3(workingDir) {
|
|
@@ -65473,7 +65770,7 @@ function parseBranchSuggestions(response) {
|
|
|
65473
65770
|
return lines.slice(0, MAX_SUGGESTIONS);
|
|
65474
65771
|
}
|
|
65475
65772
|
async function suggestBranchNames(workingDir, userMessage) {
|
|
65476
|
-
|
|
65773
|
+
log24.debug(`Suggesting branch names for: "${userMessage.substring(0, 50)}..."`);
|
|
65477
65774
|
try {
|
|
65478
65775
|
const [currentBranch, recentCommits] = await Promise.all([
|
|
65479
65776
|
getCurrentBranch3(workingDir),
|
|
@@ -65487,14 +65784,14 @@ async function suggestBranchNames(workingDir, userMessage) {
|
|
|
65487
65784
|
workingDir
|
|
65488
65785
|
});
|
|
65489
65786
|
if (!result.success || !result.response) {
|
|
65490
|
-
|
|
65787
|
+
log24.debug(`Branch suggestion failed: ${result.error || "no response"}`);
|
|
65491
65788
|
return [];
|
|
65492
65789
|
}
|
|
65493
65790
|
const suggestions = parseBranchSuggestions(result.response);
|
|
65494
|
-
|
|
65791
|
+
log24.debug(`Got ${suggestions.length} branch suggestions: ${suggestions.join(", ")}`);
|
|
65495
65792
|
return suggestions;
|
|
65496
65793
|
} catch (err) {
|
|
65497
|
-
|
|
65794
|
+
log24.debug(`Branch suggestion error: ${err}`);
|
|
65498
65795
|
return [];
|
|
65499
65796
|
}
|
|
65500
65797
|
}
|
|
@@ -65503,8 +65800,8 @@ async function suggestBranchNames(workingDir, userMessage) {
|
|
|
65503
65800
|
init_worktree();
|
|
65504
65801
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
65505
65802
|
init_logger();
|
|
65506
|
-
var
|
|
65507
|
-
var sessionLog5 = createSessionLog(
|
|
65803
|
+
var log25 = createLogger("worktree");
|
|
65804
|
+
var sessionLog5 = createSessionLog(log25);
|
|
65508
65805
|
function displayBranchName(name) {
|
|
65509
65806
|
return name.replace(/[`\r\n]/g, "").slice(0, 100);
|
|
65510
65807
|
}
|
|
@@ -66099,8 +66396,8 @@ async function cleanupWorktreeCommand(session, username, hasOtherSessionsUsingWo
|
|
|
66099
66396
|
}
|
|
66100
66397
|
// src/operations/events/handler.ts
|
|
66101
66398
|
init_logger();
|
|
66102
|
-
var
|
|
66103
|
-
var sessionLog6 = createSessionLog(
|
|
66399
|
+
var log26 = createLogger("events");
|
|
66400
|
+
var sessionLog6 = createSessionLog(log26);
|
|
66104
66401
|
function detectAndExecuteClaudeCommands(text, session, ctx) {
|
|
66105
66402
|
const parsed = parseClaudeCommand(text);
|
|
66106
66403
|
if (parsed && isClaudeAllowedCommand(parsed.command)) {
|
|
@@ -66443,7 +66740,7 @@ function updateUsageFromStatusLine(session) {
|
|
|
66443
66740
|
}
|
|
66444
66741
|
// src/operations/monitor/handler.ts
|
|
66445
66742
|
init_logger();
|
|
66446
|
-
var
|
|
66743
|
+
var log27 = createLogger("monitor");
|
|
66447
66744
|
var DEFAULT_INTERVAL_MS = 60 * 1000;
|
|
66448
66745
|
|
|
66449
66746
|
class SessionMonitor {
|
|
@@ -66465,14 +66762,14 @@ class SessionMonitor {
|
|
|
66465
66762
|
}
|
|
66466
66763
|
start() {
|
|
66467
66764
|
if (this.isRunning) {
|
|
66468
|
-
|
|
66765
|
+
log27.debug("Session monitor already running");
|
|
66469
66766
|
return;
|
|
66470
66767
|
}
|
|
66471
66768
|
this.isRunning = true;
|
|
66472
|
-
|
|
66769
|
+
log27.debug(`Session monitor started (interval: ${this.intervalMs / 1000}s)`);
|
|
66473
66770
|
this.timer = setInterval(() => {
|
|
66474
66771
|
this.runCheck().catch((err) => {
|
|
66475
|
-
|
|
66772
|
+
log27.error(`Error during session monitoring: ${err}`);
|
|
66476
66773
|
});
|
|
66477
66774
|
}, this.intervalMs);
|
|
66478
66775
|
}
|
|
@@ -66482,7 +66779,7 @@ class SessionMonitor {
|
|
|
66482
66779
|
this.timer = null;
|
|
66483
66780
|
}
|
|
66484
66781
|
this.isRunning = false;
|
|
66485
|
-
|
|
66782
|
+
log27.debug("Session monitor stopped");
|
|
66486
66783
|
}
|
|
66487
66784
|
async runCheck() {
|
|
66488
66785
|
await cleanupIdleSessions(this.sessionTimeoutMs, this.sessionWarningMs, this.getContext());
|
|
@@ -66502,8 +66799,8 @@ function createSessionContext(config, state, ops) {
|
|
|
66502
66799
|
// src/operations/context-prompt/handler.ts
|
|
66503
66800
|
init_emoji();
|
|
66504
66801
|
init_logger();
|
|
66505
|
-
var
|
|
66506
|
-
var sessionLog7 = createSessionLog(
|
|
66802
|
+
var log28 = createLogger("context");
|
|
66803
|
+
var sessionLog7 = createSessionLog(log28);
|
|
66507
66804
|
var CONTEXT_PROMPT_TIMEOUT_MS = 30000;
|
|
66508
66805
|
var CONTEXT_OPTIONS = [3, 5, 10];
|
|
66509
66806
|
var AUTO_INCLUDE_LIMIT = 25;
|
|
@@ -66725,7 +67022,7 @@ async function offerContextPrompt(session, queuedPrompt, queuedFiles, ctx, exclu
|
|
|
66725
67022
|
// src/operations/suggestions/tag.ts
|
|
66726
67023
|
init_quick_query();
|
|
66727
67024
|
init_logger();
|
|
66728
|
-
var
|
|
67025
|
+
var log29 = createLogger("tags");
|
|
66729
67026
|
var SUGGESTION_TIMEOUT2 = 15000;
|
|
66730
67027
|
var MAX_TAGS = 3;
|
|
66731
67028
|
var VALID_TAGS = [
|
|
@@ -66757,7 +67054,7 @@ function parseTags(response) {
|
|
|
66757
67054
|
return [...new Set(tags)].slice(0, MAX_TAGS);
|
|
66758
67055
|
}
|
|
66759
67056
|
async function suggestSessionTags(userMessage) {
|
|
66760
|
-
|
|
67057
|
+
log29.debug(`Suggesting tags for: "${userMessage.substring(0, 50)}..."`);
|
|
66761
67058
|
try {
|
|
66762
67059
|
const result = await quickQuery({
|
|
66763
67060
|
prompt: buildTagPrompt(userMessage),
|
|
@@ -66765,21 +67062,21 @@ async function suggestSessionTags(userMessage) {
|
|
|
66765
67062
|
timeout: SUGGESTION_TIMEOUT2
|
|
66766
67063
|
});
|
|
66767
67064
|
if (!result.success || !result.response) {
|
|
66768
|
-
|
|
67065
|
+
log29.debug(`Tag suggestion failed: ${result.error || "no response"}`);
|
|
66769
67066
|
return [];
|
|
66770
67067
|
}
|
|
66771
67068
|
const tags = parseTags(result.response);
|
|
66772
|
-
|
|
67069
|
+
log29.debug(`Got tags: ${tags.join(", ")} (${result.durationMs}ms)`);
|
|
66773
67070
|
return tags;
|
|
66774
67071
|
} catch (err) {
|
|
66775
|
-
|
|
67072
|
+
log29.debug(`Tag suggestion error: ${err}`);
|
|
66776
67073
|
return [];
|
|
66777
67074
|
}
|
|
66778
67075
|
}
|
|
66779
67076
|
// src/operations/suggestions/title.ts
|
|
66780
67077
|
init_quick_query();
|
|
66781
67078
|
init_logger();
|
|
66782
|
-
var
|
|
67079
|
+
var log30 = createLogger("title");
|
|
66783
67080
|
var SUGGESTION_TIMEOUT3 = 15000;
|
|
66784
67081
|
var MIN_TITLE_LENGTH = 3;
|
|
66785
67082
|
var MAX_TITLE_LENGTH = 50;
|
|
@@ -66843,32 +67140,32 @@ function parseMetadata(response) {
|
|
|
66843
67140
|
const titleMatch = response.match(/TITLE:\s*(.+)/i);
|
|
66844
67141
|
const descMatch = response.match(/DESC:\s*(.+)/i);
|
|
66845
67142
|
if (!titleMatch || !descMatch) {
|
|
66846
|
-
|
|
67143
|
+
log30.debug("Failed to parse title/description from response");
|
|
66847
67144
|
return null;
|
|
66848
67145
|
}
|
|
66849
67146
|
let title = titleMatch[1].trim();
|
|
66850
67147
|
let description = descMatch[1].trim();
|
|
66851
67148
|
if (title.length < MIN_TITLE_LENGTH) {
|
|
66852
|
-
|
|
67149
|
+
log30.debug(`Title too short: ${title.length} chars`);
|
|
66853
67150
|
return null;
|
|
66854
67151
|
}
|
|
66855
67152
|
if (title.length > MAX_TITLE_LENGTH) {
|
|
66856
|
-
|
|
67153
|
+
log30.debug(`Title too long (${title.length} chars), truncating`);
|
|
66857
67154
|
title = truncateAtWord(title, MAX_TITLE_LENGTH);
|
|
66858
67155
|
}
|
|
66859
67156
|
if (description.length < MIN_DESC_LENGTH) {
|
|
66860
|
-
|
|
67157
|
+
log30.debug(`Description too short: ${description.length} chars`);
|
|
66861
67158
|
return null;
|
|
66862
67159
|
}
|
|
66863
67160
|
if (description.length > MAX_DESC_LENGTH) {
|
|
66864
|
-
|
|
67161
|
+
log30.debug(`Description too long (${description.length} chars), truncating`);
|
|
66865
67162
|
description = truncateAtWord(description, MAX_DESC_LENGTH);
|
|
66866
67163
|
}
|
|
66867
67164
|
return { title, description };
|
|
66868
67165
|
}
|
|
66869
67166
|
async function suggestSessionMetadata(context) {
|
|
66870
67167
|
const logContext = typeof context === "string" ? context.substring(0, 50) : context.originalTask.substring(0, 50);
|
|
66871
|
-
|
|
67168
|
+
log30.debug(`Suggesting title for: "${logContext}..."`);
|
|
66872
67169
|
try {
|
|
66873
67170
|
const result = await quickQuery({
|
|
66874
67171
|
prompt: buildTitlePrompt(context),
|
|
@@ -66876,16 +67173,16 @@ async function suggestSessionMetadata(context) {
|
|
|
66876
67173
|
timeout: SUGGESTION_TIMEOUT3
|
|
66877
67174
|
});
|
|
66878
67175
|
if (!result.success || !result.response) {
|
|
66879
|
-
|
|
67176
|
+
log30.debug(`Title suggestion failed: ${result.error || "no response"}`);
|
|
66880
67177
|
return null;
|
|
66881
67178
|
}
|
|
66882
67179
|
const metadata = parseMetadata(result.response);
|
|
66883
67180
|
if (metadata) {
|
|
66884
|
-
|
|
67181
|
+
log30.debug(`Got title: "${metadata.title}" (${result.durationMs}ms)`);
|
|
66885
67182
|
}
|
|
66886
67183
|
return metadata;
|
|
66887
67184
|
} catch (err) {
|
|
66888
|
-
|
|
67185
|
+
log30.debug(`Title suggestion error: ${err}`);
|
|
66889
67186
|
return null;
|
|
66890
67187
|
}
|
|
66891
67188
|
}
|
|
@@ -66893,15 +67190,15 @@ async function suggestSessionMetadata(context) {
|
|
|
66893
67190
|
init_quick_query();
|
|
66894
67191
|
|
|
66895
67192
|
// src/persistence/github-emails-store.ts
|
|
66896
|
-
import { existsSync as
|
|
66897
|
-
import { homedir as
|
|
66898
|
-
import { join as
|
|
67193
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync9 } from "fs";
|
|
67194
|
+
import { homedir as homedir8 } from "os";
|
|
67195
|
+
import { join as join14 } from "path";
|
|
66899
67196
|
init_logger();
|
|
66900
|
-
var
|
|
66901
|
-
var
|
|
66902
|
-
var DEFAULT_FILE3 =
|
|
67197
|
+
var log31 = createLogger("gh-emails");
|
|
67198
|
+
var DEFAULT_CONFIG_DIR2 = join14(homedir8(), ".config", "claude-threads");
|
|
67199
|
+
var DEFAULT_FILE3 = join14(DEFAULT_CONFIG_DIR2, "github-emails.yaml");
|
|
66903
67200
|
var NOREPLY_REGEX = /^\d+\+[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})@users\.noreply\.github\.com$/;
|
|
66904
|
-
var
|
|
67201
|
+
var STORE_VERSION3 = 1;
|
|
66905
67202
|
function isValidGitHubNoreplyEmail(s) {
|
|
66906
67203
|
return NOREPLY_REGEX.test(s);
|
|
66907
67204
|
}
|
|
@@ -66914,13 +67211,13 @@ class GitHubEmailsStore {
|
|
|
66914
67211
|
const effective = filePath ?? envPath;
|
|
66915
67212
|
if (effective) {
|
|
66916
67213
|
this.file = effective;
|
|
66917
|
-
this.configDir =
|
|
67214
|
+
this.configDir = join14(effective, "..");
|
|
66918
67215
|
} else {
|
|
66919
67216
|
this.file = DEFAULT_FILE3;
|
|
66920
|
-
this.configDir =
|
|
67217
|
+
this.configDir = DEFAULT_CONFIG_DIR2;
|
|
66921
67218
|
}
|
|
66922
|
-
if (!
|
|
66923
|
-
|
|
67219
|
+
if (!existsSync10(this.configDir)) {
|
|
67220
|
+
mkdirSync7(this.configDir, { recursive: true });
|
|
66924
67221
|
}
|
|
66925
67222
|
}
|
|
66926
67223
|
get(platformId, username) {
|
|
@@ -66937,7 +67234,7 @@ class GitHubEmailsStore {
|
|
|
66937
67234
|
}
|
|
66938
67235
|
data.emails[platformId][username] = email;
|
|
66939
67236
|
this.writeAtomic(data);
|
|
66940
|
-
|
|
67237
|
+
log31.debug(`Stored GitHub email for ${platformId}/${username}`);
|
|
66941
67238
|
}
|
|
66942
67239
|
delete(platformId, username) {
|
|
66943
67240
|
const data = this.loadRaw();
|
|
@@ -66949,40 +67246,40 @@ class GitHubEmailsStore {
|
|
|
66949
67246
|
delete data.emails[platformId];
|
|
66950
67247
|
}
|
|
66951
67248
|
this.writeAtomic(data);
|
|
66952
|
-
|
|
67249
|
+
log31.debug(`Removed GitHub email for ${platformId}/${username}`);
|
|
66953
67250
|
return true;
|
|
66954
67251
|
}
|
|
66955
67252
|
lastReadDegraded = false;
|
|
66956
67253
|
loadRaw() {
|
|
66957
|
-
if (!
|
|
67254
|
+
if (!existsSync10(this.file)) {
|
|
66958
67255
|
this.lastReadDegraded = false;
|
|
66959
|
-
return { version:
|
|
67256
|
+
return { version: STORE_VERSION3, emails: {} };
|
|
66960
67257
|
}
|
|
66961
67258
|
try {
|
|
66962
|
-
const raw =
|
|
67259
|
+
const raw = readFileSync9(this.file, "utf-8");
|
|
66963
67260
|
if (raw.trim() === "") {
|
|
66964
67261
|
this.lastReadDegraded = false;
|
|
66965
|
-
return { version:
|
|
67262
|
+
return { version: STORE_VERSION3, emails: {} };
|
|
66966
67263
|
}
|
|
66967
67264
|
const parsed = yaml.load(raw);
|
|
66968
67265
|
if (!parsed || typeof parsed !== "object") {
|
|
66969
67266
|
this.lastReadDegraded = true;
|
|
66970
|
-
return { version:
|
|
67267
|
+
return { version: STORE_VERSION3, emails: {} };
|
|
66971
67268
|
}
|
|
66972
67269
|
const missing = parsed.emails === undefined || parsed.emails === null;
|
|
66973
67270
|
const valid = !missing && typeof parsed.emails === "object";
|
|
66974
67271
|
this.lastReadDegraded = !missing && !valid;
|
|
66975
67272
|
const emails = valid ? parsed.emails : {};
|
|
66976
|
-
return { version: parsed.version ??
|
|
67273
|
+
return { version: parsed.version ?? STORE_VERSION3, emails };
|
|
66977
67274
|
} catch (err) {
|
|
66978
|
-
|
|
67275
|
+
log31.warn(`Failed to read ${this.file}: ${err.message} — reads degrade to empty`);
|
|
66979
67276
|
this.lastReadDegraded = true;
|
|
66980
|
-
return { version:
|
|
67277
|
+
return { version: STORE_VERSION3, emails: {} };
|
|
66981
67278
|
}
|
|
66982
67279
|
}
|
|
66983
67280
|
writeAtomic(data) {
|
|
66984
67281
|
if (this.lastReadDegraded) {
|
|
66985
|
-
|
|
67282
|
+
log31.error(`Refusing to write ${this.file}: the last read of the existing file was degraded — writing would destroy stored emails`);
|
|
66986
67283
|
return;
|
|
66987
67284
|
}
|
|
66988
67285
|
writeFileAtomic(this.file, yaml.dump(data, { sortKeys: true, lineWidth: -1 }));
|
|
@@ -66990,8 +67287,8 @@ class GitHubEmailsStore {
|
|
|
66990
67287
|
}
|
|
66991
67288
|
|
|
66992
67289
|
// src/operations/commands/handler.ts
|
|
66993
|
-
var
|
|
66994
|
-
var sessionLog8 = createSessionLog(
|
|
67290
|
+
var log32 = createLogger("commands");
|
|
67291
|
+
var sessionLog8 = createSessionLog(log32);
|
|
66995
67292
|
function sessionAccountOption(session, ctx) {
|
|
66996
67293
|
if (!session.claudeAccountId)
|
|
66997
67294
|
return;
|
|
@@ -67160,7 +67457,7 @@ async function changeDirectory(session, newDir, username, ctx) {
|
|
|
67160
67457
|
const expandedDir = newDir.startsWith("~") ? newDir.replace("~", process.env.HOME || "") : newDir;
|
|
67161
67458
|
const absoluteDir = resolve6(expandedDir);
|
|
67162
67459
|
const formatter = session.platform.getFormatter();
|
|
67163
|
-
if (!
|
|
67460
|
+
if (!existsSync11(absoluteDir)) {
|
|
67164
67461
|
await postError(session, `Directory does not exist: ${formatter.formatCode(newDir)}`);
|
|
67165
67462
|
sessionLog8(session).warn(`\uD83D\uDCC2 Directory does not exist: ${newDir}`);
|
|
67166
67463
|
return;
|
|
@@ -67658,8 +67955,8 @@ async function handleBugReportApproval(session, isApproved, username) {
|
|
|
67658
67955
|
|
|
67659
67956
|
// src/session/metadata-suggestions.ts
|
|
67660
67957
|
init_logger();
|
|
67661
|
-
var
|
|
67662
|
-
var sessionLog9 = createSessionLog(
|
|
67958
|
+
var log33 = createLogger("session");
|
|
67959
|
+
var sessionLog9 = createSessionLog(log33);
|
|
67663
67960
|
var METADATA_RETRY_DELAY_MS = 2000;
|
|
67664
67961
|
var METADATA_MAX_RETRIES = 2;
|
|
67665
67962
|
async function attemptMetadataFetch(session, prompt, ctx, attempt = 1, options = {}) {
|
|
@@ -67799,7 +68096,7 @@ init_worktree();
|
|
|
67799
68096
|
// src/memory/distiller.ts
|
|
67800
68097
|
init_quick_query();
|
|
67801
68098
|
init_logger();
|
|
67802
|
-
var
|
|
68099
|
+
var log34 = createLogger("memory");
|
|
67803
68100
|
var MIN_THREAD_MESSAGES = 4;
|
|
67804
68101
|
var DISTILL_MESSAGE_LIMIT = 30;
|
|
67805
68102
|
var MESSAGE_CHAR_CAP = 500;
|
|
@@ -67843,21 +68140,21 @@ function scheduleDistillation(session, ctx, reason) {
|
|
|
67843
68140
|
return;
|
|
67844
68141
|
}
|
|
67845
68142
|
if (session.unattended) {
|
|
67846
|
-
|
|
68143
|
+
log34.debug(`Skipping distillation for unattended session ${session.platformId}:${session.threadId}`);
|
|
67847
68144
|
return;
|
|
67848
68145
|
}
|
|
67849
68146
|
if (isDcmThreadId(session.threadId)) {
|
|
67850
|
-
|
|
68147
|
+
log34.debug(`Skipping distillation for DCM session ${session.platformId}:${session.threadId}`);
|
|
67851
68148
|
return;
|
|
67852
68149
|
}
|
|
67853
68150
|
const { platformId, threadId, platform } = session;
|
|
67854
68151
|
const store = ctx.state.memoryStore;
|
|
67855
68152
|
distillThread(store, platformId, threadId, platform).then((added) => {
|
|
67856
68153
|
if (added > 0) {
|
|
67857
|
-
|
|
68154
|
+
log34.debug(`Distilled ${added} memory entries from ${platformId}:${threadId} (${reason})`);
|
|
67858
68155
|
}
|
|
67859
68156
|
}).catch((err) => {
|
|
67860
|
-
|
|
68157
|
+
log34.debug(`Distillation failed for ${platformId}:${threadId}: ${err.message}`);
|
|
67861
68158
|
});
|
|
67862
68159
|
}
|
|
67863
68160
|
async function distillThread(store, platformId, threadId, platform) {
|
|
@@ -67974,7 +68271,8 @@ class SessionRegistry {
|
|
|
67974
68271
|
return this.sessionStore.findByThread(platformId, threadId);
|
|
67975
68272
|
}
|
|
67976
68273
|
getPersistedByThreadId(threadId, platformId) {
|
|
67977
|
-
|
|
68274
|
+
const persisted = this.sessionStore.findByThreadIdAnyState(threadId, platformId);
|
|
68275
|
+
return persisted && isRevivable(persisted) ? persisted : undefined;
|
|
67978
68276
|
}
|
|
67979
68277
|
getSessionStore() {
|
|
67980
68278
|
return this.sessionStore;
|
|
@@ -67999,8 +68297,8 @@ class SessionRegistry {
|
|
|
67999
68297
|
|
|
68000
68298
|
// src/operations/agent-actions/handler.ts
|
|
68001
68299
|
init_logger();
|
|
68002
|
-
var
|
|
68003
|
-
var sessionLog10 = createSessionLog(
|
|
68300
|
+
var log35 = createLogger("agent-actions");
|
|
68301
|
+
var sessionLog10 = createSessionLog(log35);
|
|
68004
68302
|
var AGENT_MEMORY_WRITES_PER_SESSION = 5;
|
|
68005
68303
|
var LIST_LIMIT = 100;
|
|
68006
68304
|
async function handleAgentAction(session, ctx, request, signal) {
|
|
@@ -68264,8 +68562,8 @@ function listWatches(session, ctx) {
|
|
|
68264
68562
|
}
|
|
68265
68563
|
|
|
68266
68564
|
// src/session/lifecycle.ts
|
|
68267
|
-
var
|
|
68268
|
-
var sessionLog11 = createSessionLog(
|
|
68565
|
+
var log36 = createLogger("lifecycle");
|
|
68566
|
+
var sessionLog11 = createSessionLog(log36);
|
|
68269
68567
|
function mutableSessions(ctx) {
|
|
68270
68568
|
return ctx.state.sessions;
|
|
68271
68569
|
}
|
|
@@ -68429,14 +68727,6 @@ function handleRateLimit(session, hit, ctx) {
|
|
|
68429
68727
|
sessionLog11(session).warn(`Rate limit on account "${session.claudeAccountId}" — cooling for ~${minutes}min`);
|
|
68430
68728
|
post(session, "warning", `⚠️ Claude account \`${session.claudeAccountId}\` hit a rate limit. ` + `New sessions will use another account until it resets (~${minutes}min).`);
|
|
68431
68729
|
}
|
|
68432
|
-
function findPersistedByThreadId(persisted, threadId, platformId) {
|
|
68433
|
-
for (const session of persisted.values()) {
|
|
68434
|
-
if (session.threadId === threadId && session.platformId === platformId) {
|
|
68435
|
-
return session;
|
|
68436
|
-
}
|
|
68437
|
-
}
|
|
68438
|
-
return;
|
|
68439
|
-
}
|
|
68440
68730
|
async function createSessionDecisionBridge(ref, ctx) {
|
|
68441
68731
|
try {
|
|
68442
68732
|
return await DecisionBridgeServer.create(async (request, signal) => {
|
|
@@ -68454,7 +68744,7 @@ async function createSessionDecisionBridge(ref, ctx) {
|
|
|
68454
68744
|
return messageManager.handleBridgeRequest(request, signal);
|
|
68455
68745
|
});
|
|
68456
68746
|
} catch (err) {
|
|
68457
|
-
|
|
68747
|
+
log36.warn(`Decision bridge unavailable — falling back to legacy MCP prompts: ${err}`);
|
|
68458
68748
|
return null;
|
|
68459
68749
|
}
|
|
68460
68750
|
}
|
|
@@ -68611,7 +68901,7 @@ function resumeSessionHeaderMode(persisted, platformConfigured) {
|
|
|
68611
68901
|
function resolveSessionHeaderMode(configured, replyToPostId, platformId) {
|
|
68612
68902
|
const mode = configured ?? DEFAULT_OVERHEAD_VISIBILITY;
|
|
68613
68903
|
if (mode === "hidden" && !replyToPostId) {
|
|
68614
|
-
|
|
68904
|
+
log36.error(`sessionHeader: hidden requires a replyToPostId for ${platformId}; ` + `downgrading this session to 'minimal' so the header post is still short.`);
|
|
68615
68905
|
return "minimal";
|
|
68616
68906
|
}
|
|
68617
68907
|
return mode;
|
|
@@ -68651,7 +68941,7 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
|
|
|
68651
68941
|
throw new Error(`Platform '${platformId}' not found. Call addPlatform() first.`);
|
|
68652
68942
|
}
|
|
68653
68943
|
if (!isAuthorizedForSession({ username, platform, sessionAllowedUsers: undefined })) {
|
|
68654
|
-
|
|
68944
|
+
log36.warn(`auth.denied.startSession: @${username || "unknown"} not authorized to start session in ${threadId.substring(0, 8)}...`);
|
|
68655
68945
|
return;
|
|
68656
68946
|
}
|
|
68657
68947
|
const activeOrPending = ctx.state.sessions.size + pendingStartsCount;
|
|
@@ -68690,7 +68980,7 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
|
|
|
68690
68980
|
const { resolve: resolve7 } = await import("path");
|
|
68691
68981
|
const requestedDir = initialOptions.workingDir.startsWith("~") ? initialOptions.workingDir.replace("~", process.env.HOME || "") : initialOptions.workingDir;
|
|
68692
68982
|
const resolvedDir = resolve7(requestedDir);
|
|
68693
|
-
if (!
|
|
68983
|
+
if (!existsSync12(resolvedDir)) {
|
|
68694
68984
|
const msg = `❌ Directory does not exist: ${formatter.formatCode(initialOptions.workingDir)}`;
|
|
68695
68985
|
if (startPost) {
|
|
68696
68986
|
await platform.updatePost(startPost.id, msg);
|
|
@@ -68712,17 +69002,17 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
|
|
|
68712
69002
|
return;
|
|
68713
69003
|
}
|
|
68714
69004
|
workingDir = resolvedDir;
|
|
68715
|
-
|
|
69005
|
+
log36.info(`Starting session in directory: ${workingDir} (from !cd command)`);
|
|
68716
69006
|
}
|
|
68717
69007
|
if (initialOptions?.permissionMode) {
|
|
68718
69008
|
permissionMode = initialOptions.permissionMode;
|
|
68719
69009
|
forceInteractivePermissions = permissionMode === "default";
|
|
68720
69010
|
sessionPermissionModeOverride = permissionMode;
|
|
68721
|
-
|
|
69011
|
+
log36.info(`Starting session with permission mode "${permissionMode}" (from !permissions command)`);
|
|
68722
69012
|
} else if (initialOptions?.forceInteractivePermissions) {
|
|
68723
69013
|
forceInteractivePermissions = true;
|
|
68724
69014
|
permissionMode = "default";
|
|
68725
|
-
|
|
69015
|
+
log36.info(`Starting session with interactive permissions (from !permissions command)`);
|
|
68726
69016
|
}
|
|
68727
69017
|
const userAttribution = ctx.config.userAttribution ?? true;
|
|
68728
69018
|
const memoryConfig = ctx.ops.getPlatformMemoryConfig(platformId);
|
|
@@ -68736,7 +69026,7 @@ async function startSessionImpl(options, username, displayName, replyToPostId, p
|
|
|
68736
69026
|
balanceByUsage: true
|
|
68737
69027
|
});
|
|
68738
69028
|
if (claudeAccount) {
|
|
68739
|
-
|
|
69029
|
+
log36.info(`Session ${sessionId.substring(0, 20)} reserved Claude account "${claudeAccount.id}"`);
|
|
68740
69030
|
}
|
|
68741
69031
|
const bridgeSessionRef = {};
|
|
68742
69032
|
const decisionBridge = await createSessionDecisionBridge(bridgeSessionRef, ctx);
|
|
@@ -68877,7 +69167,7 @@ async function resumeSession(state, ctx, resumedBy) {
|
|
|
68877
69167
|
const sessionKey = compositeSessionId(state.platformId, state.threadId);
|
|
68878
69168
|
const sessions = ctx.state?.sessions;
|
|
68879
69169
|
if (sessions?.has(sessionKey)) {
|
|
68880
|
-
|
|
69170
|
+
log36.debug(`Session ${state.threadId.substring(0, 8)}... already active, skipping resume`);
|
|
68881
69171
|
return;
|
|
68882
69172
|
}
|
|
68883
69173
|
const inFlight = _inFlightSessionStarts.get(sessionKey);
|
|
@@ -68904,35 +69194,35 @@ async function resumeSessionImpl(state, ctx, resumedBy) {
|
|
|
68904
69194
|
!state.claudeSessionId && "claudeSessionId",
|
|
68905
69195
|
!state.workingDir && "workingDir"
|
|
68906
69196
|
].filter(Boolean).join(", ");
|
|
68907
|
-
|
|
69197
|
+
log36.warn(`Skipping session with missing required fields: ${missing}`);
|
|
68908
69198
|
return;
|
|
68909
69199
|
}
|
|
68910
69200
|
const shortId = state.threadId.substring(0, 8);
|
|
68911
69201
|
const platforms = ctx.state.platforms;
|
|
68912
69202
|
const platform = platforms.get(state.platformId);
|
|
68913
69203
|
if (!platform) {
|
|
68914
|
-
|
|
69204
|
+
log36.warn(`Platform ${state.platformId} not registered, skipping resume for ${shortId}...`);
|
|
68915
69205
|
return;
|
|
68916
69206
|
}
|
|
68917
69207
|
if (isDcmThreadId(state.threadId) && !platform.directChannelMode?.enabled) {
|
|
68918
|
-
|
|
69208
|
+
log36.warn(`Direct channel mode disabled for ${state.platformId}, dropping persisted DCM session`);
|
|
68919
69209
|
ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
|
|
68920
69210
|
return;
|
|
68921
69211
|
}
|
|
68922
69212
|
if (!isDcmThreadId(state.threadId)) {
|
|
68923
69213
|
const threadPost = await platform.getPost(state.threadId);
|
|
68924
69214
|
if (!threadPost) {
|
|
68925
|
-
|
|
69215
|
+
log36.warn(`Thread ${shortId}... deleted, skipping resume`);
|
|
68926
69216
|
ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
|
|
68927
69217
|
return;
|
|
68928
69218
|
}
|
|
68929
69219
|
}
|
|
68930
69220
|
if (ctx.state.sessions.size >= ctx.config.maxSessions) {
|
|
68931
|
-
|
|
69221
|
+
log36.warn(`Max sessions reached, skipping resume for ${shortId}...`);
|
|
68932
69222
|
return;
|
|
68933
69223
|
}
|
|
68934
|
-
if (!
|
|
68935
|
-
|
|
69224
|
+
if (!existsSync12(state.workingDir)) {
|
|
69225
|
+
log36.warn(`Working directory ${state.workingDir} no longer exists, skipping resume for ${shortId}...`);
|
|
68936
69226
|
ctx.state.sessionStore.remove(`${state.platformId}:${state.threadId}`);
|
|
68937
69227
|
const resumeFormatter = platform.getFormatter();
|
|
68938
69228
|
const tempSession = {
|
|
@@ -68958,7 +69248,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
|
|
|
68958
69248
|
const appendSystemPrompt = await buildAppendSystemPrompt(platform, state.platformId, state.workingDir, state.threadId, state.startedBy, [...sessionAllowedUserSet(state)], CHAT_PLATFORM_PROMPT, ctx.state.githubEmailsStore, memoryConfig.enabled && memoryConfig.channelLayer ? ctx.state.memoryStore : null, { userAttribution });
|
|
68959
69249
|
const claudeAccount = ctx.ops.acquireClaudeAccount(state.claudeAccountId, state.threadId);
|
|
68960
69250
|
if (state.claudeAccountId && !claudeAccount) {
|
|
68961
|
-
|
|
69251
|
+
log36.warn(`Persisted session referenced Claude account "${state.claudeAccountId}" ` + `which is no longer configured — resuming under default env`);
|
|
68962
69252
|
}
|
|
68963
69253
|
const resumeBridgeRef = {};
|
|
68964
69254
|
const resumeBridge = await createSessionDecisionBridge(resumeBridgeRef, ctx);
|
|
@@ -69043,7 +69333,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
|
|
|
69043
69333
|
worktreePath: detected.worktreePath,
|
|
69044
69334
|
branch: detected.branch
|
|
69045
69335
|
};
|
|
69046
|
-
|
|
69336
|
+
log36.info(`Auto-detected worktree info for resumed session: branch=${detected.branch}`);
|
|
69047
69337
|
}
|
|
69048
69338
|
}
|
|
69049
69339
|
session.messageManager = createMessageManager(session, ctx);
|
|
@@ -69110,7 +69400,7 @@ ${sessionFormatter.formatItalic("Reconnected to Claude session. You can continue
|
|
|
69110
69400
|
await postResumeCoAuthorOnboarding(session, ctx);
|
|
69111
69401
|
ctx.ops.persistSession(session);
|
|
69112
69402
|
} catch (err) {
|
|
69113
|
-
|
|
69403
|
+
log36.error(`Failed to resume session ${shortId}`, err instanceof Error ? err : undefined);
|
|
69114
69404
|
auditSessionEnd(session, "resume-failed");
|
|
69115
69405
|
session.messageManager?.dispose();
|
|
69116
69406
|
session.decisionBridge?.close();
|
|
@@ -69163,31 +69453,40 @@ async function sendFollowUp(session, message, files, ctx, username, displayName,
|
|
|
69163
69453
|
await session.messageManager.handleUserMessage(message, files, username, displayName);
|
|
69164
69454
|
}
|
|
69165
69455
|
async function resumePausedSession(threadId, message, files, ctx, username, platformId) {
|
|
69166
|
-
const
|
|
69167
|
-
const state = findPersistedByThreadId(persisted, threadId, platformId);
|
|
69456
|
+
const state = ctx.state.sessionStore.findByThreadIdAnyState(threadId, platformId);
|
|
69168
69457
|
if (!state) {
|
|
69169
|
-
|
|
69458
|
+
log36.debug(`No persisted session found for ${threadId.substring(0, 8)}...`);
|
|
69459
|
+
return;
|
|
69460
|
+
}
|
|
69461
|
+
if (!isRevivable(state)) {
|
|
69462
|
+
log36.debug(`Not resuming stopped session ${threadId.substring(0, 8)}... — it ended`);
|
|
69170
69463
|
return;
|
|
69171
69464
|
}
|
|
69172
69465
|
const shortId = threadId.substring(0, 8);
|
|
69173
69466
|
const platform = ctx.state.platforms.get(state.platformId);
|
|
69174
69467
|
if (!platform) {
|
|
69175
|
-
|
|
69468
|
+
log36.warn(`auth.denied.resume: platform '${state.platformId}' not found for ${shortId}...`);
|
|
69176
69469
|
return;
|
|
69177
69470
|
}
|
|
69178
69471
|
const sessionAllowedUsers = sessionAllowedUserSet(state);
|
|
69179
69472
|
if (!isAuthorizedForSession({ username, platform, sessionAllowedUsers })) {
|
|
69180
|
-
|
|
69473
|
+
log36.warn(`auth.denied.resume: @${username || "unknown"} not authorized to resume ${shortId}...`);
|
|
69181
69474
|
return;
|
|
69182
69475
|
}
|
|
69183
|
-
|
|
69476
|
+
if (state.cleanedAt) {
|
|
69477
|
+
log36.info(`\uD83E\uDEA6 Reviving soft-deleted session ${shortId}... (resumed by @${username})`);
|
|
69478
|
+
delete state.cleanedAt;
|
|
69479
|
+
delete state.endReason;
|
|
69480
|
+
ctx.state.sessionStore.save(compositeSessionId(state.platformId, state.threadId), state);
|
|
69481
|
+
}
|
|
69482
|
+
log36.info(`\uD83D\uDD04 Resuming paused session ${shortId}... for new message`);
|
|
69184
69483
|
await resumeSession(state, ctx, username);
|
|
69185
|
-
const session = ctx.
|
|
69484
|
+
const session = ctx.state.sessions.get(compositeSessionId(state.platformId, state.threadId));
|
|
69186
69485
|
if (session && session.claude.isRunning() && session.messageManager) {
|
|
69187
69486
|
session.messageCount++;
|
|
69188
69487
|
await session.messageManager.handleUserMessage(message, files, username);
|
|
69189
69488
|
} else {
|
|
69190
|
-
|
|
69489
|
+
log36.warn(`Failed to resume session ${shortId}..., could not send message`);
|
|
69191
69490
|
}
|
|
69192
69491
|
}
|
|
69193
69492
|
async function handleExit(sessionId, code, ctx, source) {
|
|
@@ -69195,7 +69494,7 @@ async function handleExit(sessionId, code, ctx, source) {
|
|
|
69195
69494
|
const shortId = sessionId.substring(0, 8);
|
|
69196
69495
|
sessionLog11(session).debug(`handleExit called code=${code} isShuttingDown=${ctx.state.isShuttingDown}`);
|
|
69197
69496
|
if (!session) {
|
|
69198
|
-
|
|
69497
|
+
log36.debug(`Session ${shortId}... not found (already cleaned up)`);
|
|
69199
69498
|
return;
|
|
69200
69499
|
}
|
|
69201
69500
|
if (source && session.claude !== source) {
|
|
@@ -69402,7 +69701,7 @@ async function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
|
|
|
69402
69701
|
|
|
69403
69702
|
// src/platform/dm-discovery-runtime.ts
|
|
69404
69703
|
function createDmDiscoveryRuntime(deps) {
|
|
69405
|
-
const { platforms, session, log:
|
|
69704
|
+
const { platforms, session, log: log37 } = deps;
|
|
69406
69705
|
const graceMs = deps.graceMs ?? 30000;
|
|
69407
69706
|
const orphanTtlMs = deps.orphanTtlMs ?? 10 * 60000;
|
|
69408
69707
|
const instanceByChannel = new Map;
|
|
@@ -69448,7 +69747,7 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
69448
69747
|
configureAuditLog(dmId, false);
|
|
69449
69748
|
if (deps.isEnabled?.(dmId) !== false)
|
|
69450
69749
|
deps.removeUiRow?.(dmId);
|
|
69451
|
-
|
|
69750
|
+
log37("info", `\uD83E\uDDF9 DM instance ${dmId} torn down (${reason})`);
|
|
69452
69751
|
};
|
|
69453
69752
|
const register = (parentCfg, channelId, partnerUsernames) => {
|
|
69454
69753
|
const dmConfig = deriveDmPlatformConfig(parentCfg, channelId, partnerUsernames);
|
|
@@ -69477,10 +69776,10 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
69477
69776
|
}
|
|
69478
69777
|
const dmId = dmPlatformId(parentConfig.id, post2.channelId);
|
|
69479
69778
|
if (deps.isEnabled && !deps.isEnabled(dmId)) {
|
|
69480
|
-
|
|
69779
|
+
log37("info", `Ignoring DM for disabled instance ${dmId}`);
|
|
69481
69780
|
return;
|
|
69482
69781
|
}
|
|
69483
|
-
|
|
69782
|
+
log37("info", `\uD83D\uDCE9 New DM conversation with @${username} — spawning ${dmId}`);
|
|
69484
69783
|
const dmClient = register(parentConfig, post2.channelId, [username]);
|
|
69485
69784
|
connecting.add(dmId);
|
|
69486
69785
|
dmClient.connect().then(() => {
|
|
@@ -69490,7 +69789,7 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
69490
69789
|
}).catch((err) => {
|
|
69491
69790
|
if (platforms.get(dmId) !== dmClient)
|
|
69492
69791
|
return;
|
|
69493
|
-
|
|
69792
|
+
log37("error", `Failed to connect DM instance ${dmId}, discarding: ${err}`);
|
|
69494
69793
|
(async () => {
|
|
69495
69794
|
const threadId = dcmThreadId(dmId);
|
|
69496
69795
|
const inFlightDeadline = Date.now() + 30000;
|
|
@@ -69499,7 +69798,7 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
69499
69798
|
if (!inFlight)
|
|
69500
69799
|
break;
|
|
69501
69800
|
if (Date.now() > inFlightDeadline) {
|
|
69502
|
-
|
|
69801
|
+
log37("warn", `In-flight session start for ${dmId} did not settle within 30s — proceeding with teardown`);
|
|
69503
69802
|
break;
|
|
69504
69803
|
}
|
|
69505
69804
|
await Promise.race([
|
|
@@ -69514,7 +69813,7 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
69514
69813
|
try {
|
|
69515
69814
|
await session.cancelSession(threadId, dmClient.getBotName());
|
|
69516
69815
|
} catch (cancelErr) {
|
|
69517
|
-
|
|
69816
|
+
log37("warn", `Failed to cancel stranded DM session ${threadId} (will be reaped by idle cleanup): ${cancelErr}`);
|
|
69518
69817
|
}
|
|
69519
69818
|
}
|
|
69520
69819
|
}
|
|
@@ -69524,7 +69823,7 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
69524
69823
|
return;
|
|
69525
69824
|
if (!session.registry.findByThreadId(threadId))
|
|
69526
69825
|
return;
|
|
69527
|
-
|
|
69826
|
+
log37("warn", `Sweeping session stranded on removed DM platform ${dmId}`);
|
|
69528
69827
|
session.cancelSession(threadId, dmClient.getBotName()).catch(() => {});
|
|
69529
69828
|
}, 2000);
|
|
69530
69829
|
})();
|
|
@@ -69555,21 +69854,21 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
69555
69854
|
continue;
|
|
69556
69855
|
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];
|
|
69557
69856
|
if (!parentCfg) {
|
|
69558
|
-
|
|
69857
|
+
log37("warn", `Skipping persisted DM session for ${pid} (parent missing, renamed, or directMessages off)`);
|
|
69559
69858
|
continue;
|
|
69560
69859
|
}
|
|
69561
69860
|
const channelId = pid.slice(parentCfg.id.length + DM_PLATFORM_SEP.length);
|
|
69562
69861
|
if (instanceByChannel.has(channelId)) {
|
|
69563
|
-
|
|
69862
|
+
log37("warn", `Skipping persisted DM session for ${pid} (channel already owned by ${instanceByChannel.get(channelId)})`);
|
|
69564
69863
|
continue;
|
|
69565
69864
|
}
|
|
69566
69865
|
if (!isEnabled(pid)) {
|
|
69567
|
-
|
|
69866
|
+
log37("info", `Skipping disabled DM instance ${pid}`);
|
|
69568
69867
|
skippedDisabled.push({ platformId: pid, channelId });
|
|
69569
69868
|
continue;
|
|
69570
69869
|
}
|
|
69571
69870
|
const partners = persisted.sessionAllowedUsers && persisted.sessionAllowedUsers.length > 0 ? persisted.sessionAllowedUsers : [persisted.startedBy].filter((u) => !!u);
|
|
69572
|
-
|
|
69871
|
+
log37("info", `♻️ Reconstructing DM instance ${pid}`);
|
|
69573
69872
|
register(parentCfg, channelId, partners);
|
|
69574
69873
|
connecting.add(pid);
|
|
69575
69874
|
reconstructed.set(pid, channelId);
|
|
@@ -69610,8 +69909,8 @@ function createDmDiscoveryRuntime(deps) {
|
|
|
69610
69909
|
|
|
69611
69910
|
// src/onboarding.ts
|
|
69612
69911
|
var import_prompts = __toESM(require_prompts3(), 1);
|
|
69613
|
-
import { existsSync as
|
|
69614
|
-
import { join as
|
|
69912
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
|
|
69913
|
+
import { join as join15, dirname as dirname8 } from "path";
|
|
69615
69914
|
import { spawn as spawn3 } from "child_process";
|
|
69616
69915
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
69617
69916
|
|
|
@@ -69676,7 +69975,7 @@ function overheadVisibilityChoiceIndex(mode) {
|
|
|
69676
69975
|
return OVERHEAD_VISIBILITY_CHOICES.findIndex((c) => c.value === mode);
|
|
69677
69976
|
}
|
|
69678
69977
|
var __dirname6 = dirname8(fileURLToPath6(import.meta.url));
|
|
69679
|
-
var SLACK_MANIFEST_PATH =
|
|
69978
|
+
var SLACK_MANIFEST_PATH = join15(__dirname6, "..", "docs", "slack-app-manifest.yaml");
|
|
69680
69979
|
var onCancel = () => {
|
|
69681
69980
|
console.log("");
|
|
69682
69981
|
console.log(dim(" Setup cancelled."));
|
|
@@ -69731,7 +70030,7 @@ async function showPlatformInstructions(platformType) {
|
|
|
69731
70030
|
} else {
|
|
69732
70031
|
let manifest;
|
|
69733
70032
|
try {
|
|
69734
|
-
manifest =
|
|
70033
|
+
manifest = readFileSync10(SLACK_MANIFEST_PATH, "utf-8");
|
|
69735
70034
|
} catch {
|
|
69736
70035
|
console.log("");
|
|
69737
70036
|
console.log(dim(" ⚠️ Could not find Slack manifest file."));
|
|
@@ -69835,9 +70134,9 @@ async function runOnboarding(reconfigure = false) {
|
|
|
69835
70134
|
console.log(dim(" ─────────────────────────────────"));
|
|
69836
70135
|
console.log("");
|
|
69837
70136
|
let existingConfig = null;
|
|
69838
|
-
if (reconfigure &&
|
|
70137
|
+
if (reconfigure && existsSync13(CONFIG_PATH)) {
|
|
69839
70138
|
try {
|
|
69840
|
-
const content =
|
|
70139
|
+
const content = readFileSync10(CONFIG_PATH, "utf-8");
|
|
69841
70140
|
existingConfig = yaml.load(content);
|
|
69842
70141
|
console.log(dim(" Reconfiguring existing setup."));
|
|
69843
70142
|
} catch {
|
|
@@ -70980,7 +71279,7 @@ async function setupSlackPlatform(id, existing) {
|
|
|
70980
71279
|
// src/platform/base-client.ts
|
|
70981
71280
|
init_logger();
|
|
70982
71281
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
70983
|
-
var
|
|
71282
|
+
var log37 = createLogger("base-client");
|
|
70984
71283
|
|
|
70985
71284
|
class BasePlatformClient extends EventEmitter3 {
|
|
70986
71285
|
closeSocket(ws) {
|
|
@@ -71042,7 +71341,7 @@ class BasePlatformClient extends EventEmitter3 {
|
|
|
71042
71341
|
try {
|
|
71043
71342
|
await this.addReaction(post2.id, emoji);
|
|
71044
71343
|
} catch (err) {
|
|
71045
|
-
|
|
71344
|
+
log37.warn(`Failed to add reaction ${emoji}: ${err}`);
|
|
71046
71345
|
}
|
|
71047
71346
|
}
|
|
71048
71347
|
return post2;
|
|
@@ -71069,7 +71368,7 @@ class BasePlatformClient extends EventEmitter3 {
|
|
|
71069
71368
|
this.heartbeatInterval = setInterval(() => {
|
|
71070
71369
|
const silentFor = Date.now() - this.lastMessageAt;
|
|
71071
71370
|
if (silentFor > this.HEARTBEAT_TIMEOUT_MS) {
|
|
71072
|
-
|
|
71371
|
+
log37.warn(`Connection dead (no activity for ${Math.round(silentFor / 1000)}s), reconnecting...`);
|
|
71073
71372
|
this.stopHeartbeat();
|
|
71074
71373
|
this.scheduleReconnect();
|
|
71075
71374
|
return;
|
|
@@ -71089,7 +71388,7 @@ class BasePlatformClient extends EventEmitter3 {
|
|
|
71089
71388
|
this.reconnectTimeout = null;
|
|
71090
71389
|
}
|
|
71091
71390
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
71092
|
-
|
|
71391
|
+
log37.error("Max reconnection attempts reached");
|
|
71093
71392
|
return;
|
|
71094
71393
|
}
|
|
71095
71394
|
this.forceCloseConnection();
|
|
@@ -71116,7 +71415,7 @@ class BasePlatformClient extends EventEmitter3 {
|
|
|
71116
71415
|
this.emit("connected");
|
|
71117
71416
|
if (this.isReconnecting) {
|
|
71118
71417
|
this.recoverMissedMessages().catch((err) => {
|
|
71119
|
-
|
|
71418
|
+
log37.warn(`Failed to recover missed messages: ${err}`);
|
|
71120
71419
|
});
|
|
71121
71420
|
}
|
|
71122
71421
|
this.isReconnecting = false;
|
|
@@ -71150,7 +71449,7 @@ init_logger();
|
|
|
71150
71449
|
// src/platform/mattermost/upload.ts
|
|
71151
71450
|
init_logger();
|
|
71152
71451
|
import { readFile as readFile3 } from "fs/promises";
|
|
71153
|
-
var
|
|
71452
|
+
var log38 = createLogger("mm-upload");
|
|
71154
71453
|
async function uploadFileMattermost(args) {
|
|
71155
71454
|
const { url, token, channelId, threadId, filePath, filename, caption } = args;
|
|
71156
71455
|
const buffer = await readFile3(filePath);
|
|
@@ -71158,7 +71457,7 @@ async function uploadFileMattermost(args) {
|
|
|
71158
71457
|
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
71159
71458
|
const formData = new FormData;
|
|
71160
71459
|
formData.append("files", new Blob([arrayBuffer]), filename);
|
|
71161
|
-
|
|
71460
|
+
log38.debug(`POST /files (${buffer.length} bytes, ${filename})`);
|
|
71162
71461
|
const uploadResponse = await fetch(uploadUrl, {
|
|
71163
71462
|
method: "POST",
|
|
71164
71463
|
headers: {
|
|
@@ -71182,7 +71481,7 @@ async function uploadFileMattermost(args) {
|
|
|
71182
71481
|
root_id: resolvePostThreadId(threadId),
|
|
71183
71482
|
file_ids: [fileInfo.id]
|
|
71184
71483
|
};
|
|
71185
|
-
|
|
71484
|
+
log38.debug(`POST /posts (file_ids=[${fileInfo.id}])`);
|
|
71186
71485
|
const postResponse = await fetch(postUrl, {
|
|
71187
71486
|
method: "POST",
|
|
71188
71487
|
headers: {
|
|
@@ -71269,7 +71568,7 @@ ${code}
|
|
|
71269
71568
|
}
|
|
71270
71569
|
|
|
71271
71570
|
// src/platform/mattermost/client.ts
|
|
71272
|
-
var
|
|
71571
|
+
var log39 = createLogger("mattermost");
|
|
71273
71572
|
|
|
71274
71573
|
class MattermostClient extends BasePlatformClient {
|
|
71275
71574
|
platformId;
|
|
@@ -71359,7 +71658,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71359
71658
|
const hasFileIds = fileIds && fileIds.length > 0;
|
|
71360
71659
|
const hasFileMetadata = post2.metadata?.files && post2.metadata.files.length > 0;
|
|
71361
71660
|
if (hasFileIds && !hasFileMetadata) {
|
|
71362
|
-
|
|
71661
|
+
log39.debug(`Post ${formatShortId(post2.id)} has ${fileIds.length} file(s), fetching metadata`);
|
|
71363
71662
|
try {
|
|
71364
71663
|
const files = [];
|
|
71365
71664
|
for (const fileId of fileIds) {
|
|
@@ -71367,7 +71666,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71367
71666
|
const file = await this.api("GET", `/files/${fileId}/info`);
|
|
71368
71667
|
files.push(file);
|
|
71369
71668
|
} catch (err) {
|
|
71370
|
-
|
|
71669
|
+
log39.warn(`Failed to fetch file info for ${fileId}: ${err}`);
|
|
71371
71670
|
}
|
|
71372
71671
|
}
|
|
71373
71672
|
if (files.length > 0) {
|
|
@@ -71375,10 +71674,10 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71375
71674
|
...post2.metadata,
|
|
71376
71675
|
files
|
|
71377
71676
|
};
|
|
71378
|
-
|
|
71677
|
+
log39.debug(`Enriched post ${formatShortId(post2.id)} with ${files.length} file(s)`);
|
|
71379
71678
|
}
|
|
71380
71679
|
} catch (err) {
|
|
71381
|
-
|
|
71680
|
+
log39.warn(`Failed to fetch file metadata for post ${formatShortId(post2.id)}: ${err}`);
|
|
71382
71681
|
}
|
|
71383
71682
|
}
|
|
71384
71683
|
}
|
|
@@ -71388,7 +71687,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71388
71687
|
const user = await this.getUser(post2.user_id);
|
|
71389
71688
|
this.emit("direct_message", this.normalizePlatformPost(post2), user);
|
|
71390
71689
|
} catch (err) {
|
|
71391
|
-
|
|
71690
|
+
log39.warn(`Failed to emit direct message: ${err}`);
|
|
71392
71691
|
}
|
|
71393
71692
|
}
|
|
71394
71693
|
MAX_RETRIES = 6;
|
|
@@ -71396,7 +71695,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71396
71695
|
RETRY_DELAY_CAP_MS = 2000;
|
|
71397
71696
|
async api(method, path10, body, retryCount = 0, options) {
|
|
71398
71697
|
const url = `${this.url}/api/v4${path10}`;
|
|
71399
|
-
|
|
71698
|
+
log39.debug(`API ${method} ${path10}`);
|
|
71400
71699
|
const response = await fetch(url, {
|
|
71401
71700
|
method,
|
|
71402
71701
|
headers: {
|
|
@@ -71409,19 +71708,19 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71409
71708
|
const text = await response.text();
|
|
71410
71709
|
if (response.status === 500 && retryCount < this.MAX_RETRIES) {
|
|
71411
71710
|
const delay2 = this.retryDelayMs(retryCount);
|
|
71412
|
-
|
|
71711
|
+
log39.warn(`API ${method} ${path10} failed with 500, retrying in ${delay2}ms (attempt ${retryCount + 1}/${this.MAX_RETRIES})`);
|
|
71413
71712
|
await new Promise((resolve7) => setTimeout(resolve7, delay2));
|
|
71414
71713
|
return this.api(method, path10, body, retryCount + 1, options);
|
|
71415
71714
|
}
|
|
71416
71715
|
const isSilent = options?.silent?.includes(response.status);
|
|
71417
71716
|
if (isSilent) {
|
|
71418
|
-
|
|
71717
|
+
log39.debug(`API ${method} ${path10} failed: ${response.status} (expected)`);
|
|
71419
71718
|
} else {
|
|
71420
|
-
|
|
71719
|
+
log39.warn(`API ${method} ${path10} failed: ${response.status} ${text.substring(0, 100)}`);
|
|
71421
71720
|
}
|
|
71422
71721
|
throw new Error(`Mattermost API error ${response.status}: ${text}`);
|
|
71423
71722
|
}
|
|
71424
|
-
|
|
71723
|
+
log39.debug(`API ${method} ${path10} → ${response.status}`);
|
|
71425
71724
|
return response.json();
|
|
71426
71725
|
}
|
|
71427
71726
|
retryDelayMs(retryCount) {
|
|
@@ -71437,28 +71736,28 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71437
71736
|
async getUser(userId) {
|
|
71438
71737
|
const cached = this.userCache.get(userId);
|
|
71439
71738
|
if (cached) {
|
|
71440
|
-
|
|
71739
|
+
log39.debug(`User ${userId} found in cache: @${cached.username}`);
|
|
71441
71740
|
return this.normalizePlatformUser(cached);
|
|
71442
71741
|
}
|
|
71443
71742
|
try {
|
|
71444
71743
|
const user = await this.api("GET", `/users/${userId}`);
|
|
71445
71744
|
this.userCache.set(userId, user);
|
|
71446
|
-
|
|
71745
|
+
log39.debug(`User ${userId} fetched: @${user.username}`);
|
|
71447
71746
|
return this.normalizePlatformUser(user);
|
|
71448
71747
|
} catch (err) {
|
|
71449
|
-
|
|
71748
|
+
log39.warn(`Failed to get user ${userId}: ${err}`);
|
|
71450
71749
|
return null;
|
|
71451
71750
|
}
|
|
71452
71751
|
}
|
|
71453
71752
|
async getUserByUsername(username) {
|
|
71454
71753
|
try {
|
|
71455
|
-
|
|
71754
|
+
log39.debug(`Looking up user by username: @${username}`);
|
|
71456
71755
|
const user = await this.api("GET", `/users/username/${username}`);
|
|
71457
71756
|
this.userCache.set(user.id, user);
|
|
71458
|
-
|
|
71757
|
+
log39.debug(`User @${username} found: ${user.id}`);
|
|
71459
71758
|
return this.normalizePlatformUser(user);
|
|
71460
71759
|
} catch (err) {
|
|
71461
|
-
|
|
71760
|
+
log39.warn(`User @${username} not found: ${err}`);
|
|
71462
71761
|
return null;
|
|
71463
71762
|
}
|
|
71464
71763
|
}
|
|
@@ -71480,7 +71779,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71480
71779
|
return this.normalizePlatformPost(post2);
|
|
71481
71780
|
}
|
|
71482
71781
|
async addReaction(postId, emojiName) {
|
|
71483
|
-
|
|
71782
|
+
log39.debug(`Adding reaction :${emojiName}: to post ${postId.substring(0, 8)}`);
|
|
71484
71783
|
await this.api("POST", "/reactions", {
|
|
71485
71784
|
user_id: this.botUserId,
|
|
71486
71785
|
post_id: postId,
|
|
@@ -71488,11 +71787,11 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71488
71787
|
});
|
|
71489
71788
|
}
|
|
71490
71789
|
async removeReaction(postId, emojiName) {
|
|
71491
|
-
|
|
71790
|
+
log39.debug(`Removing reaction :${emojiName}: from post ${postId.substring(0, 8)}`);
|
|
71492
71791
|
await this.api("DELETE", `/users/${this.botUserId}/posts/${postId}/reactions/${emojiName}`);
|
|
71493
71792
|
}
|
|
71494
71793
|
async downloadFile(fileId) {
|
|
71495
|
-
|
|
71794
|
+
log39.debug(`Downloading file ${fileId}`);
|
|
71496
71795
|
const url = `${this.url}/api/v4/files/${fileId}`;
|
|
71497
71796
|
const response = await fetch(url, {
|
|
71498
71797
|
headers: {
|
|
@@ -71500,11 +71799,11 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71500
71799
|
}
|
|
71501
71800
|
});
|
|
71502
71801
|
if (!response.ok) {
|
|
71503
|
-
|
|
71802
|
+
log39.warn(`Failed to download file ${fileId}: ${response.status}`);
|
|
71504
71803
|
throw new Error(`Failed to download file ${fileId}: ${response.status}`);
|
|
71505
71804
|
}
|
|
71506
71805
|
const arrayBuffer = await response.arrayBuffer();
|
|
71507
|
-
|
|
71806
|
+
log39.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
|
|
71508
71807
|
return Buffer.from(arrayBuffer);
|
|
71509
71808
|
}
|
|
71510
71809
|
async getFileInfo(fileId) {
|
|
@@ -71526,24 +71825,24 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71526
71825
|
}
|
|
71527
71826
|
async getPost(postId) {
|
|
71528
71827
|
try {
|
|
71529
|
-
|
|
71828
|
+
log39.debug(`Fetching post ${postId.substring(0, 8)}`);
|
|
71530
71829
|
const post2 = await this.api("GET", `/posts/${postId}`);
|
|
71531
71830
|
return this.normalizePlatformPost(post2);
|
|
71532
71831
|
} catch (err) {
|
|
71533
|
-
|
|
71832
|
+
log39.debug(`Post ${postId.substring(0, 8)} not found: ${err}`);
|
|
71534
71833
|
return null;
|
|
71535
71834
|
}
|
|
71536
71835
|
}
|
|
71537
71836
|
async deletePost(postId) {
|
|
71538
|
-
|
|
71837
|
+
log39.debug(`Deleting post ${postId.substring(0, 8)}`);
|
|
71539
71838
|
await this.api("DELETE", `/posts/${postId}`);
|
|
71540
71839
|
}
|
|
71541
71840
|
async pinPost(postId) {
|
|
71542
|
-
|
|
71841
|
+
log39.debug(`Pinning post ${postId.substring(0, 8)}`);
|
|
71543
71842
|
await this.api("POST", `/posts/${postId}/pin`);
|
|
71544
71843
|
}
|
|
71545
71844
|
async unpinPost(postId) {
|
|
71546
|
-
|
|
71845
|
+
log39.debug(`Unpinning post ${postId.substring(0, 8)}`);
|
|
71547
71846
|
try {
|
|
71548
71847
|
await this.api("POST", `/posts/${postId}/unpin`, undefined, 0, { silent: [403, 404] });
|
|
71549
71848
|
} catch (err) {
|
|
@@ -71578,7 +71877,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71578
71877
|
}
|
|
71579
71878
|
return messages;
|
|
71580
71879
|
} catch (err) {
|
|
71581
|
-
|
|
71880
|
+
log39.warn(`Failed to get thread history for ${threadId}: ${err}`);
|
|
71582
71881
|
return [];
|
|
71583
71882
|
}
|
|
71584
71883
|
}
|
|
@@ -71597,7 +71896,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71597
71896
|
posts.sort((a, b) => (a.createAt ?? 0) - (b.createAt ?? 0));
|
|
71598
71897
|
return posts;
|
|
71599
71898
|
} catch (err) {
|
|
71600
|
-
|
|
71899
|
+
log39.warn(`Failed to get channel posts after ${afterPostId}: ${err}`);
|
|
71601
71900
|
return [];
|
|
71602
71901
|
}
|
|
71603
71902
|
}
|
|
@@ -71710,13 +72009,13 @@ class MattermostClient extends BasePlatformClient {
|
|
|
71710
72009
|
if (!this.lastProcessedPostId) {
|
|
71711
72010
|
return;
|
|
71712
72011
|
}
|
|
71713
|
-
|
|
72012
|
+
log39.info(`Recovering missed messages after post ${this.lastProcessedPostId}...`);
|
|
71714
72013
|
const missedPosts = await this.getChannelPostsAfter(this.lastProcessedPostId);
|
|
71715
72014
|
if (missedPosts.length === 0) {
|
|
71716
|
-
|
|
72015
|
+
log39.info("No missed messages to recover");
|
|
71717
72016
|
return;
|
|
71718
72017
|
}
|
|
71719
|
-
|
|
72018
|
+
log39.info(`Recovered ${missedPosts.length} missed message(s)`);
|
|
71720
72019
|
for (const post2 of missedPosts) {
|
|
71721
72020
|
this.lastProcessedPostId = post2.id;
|
|
71722
72021
|
const user = await this.getUser(post2.userId);
|
|
@@ -71776,7 +72075,7 @@ init_logger();
|
|
|
71776
72075
|
// src/platform/slack/upload.ts
|
|
71777
72076
|
init_logger();
|
|
71778
72077
|
import { readFile as readFile4 } from "fs/promises";
|
|
71779
|
-
var
|
|
72078
|
+
var log40 = createLogger("slack-upload");
|
|
71780
72079
|
var DEFAULT_API_URL = "https://slack.com/api";
|
|
71781
72080
|
async function uploadFileSlack(args) {
|
|
71782
72081
|
const { botToken, channelId, threadTs, filePath, filename, caption } = args;
|
|
@@ -71784,7 +72083,7 @@ async function uploadFileSlack(args) {
|
|
|
71784
72083
|
const buffer = await readFile4(filePath);
|
|
71785
72084
|
const params = new URLSearchParams({ filename, length: String(buffer.length) });
|
|
71786
72085
|
const step1Url = `${apiUrl}/files.getUploadURLExternal?${params.toString()}`;
|
|
71787
|
-
|
|
72086
|
+
log40.debug(`GET files.getUploadURLExternal (${buffer.length} bytes, ${filename})`);
|
|
71788
72087
|
const step1Response = await fetch(step1Url, {
|
|
71789
72088
|
method: "GET",
|
|
71790
72089
|
headers: {
|
|
@@ -71802,7 +72101,7 @@ async function uploadFileSlack(args) {
|
|
|
71802
72101
|
const uploadUrl = step1Data.upload_url;
|
|
71803
72102
|
const fileId = step1Data.file_id;
|
|
71804
72103
|
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
71805
|
-
|
|
72104
|
+
log40.debug(`POST <upload_url>`);
|
|
71806
72105
|
const step2Response = await fetch(uploadUrl, {
|
|
71807
72106
|
method: "POST",
|
|
71808
72107
|
headers: {
|
|
@@ -71822,7 +72121,7 @@ async function uploadFileSlack(args) {
|
|
|
71822
72121
|
if (caption !== undefined) {
|
|
71823
72122
|
step3Body.initial_comment = caption;
|
|
71824
72123
|
}
|
|
71825
|
-
|
|
72124
|
+
log40.debug(`POST files.completeUploadExternal (file_id=${fileId}, thread_ts=${threadTs})`);
|
|
71826
72125
|
const step3Response = await fetch(`${apiUrl}/files.completeUploadExternal`, {
|
|
71827
72126
|
method: "POST",
|
|
71828
72127
|
headers: {
|
|
@@ -71840,7 +72139,7 @@ async function uploadFileSlack(args) {
|
|
|
71840
72139
|
throw new Error(`Slack completeUploadExternal error: ${step3Data.error || "unknown"}`);
|
|
71841
72140
|
}
|
|
71842
72141
|
if (!step3Data.ts) {
|
|
71843
|
-
|
|
72142
|
+
log40.warn(`Slack completeUploadExternal returned no ts; using fileId ${fileId} as postId. ` + `Do not use this id for updatePost/addReaction.`);
|
|
71844
72143
|
}
|
|
71845
72144
|
return { fileId, postId: step3Data.ts ?? fileId };
|
|
71846
72145
|
}
|
|
@@ -71915,7 +72214,7 @@ ${code}
|
|
|
71915
72214
|
}
|
|
71916
72215
|
|
|
71917
72216
|
// src/platform/slack/client.ts
|
|
71918
|
-
var
|
|
72217
|
+
var log41 = createLogger("slack");
|
|
71919
72218
|
|
|
71920
72219
|
class SlackClient extends BasePlatformClient {
|
|
71921
72220
|
platformId;
|
|
@@ -71984,7 +72283,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
71984
72283
|
return;
|
|
71985
72284
|
for (const secondary of this.channelClients.values()) {
|
|
71986
72285
|
secondary.recoverMissedMessages().catch((err) => {
|
|
71987
|
-
|
|
72286
|
+
log41.warn(`Failed to recover missed messages for ${secondary.platformId}: ${err}`);
|
|
71988
72287
|
});
|
|
71989
72288
|
}
|
|
71990
72289
|
}
|
|
@@ -72052,13 +72351,13 @@ class SlackClient extends BasePlatformClient {
|
|
|
72052
72351
|
const now = Date.now();
|
|
72053
72352
|
if (now < this.rateLimitRetryAfter) {
|
|
72054
72353
|
const waitTime = this.rateLimitRetryAfter - now;
|
|
72055
|
-
|
|
72354
|
+
log41.debug(`Rate limited, waiting ${waitTime}ms`);
|
|
72056
72355
|
await new Promise((resolve7) => setTimeout(resolve7, waitTime));
|
|
72057
72356
|
}
|
|
72058
72357
|
this.rateLimitDelay = 0;
|
|
72059
72358
|
}
|
|
72060
72359
|
const url = `${this.apiUrl}/${endpoint}`;
|
|
72061
|
-
|
|
72360
|
+
log41.debug(`API ${method} ${endpoint}`);
|
|
72062
72361
|
const headers = {
|
|
72063
72362
|
Authorization: `Bearer ${this.botToken}`,
|
|
72064
72363
|
"Content-Type": "application/json; charset=utf-8"
|
|
@@ -72070,25 +72369,25 @@ class SlackClient extends BasePlatformClient {
|
|
|
72070
72369
|
});
|
|
72071
72370
|
if (response.status === 429) {
|
|
72072
72371
|
if (retryCount >= this.MAX_RATE_LIMIT_RETRIES) {
|
|
72073
|
-
|
|
72372
|
+
log41.error(`Rate limit max retries (${this.MAX_RATE_LIMIT_RETRIES}) exceeded for ${endpoint}`);
|
|
72074
72373
|
throw new Error(`Slack API rate limit exceeded after ${this.MAX_RATE_LIMIT_RETRIES} retries`);
|
|
72075
72374
|
}
|
|
72076
72375
|
const retryAfter = parseInt(response.headers.get("Retry-After") || "5", 10);
|
|
72077
72376
|
this.rateLimitDelay = retryAfter * 1000;
|
|
72078
72377
|
this.rateLimitRetryAfter = Date.now() + this.rateLimitDelay;
|
|
72079
|
-
|
|
72378
|
+
log41.warn(`Rate limited by Slack, retrying after ${retryAfter}s (attempt ${retryCount + 1}/${this.MAX_RATE_LIMIT_RETRIES})`);
|
|
72080
72379
|
await new Promise((resolve7) => setTimeout(resolve7, this.rateLimitDelay));
|
|
72081
72380
|
return this.api(method, endpoint, body, retryCount + 1);
|
|
72082
72381
|
}
|
|
72083
72382
|
if (!response.ok) {
|
|
72084
72383
|
const text = await response.text();
|
|
72085
|
-
|
|
72384
|
+
log41.warn(`API ${method} ${endpoint} failed: ${response.status} ${text.substring(0, 100)}`);
|
|
72086
72385
|
throw new Error(`Slack API error ${response.status}: ${text}`);
|
|
72087
72386
|
}
|
|
72088
72387
|
const data = await response.json();
|
|
72089
72388
|
if (!data.ok) {
|
|
72090
72389
|
if (!expectedErrors.includes(data.error || "")) {
|
|
72091
|
-
|
|
72390
|
+
log41.warn(`API ${method} ${endpoint} error: ${data.error}`);
|
|
72092
72391
|
}
|
|
72093
72392
|
throw new Error(`Slack API error: ${data.error}`);
|
|
72094
72393
|
}
|
|
@@ -72096,7 +72395,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
72096
72395
|
}
|
|
72097
72396
|
async appApi(method, endpoint, body) {
|
|
72098
72397
|
const url = `${this.apiUrl}/${endpoint}`;
|
|
72099
|
-
|
|
72398
|
+
log41.debug(`App API ${method} ${endpoint}`);
|
|
72100
72399
|
const headers = {
|
|
72101
72400
|
Authorization: `Bearer ${this.appToken}`,
|
|
72102
72401
|
"Content-Type": "application/json; charset=utf-8"
|
|
@@ -72287,7 +72586,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
72287
72586
|
this.emit("channel_post", post2, user);
|
|
72288
72587
|
}
|
|
72289
72588
|
}).catch((err) => {
|
|
72290
|
-
|
|
72589
|
+
log41.warn(`Failed to get user for message event: ${err}`);
|
|
72291
72590
|
this.emit("message", post2, null);
|
|
72292
72591
|
});
|
|
72293
72592
|
}
|
|
@@ -72307,7 +72606,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
72307
72606
|
this.getUser(event.user || "").then((user) => {
|
|
72308
72607
|
this.emit("reaction", reaction, user);
|
|
72309
72608
|
}).catch((err) => {
|
|
72310
|
-
|
|
72609
|
+
log41.warn(`Failed to get user for reaction event: ${err}`);
|
|
72311
72610
|
this.emit("reaction", reaction, null);
|
|
72312
72611
|
});
|
|
72313
72612
|
}
|
|
@@ -72327,7 +72626,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
72327
72626
|
this.getUser(event.user || "").then((user) => {
|
|
72328
72627
|
this.emit("reaction_removed", reaction, user);
|
|
72329
72628
|
}).catch((err) => {
|
|
72330
|
-
|
|
72629
|
+
log41.warn(`Failed to get user for reaction_removed event: ${err}`);
|
|
72331
72630
|
this.emit("reaction_removed", reaction, null);
|
|
72332
72631
|
});
|
|
72333
72632
|
}
|
|
@@ -72341,15 +72640,15 @@ class SlackClient extends BasePlatformClient {
|
|
|
72341
72640
|
if (!this.lastProcessedTs) {
|
|
72342
72641
|
return;
|
|
72343
72642
|
}
|
|
72344
|
-
|
|
72643
|
+
log41.info(`Recovering missed messages after ts ${this.lastProcessedTs}...`);
|
|
72345
72644
|
try {
|
|
72346
72645
|
const response = await this.api("GET", `conversations.history?channel=${this.channelId}&oldest=${this.lastProcessedTs}&inclusive=false&limit=100`);
|
|
72347
72646
|
const messages = response.messages || [];
|
|
72348
72647
|
if (messages.length === 0) {
|
|
72349
|
-
|
|
72648
|
+
log41.info("No missed messages to recover");
|
|
72350
72649
|
return;
|
|
72351
72650
|
}
|
|
72352
|
-
|
|
72651
|
+
log41.info(`Recovered ${messages.length} missed message(s)`);
|
|
72353
72652
|
const sortedMessages = messages.sort((a, b) => parseFloat(a.ts) - parseFloat(b.ts));
|
|
72354
72653
|
for (const message of sortedMessages) {
|
|
72355
72654
|
if (this.isBotAuthored(message)) {
|
|
@@ -72364,7 +72663,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
72364
72663
|
}
|
|
72365
72664
|
}
|
|
72366
72665
|
} catch (err) {
|
|
72367
|
-
|
|
72666
|
+
log41.warn(`Failed to recover missed messages: ${err}`);
|
|
72368
72667
|
}
|
|
72369
72668
|
}
|
|
72370
72669
|
async fetchBotUser() {
|
|
@@ -72389,17 +72688,17 @@ class SlackClient extends BasePlatformClient {
|
|
|
72389
72688
|
}
|
|
72390
72689
|
const cached = this.userCache.get(userId);
|
|
72391
72690
|
if (cached) {
|
|
72392
|
-
|
|
72691
|
+
log41.debug(`User ${userId} found in cache: @${cached.name}`);
|
|
72393
72692
|
return this.normalizePlatformUser(cached);
|
|
72394
72693
|
}
|
|
72395
72694
|
try {
|
|
72396
72695
|
const response = await this.api("GET", `users.info?user=${userId}`);
|
|
72397
72696
|
this.userCache.set(userId, response.user);
|
|
72398
72697
|
this.usernameToIdCache.set(response.user.name, userId);
|
|
72399
|
-
|
|
72698
|
+
log41.debug(`User ${userId} fetched: @${response.user.name}`);
|
|
72400
72699
|
return this.normalizePlatformUser(response.user);
|
|
72401
72700
|
} catch (err) {
|
|
72402
|
-
|
|
72701
|
+
log41.warn(`Failed to get user ${userId}: ${err}`);
|
|
72403
72702
|
return null;
|
|
72404
72703
|
}
|
|
72405
72704
|
}
|
|
@@ -72409,7 +72708,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
72409
72708
|
return this.getUser(cachedId);
|
|
72410
72709
|
}
|
|
72411
72710
|
try {
|
|
72412
|
-
|
|
72711
|
+
log41.debug(`Looking up user by username: @${username}`);
|
|
72413
72712
|
let cursor;
|
|
72414
72713
|
do {
|
|
72415
72714
|
const params = cursor ? `cursor=${cursor}&limit=200` : "limit=200";
|
|
@@ -72418,16 +72717,16 @@ class SlackClient extends BasePlatformClient {
|
|
|
72418
72717
|
this.userCache.set(user.id, user);
|
|
72419
72718
|
this.usernameToIdCache.set(user.name, user.id);
|
|
72420
72719
|
if (user.name === username) {
|
|
72421
|
-
|
|
72720
|
+
log41.debug(`User @${username} found: ${user.id}`);
|
|
72422
72721
|
return this.normalizePlatformUser(user);
|
|
72423
72722
|
}
|
|
72424
72723
|
}
|
|
72425
72724
|
cursor = response.response_metadata?.next_cursor;
|
|
72426
72725
|
} while (cursor);
|
|
72427
|
-
|
|
72726
|
+
log41.warn(`User @${username} not found`);
|
|
72428
72727
|
return null;
|
|
72429
72728
|
} catch (err) {
|
|
72430
|
-
|
|
72729
|
+
log41.warn(`Failed to lookup user @${username}: ${err}`);
|
|
72431
72730
|
return null;
|
|
72432
72731
|
}
|
|
72433
72732
|
}
|
|
@@ -72515,19 +72814,19 @@ class SlackClient extends BasePlatformClient {
|
|
|
72515
72814
|
}
|
|
72516
72815
|
return null;
|
|
72517
72816
|
} catch (err) {
|
|
72518
|
-
|
|
72817
|
+
log41.debug(`Post ${postId.substring(0, 12)} not found: ${err}`);
|
|
72519
72818
|
return null;
|
|
72520
72819
|
}
|
|
72521
72820
|
}
|
|
72522
72821
|
async deletePost(postId) {
|
|
72523
|
-
|
|
72822
|
+
log41.debug(`Deleting post ${postId.substring(0, 12)}`);
|
|
72524
72823
|
await this.api("POST", "chat.delete", {
|
|
72525
72824
|
channel: this.channelId,
|
|
72526
72825
|
ts: postId
|
|
72527
72826
|
});
|
|
72528
72827
|
}
|
|
72529
72828
|
async pinPost(postId) {
|
|
72530
|
-
|
|
72829
|
+
log41.debug(`Pinning post ${postId.substring(0, 12)}`);
|
|
72531
72830
|
try {
|
|
72532
72831
|
await this.api("POST", "pins.add", {
|
|
72533
72832
|
channel: this.channelId,
|
|
@@ -72535,14 +72834,14 @@ class SlackClient extends BasePlatformClient {
|
|
|
72535
72834
|
}, 0, ["already_pinned"]);
|
|
72536
72835
|
} catch (err) {
|
|
72537
72836
|
if (err instanceof Error && err.message.includes("already_pinned")) {
|
|
72538
|
-
|
|
72837
|
+
log41.debug(`Post ${postId.substring(0, 12)} already pinned`);
|
|
72539
72838
|
return;
|
|
72540
72839
|
}
|
|
72541
72840
|
throw err;
|
|
72542
72841
|
}
|
|
72543
72842
|
}
|
|
72544
72843
|
async unpinPost(postId) {
|
|
72545
|
-
|
|
72844
|
+
log41.debug(`Unpinning post ${postId.substring(0, 12)}`);
|
|
72546
72845
|
try {
|
|
72547
72846
|
await this.api("POST", "pins.remove", {
|
|
72548
72847
|
channel: this.channelId,
|
|
@@ -72550,7 +72849,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
72550
72849
|
}, 0, ["no_pin"]);
|
|
72551
72850
|
} catch (err) {
|
|
72552
72851
|
if (err instanceof Error && err.message.includes("no_pin")) {
|
|
72553
|
-
|
|
72852
|
+
log41.debug(`Post ${postId.substring(0, 12)} was not pinned`);
|
|
72554
72853
|
return;
|
|
72555
72854
|
}
|
|
72556
72855
|
throw err;
|
|
@@ -72568,7 +72867,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
72568
72867
|
if (message.length <= maxLength) {
|
|
72569
72868
|
return message;
|
|
72570
72869
|
}
|
|
72571
|
-
|
|
72870
|
+
log41.warn(`Truncating message from ${message.length} to ~${maxLength} chars`);
|
|
72572
72871
|
return truncateMessageSafely(message, maxLength, "_... (truncated)_");
|
|
72573
72872
|
}
|
|
72574
72873
|
async getThreadHistory(threadId, options) {
|
|
@@ -72592,7 +72891,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
72592
72891
|
if (!cursor)
|
|
72593
72892
|
break;
|
|
72594
72893
|
if (page === MAX_PAGES - 1 && options?.limit) {
|
|
72595
|
-
|
|
72894
|
+
log41.warn(`Thread ${threadId} exceeds ${MAX_PAGES * 1000} messages — walk stopped early, the NEWEST messages are missing from context`);
|
|
72596
72895
|
}
|
|
72597
72896
|
}
|
|
72598
72897
|
const kept = filtered;
|
|
@@ -72609,13 +72908,13 @@ class SlackClient extends BasePlatformClient {
|
|
|
72609
72908
|
}
|
|
72610
72909
|
return messages;
|
|
72611
72910
|
} catch (err) {
|
|
72612
|
-
|
|
72911
|
+
log41.warn(`Failed to get thread history for ${threadId}: ${err}`);
|
|
72613
72912
|
return [];
|
|
72614
72913
|
}
|
|
72615
72914
|
}
|
|
72616
72915
|
async addReaction(postId, emojiName) {
|
|
72617
72916
|
const name = getEmojiName(emojiName);
|
|
72618
|
-
|
|
72917
|
+
log41.debug(`Adding reaction :${name}: to post ${postId.substring(0, 12)}`);
|
|
72619
72918
|
await this.api("POST", "reactions.add", {
|
|
72620
72919
|
channel: this.channelId,
|
|
72621
72920
|
timestamp: postId,
|
|
@@ -72624,7 +72923,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
72624
72923
|
}
|
|
72625
72924
|
async removeReaction(postId, emojiName) {
|
|
72626
72925
|
const name = getEmojiName(emojiName);
|
|
72627
|
-
|
|
72926
|
+
log41.debug(`Removing reaction :${name}: from post ${postId.substring(0, 12)}`);
|
|
72628
72927
|
await this.api("POST", "reactions.remove", {
|
|
72629
72928
|
channel: this.channelId,
|
|
72630
72929
|
timestamp: postId,
|
|
@@ -72650,7 +72949,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
72650
72949
|
}
|
|
72651
72950
|
sendTyping(_threadId) {}
|
|
72652
72951
|
async downloadFile(fileId) {
|
|
72653
|
-
|
|
72952
|
+
log41.debug(`Downloading file ${fileId}`);
|
|
72654
72953
|
const fileInfo = await this.api("GET", `files.info?file=${fileId}`);
|
|
72655
72954
|
const downloadUrl = fileInfo.file.url_private_download || fileInfo.file.url_private;
|
|
72656
72955
|
if (!downloadUrl) {
|
|
@@ -72662,11 +72961,11 @@ class SlackClient extends BasePlatformClient {
|
|
|
72662
72961
|
}
|
|
72663
72962
|
});
|
|
72664
72963
|
if (!response.ok) {
|
|
72665
|
-
|
|
72964
|
+
log41.warn(`Failed to download file ${fileId}: ${response.status}`);
|
|
72666
72965
|
throw new Error(`Failed to download file ${fileId}: ${response.status}`);
|
|
72667
72966
|
}
|
|
72668
72967
|
const arrayBuffer = await response.arrayBuffer();
|
|
72669
|
-
|
|
72968
|
+
log41.debug(`Downloaded file ${fileId}: ${arrayBuffer.byteLength} bytes`);
|
|
72670
72969
|
return Buffer.from(arrayBuffer);
|
|
72671
72970
|
}
|
|
72672
72971
|
async getFileInfo(fileId) {
|
|
@@ -73465,285 +73764,6 @@ function slackMessageToMcpPost(message, channelId, username) {
|
|
|
73465
73764
|
// src/session/manager.ts
|
|
73466
73765
|
import { EventEmitter as EventEmitter4 } from "events";
|
|
73467
73766
|
|
|
73468
|
-
// src/persistence/session-store.ts
|
|
73469
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync10 } from "fs";
|
|
73470
|
-
init_logger();
|
|
73471
|
-
import { homedir as homedir8 } from "os";
|
|
73472
|
-
import { join as join15 } from "path";
|
|
73473
|
-
var log41 = createLogger("persist");
|
|
73474
|
-
var STORE_VERSION3 = 2;
|
|
73475
|
-
var DEFAULT_CONFIG_DIR2 = join15(homedir8(), ".config", "claude-threads");
|
|
73476
|
-
var DEFAULT_SESSIONS_FILE = join15(DEFAULT_CONFIG_DIR2, "sessions.json");
|
|
73477
|
-
|
|
73478
|
-
class SessionStore {
|
|
73479
|
-
sessionsFile;
|
|
73480
|
-
configDir;
|
|
73481
|
-
constructor(sessionsPath) {
|
|
73482
|
-
const envPath = process.env.CLAUDE_THREADS_SESSIONS_PATH;
|
|
73483
|
-
const effectivePath = sessionsPath ?? envPath;
|
|
73484
|
-
if (effectivePath) {
|
|
73485
|
-
this.sessionsFile = effectivePath;
|
|
73486
|
-
this.configDir = join15(effectivePath, "..");
|
|
73487
|
-
} else {
|
|
73488
|
-
this.sessionsFile = DEFAULT_SESSIONS_FILE;
|
|
73489
|
-
this.configDir = DEFAULT_CONFIG_DIR2;
|
|
73490
|
-
}
|
|
73491
|
-
if (!existsSync13(this.configDir)) {
|
|
73492
|
-
mkdirSync7(this.configDir, { recursive: true });
|
|
73493
|
-
}
|
|
73494
|
-
}
|
|
73495
|
-
load() {
|
|
73496
|
-
const sessions = new Map;
|
|
73497
|
-
if (!existsSync13(this.sessionsFile)) {
|
|
73498
|
-
log41.debug("No sessions file found");
|
|
73499
|
-
return sessions;
|
|
73500
|
-
}
|
|
73501
|
-
try {
|
|
73502
|
-
const data = this.loadRaw();
|
|
73503
|
-
if (data.version === 1) {
|
|
73504
|
-
log41.info("Migrating sessions from v1 to v2 (adding platformId)");
|
|
73505
|
-
const newSessions = {};
|
|
73506
|
-
for (const [_oldKey, session] of Object.entries(data.sessions)) {
|
|
73507
|
-
const v1Session = session;
|
|
73508
|
-
if (!v1Session.platformId) {
|
|
73509
|
-
v1Session.platformId = "default";
|
|
73510
|
-
}
|
|
73511
|
-
const newKey = `${v1Session.platformId}:${v1Session.threadId}`;
|
|
73512
|
-
newSessions[newKey] = v1Session;
|
|
73513
|
-
}
|
|
73514
|
-
data.sessions = newSessions;
|
|
73515
|
-
data.version = 2;
|
|
73516
|
-
this.writeAtomic(data);
|
|
73517
|
-
} else if (data.version !== STORE_VERSION3) {
|
|
73518
|
-
log41.warn(`Sessions file version ${data.version} not supported, starting fresh`);
|
|
73519
|
-
return sessions;
|
|
73520
|
-
}
|
|
73521
|
-
for (const session of Object.values(data.sessions)) {
|
|
73522
|
-
if (session.cleanedAt)
|
|
73523
|
-
continue;
|
|
73524
|
-
const sessionId = `${session.platformId}:${session.threadId}`;
|
|
73525
|
-
sessions.set(sessionId, session);
|
|
73526
|
-
}
|
|
73527
|
-
log41.debug(`Loaded ${sessions.size} active session(s)`);
|
|
73528
|
-
} catch (err) {
|
|
73529
|
-
log41.error(`Failed to load sessions: ${err}`);
|
|
73530
|
-
}
|
|
73531
|
-
return sessions;
|
|
73532
|
-
}
|
|
73533
|
-
save(sessionId, session) {
|
|
73534
|
-
const data = this.loadRaw();
|
|
73535
|
-
data.sessions[sessionId] = session;
|
|
73536
|
-
this.writeAtomic(data);
|
|
73537
|
-
const shortId = sessionId.substring(0, 20);
|
|
73538
|
-
log41.debug(`Saved session ${shortId}...`);
|
|
73539
|
-
}
|
|
73540
|
-
remove(sessionId) {
|
|
73541
|
-
const data = this.loadRaw();
|
|
73542
|
-
if (data.sessions[sessionId]) {
|
|
73543
|
-
delete data.sessions[sessionId];
|
|
73544
|
-
this.writeAtomic(data);
|
|
73545
|
-
const shortId = sessionId.substring(0, 20);
|
|
73546
|
-
log41.debug(`Removed session ${shortId}...`);
|
|
73547
|
-
}
|
|
73548
|
-
}
|
|
73549
|
-
softDelete(sessionId) {
|
|
73550
|
-
const data = this.loadRaw();
|
|
73551
|
-
if (data.sessions[sessionId]) {
|
|
73552
|
-
data.sessions[sessionId].cleanedAt = new Date().toISOString();
|
|
73553
|
-
this.writeAtomic(data);
|
|
73554
|
-
const shortId = sessionId.substring(0, 20);
|
|
73555
|
-
log41.debug(`Soft-deleted session ${shortId}...`);
|
|
73556
|
-
}
|
|
73557
|
-
}
|
|
73558
|
-
cleanStale(maxAgeMs) {
|
|
73559
|
-
const data = this.loadRaw();
|
|
73560
|
-
const now = Date.now();
|
|
73561
|
-
const staleIds = [];
|
|
73562
|
-
for (const [sessionId, session] of Object.entries(data.sessions)) {
|
|
73563
|
-
if (session.cleanedAt)
|
|
73564
|
-
continue;
|
|
73565
|
-
const lastActivity = new Date(session.lastActivityAt).getTime();
|
|
73566
|
-
if (now - lastActivity > maxAgeMs) {
|
|
73567
|
-
staleIds.push(sessionId);
|
|
73568
|
-
session.cleanedAt = new Date().toISOString();
|
|
73569
|
-
}
|
|
73570
|
-
}
|
|
73571
|
-
if (staleIds.length > 0) {
|
|
73572
|
-
this.writeAtomic(data);
|
|
73573
|
-
log41.debug(`Soft-deleted ${staleIds.length} stale session(s)`);
|
|
73574
|
-
}
|
|
73575
|
-
return staleIds;
|
|
73576
|
-
}
|
|
73577
|
-
cleanHistory(historyRetentionMs = 3 * 24 * 60 * 60 * 1000) {
|
|
73578
|
-
const data = this.loadRaw();
|
|
73579
|
-
const now = Date.now();
|
|
73580
|
-
let removedCount = 0;
|
|
73581
|
-
for (const [sessionId, session] of Object.entries(data.sessions)) {
|
|
73582
|
-
if (!session.cleanedAt)
|
|
73583
|
-
continue;
|
|
73584
|
-
const cleanedTime = new Date(session.cleanedAt).getTime();
|
|
73585
|
-
if (now - cleanedTime > historyRetentionMs) {
|
|
73586
|
-
delete data.sessions[sessionId];
|
|
73587
|
-
removedCount++;
|
|
73588
|
-
}
|
|
73589
|
-
}
|
|
73590
|
-
if (removedCount > 0) {
|
|
73591
|
-
this.writeAtomic(data);
|
|
73592
|
-
log41.debug(`Permanently removed ${removedCount} old session(s) from history`);
|
|
73593
|
-
}
|
|
73594
|
-
return removedCount;
|
|
73595
|
-
}
|
|
73596
|
-
getHistory(platformId, activeSessions) {
|
|
73597
|
-
const data = this.loadRaw();
|
|
73598
|
-
const historySessions = [];
|
|
73599
|
-
for (const [sessionId, session] of Object.entries(data.sessions)) {
|
|
73600
|
-
if (session.platformId !== platformId)
|
|
73601
|
-
continue;
|
|
73602
|
-
if (session.cleanedAt) {
|
|
73603
|
-
historySessions.push(session);
|
|
73604
|
-
continue;
|
|
73605
|
-
}
|
|
73606
|
-
if (session.lifecyclePostId && activeSessions && !activeSessions.has(sessionId)) {
|
|
73607
|
-
historySessions.push(session);
|
|
73608
|
-
}
|
|
73609
|
-
}
|
|
73610
|
-
return historySessions.sort((a, b) => {
|
|
73611
|
-
const aTime = new Date(a.cleanedAt || a.lastActivityAt).getTime();
|
|
73612
|
-
const bTime = new Date(b.cleanedAt || b.lastActivityAt).getTime();
|
|
73613
|
-
return bTime - aTime;
|
|
73614
|
-
});
|
|
73615
|
-
}
|
|
73616
|
-
clear() {
|
|
73617
|
-
const data = this.loadRaw();
|
|
73618
|
-
this.writeAtomic({ version: STORE_VERSION3, sessions: {}, stickyPostIds: data.stickyPostIds });
|
|
73619
|
-
log41.debug("Cleared all sessions");
|
|
73620
|
-
}
|
|
73621
|
-
saveStickyPostId(platformId, postId) {
|
|
73622
|
-
const data = this.loadRaw();
|
|
73623
|
-
if (!data.stickyPostIds) {
|
|
73624
|
-
data.stickyPostIds = {};
|
|
73625
|
-
}
|
|
73626
|
-
data.stickyPostIds[platformId] = postId;
|
|
73627
|
-
this.writeAtomic(data);
|
|
73628
|
-
log41.debug(`Saved sticky post ID for ${platformId}: ${postId.substring(0, 8)}...`);
|
|
73629
|
-
}
|
|
73630
|
-
getStickyPostIds() {
|
|
73631
|
-
const data = this.loadRaw();
|
|
73632
|
-
return new Map(Object.entries(data.stickyPostIds || {}));
|
|
73633
|
-
}
|
|
73634
|
-
removeStickyPostId(platformId) {
|
|
73635
|
-
const data = this.loadRaw();
|
|
73636
|
-
if (data.stickyPostIds && data.stickyPostIds[platformId]) {
|
|
73637
|
-
delete data.stickyPostIds[platformId];
|
|
73638
|
-
this.writeAtomic(data);
|
|
73639
|
-
log41.debug(`Removed sticky post ID for ${platformId}`);
|
|
73640
|
-
}
|
|
73641
|
-
}
|
|
73642
|
-
getPlatformEnabledState() {
|
|
73643
|
-
const data = this.loadRaw();
|
|
73644
|
-
return new Map(Object.entries(data.platformEnabledState || {}));
|
|
73645
|
-
}
|
|
73646
|
-
isPlatformEnabled(platformId) {
|
|
73647
|
-
const data = this.loadRaw();
|
|
73648
|
-
return data.platformEnabledState?.[platformId] ?? true;
|
|
73649
|
-
}
|
|
73650
|
-
setPlatformEnabled(platformId, enabled) {
|
|
73651
|
-
const data = this.loadRaw();
|
|
73652
|
-
if (!data.platformEnabledState) {
|
|
73653
|
-
data.platformEnabledState = {};
|
|
73654
|
-
}
|
|
73655
|
-
data.platformEnabledState[platformId] = enabled;
|
|
73656
|
-
this.writeAtomic(data);
|
|
73657
|
-
log41.debug(`Set platform ${platformId} enabled state to ${enabled}`);
|
|
73658
|
-
}
|
|
73659
|
-
findByThread(platformId, threadId) {
|
|
73660
|
-
const sessionId = `${platformId}:${threadId}`;
|
|
73661
|
-
const data = this.loadRaw();
|
|
73662
|
-
return data.sessions[sessionId];
|
|
73663
|
-
}
|
|
73664
|
-
findByThreadIdAnyState(threadId, platformId) {
|
|
73665
|
-
const data = this.loadRaw();
|
|
73666
|
-
for (const session of Object.values(data.sessions)) {
|
|
73667
|
-
if (session.threadId !== threadId)
|
|
73668
|
-
continue;
|
|
73669
|
-
if (platformId !== undefined && session.platformId !== platformId)
|
|
73670
|
-
continue;
|
|
73671
|
-
return session;
|
|
73672
|
-
}
|
|
73673
|
-
return;
|
|
73674
|
-
}
|
|
73675
|
-
findByPostId(platformId, postId) {
|
|
73676
|
-
const data = this.loadRaw();
|
|
73677
|
-
for (const session of Object.values(data.sessions)) {
|
|
73678
|
-
if (session.platformId !== platformId)
|
|
73679
|
-
continue;
|
|
73680
|
-
if (session.lifecyclePostId === postId || session.sessionStartPostId === postId) {
|
|
73681
|
-
return session;
|
|
73682
|
-
}
|
|
73683
|
-
}
|
|
73684
|
-
return;
|
|
73685
|
-
}
|
|
73686
|
-
getStats() {
|
|
73687
|
-
const data = this.loadRaw();
|
|
73688
|
-
return data.stats ?? { totalSessionsStarted: 0 };
|
|
73689
|
-
}
|
|
73690
|
-
recordSessionStarted() {
|
|
73691
|
-
const data = this.loadRaw();
|
|
73692
|
-
const stats = data.stats ?? { totalSessionsStarted: 0 };
|
|
73693
|
-
stats.totalSessionsStarted = (stats.totalSessionsStarted ?? 0) + 1;
|
|
73694
|
-
const milestone = milestoneReached(stats.totalSessionsStarted);
|
|
73695
|
-
if (milestone) {
|
|
73696
|
-
stats.milestone = { n: milestone, reachedAt: new Date().toISOString() };
|
|
73697
|
-
}
|
|
73698
|
-
data.stats = stats;
|
|
73699
|
-
this.writeAtomic(data);
|
|
73700
|
-
return stats.totalSessionsStarted;
|
|
73701
|
-
}
|
|
73702
|
-
lastReadDegraded = false;
|
|
73703
|
-
loadRaw() {
|
|
73704
|
-
if (!existsSync13(this.sessionsFile)) {
|
|
73705
|
-
this.lastReadDegraded = false;
|
|
73706
|
-
return { version: STORE_VERSION3, sessions: {} };
|
|
73707
|
-
}
|
|
73708
|
-
try {
|
|
73709
|
-
const raw = readFileSync10(this.sessionsFile, "utf-8");
|
|
73710
|
-
if (raw.trim() === "") {
|
|
73711
|
-
this.lastReadDegraded = false;
|
|
73712
|
-
return { version: STORE_VERSION3, sessions: {} };
|
|
73713
|
-
}
|
|
73714
|
-
const data = JSON.parse(raw);
|
|
73715
|
-
if (!data || typeof data !== "object") {
|
|
73716
|
-
this.lastReadDegraded = true;
|
|
73717
|
-
return { version: STORE_VERSION3, sessions: {} };
|
|
73718
|
-
}
|
|
73719
|
-
if (data.sessions === undefined || data.sessions === null) {
|
|
73720
|
-
this.lastReadDegraded = false;
|
|
73721
|
-
data.sessions = {};
|
|
73722
|
-
} else if (typeof data.sessions !== "object") {
|
|
73723
|
-
this.lastReadDegraded = true;
|
|
73724
|
-
data.sessions = {};
|
|
73725
|
-
} else {
|
|
73726
|
-
this.lastReadDegraded = false;
|
|
73727
|
-
}
|
|
73728
|
-
if (!data.version) {
|
|
73729
|
-
data.version = STORE_VERSION3;
|
|
73730
|
-
}
|
|
73731
|
-
return data;
|
|
73732
|
-
} catch (err) {
|
|
73733
|
-
log41.warn(`Failed to read ${this.sessionsFile}: ${err.message} — reads degrade to empty`);
|
|
73734
|
-
this.lastReadDegraded = true;
|
|
73735
|
-
return { version: STORE_VERSION3, sessions: {} };
|
|
73736
|
-
}
|
|
73737
|
-
}
|
|
73738
|
-
writeAtomic(data) {
|
|
73739
|
-
if (this.lastReadDegraded) {
|
|
73740
|
-
log41.error(`Refusing to write ${this.sessionsFile}: the last read of the existing file was degraded — writing would destroy persisted sessions`);
|
|
73741
|
-
return;
|
|
73742
|
-
}
|
|
73743
|
-
writeFileAtomic(this.sessionsFile, JSON.stringify(data, null, 2));
|
|
73744
|
-
}
|
|
73745
|
-
}
|
|
73746
|
-
|
|
73747
73767
|
// src/watches/evaluator.ts
|
|
73748
73768
|
init_quick_query();
|
|
73749
73769
|
|
|
@@ -74817,6 +74837,10 @@ async function tryResumeFromReaction(deps, platformId, postId, username) {
|
|
|
74817
74837
|
const persistedSession = deps.sessionStore.findByPostId(platformId, postId);
|
|
74818
74838
|
if (!persistedSession)
|
|
74819
74839
|
return false;
|
|
74840
|
+
if (!isRevivable(persistedSession)) {
|
|
74841
|
+
log50.debug(`Ignoring resume reaction on stopped session ${persistedSession.threadId.substring(0, 8)}...`);
|
|
74842
|
+
return false;
|
|
74843
|
+
}
|
|
74820
74844
|
const sessionId = `${platformId}:${persistedSession.threadId}`;
|
|
74821
74845
|
if (deps.registry.hasById(sessionId))
|
|
74822
74846
|
return false;
|
|
@@ -75318,7 +75342,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
75318
75342
|
this.sessionStore.save(session.sessionId, state);
|
|
75319
75343
|
}
|
|
75320
75344
|
unpersistSession(sessionId) {
|
|
75321
|
-
this.sessionStore.softDelete(sessionId);
|
|
75345
|
+
this.sessionStore.softDelete(sessionId, "stopped");
|
|
75322
75346
|
}
|
|
75323
75347
|
async updateSessionHeader(session) {
|
|
75324
75348
|
await updateSessionHeader(session, this.getContext());
|
|
@@ -75560,7 +75584,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
75560
75584
|
const persisted = this.registry.getPersistedByThreadId(threadId, platformId);
|
|
75561
75585
|
if (persisted) {
|
|
75562
75586
|
const sessionId = `${persisted.platformId}:${persisted.threadId}`;
|
|
75563
|
-
this.sessionStore.softDelete(sessionId);
|
|
75587
|
+
this.sessionStore.softDelete(sessionId, "stopped");
|
|
75564
75588
|
}
|
|
75565
75589
|
}
|
|
75566
75590
|
async killSession(threadId, unpersist = true) {
|
|
@@ -85501,6 +85525,7 @@ init_logger();
|
|
|
85501
85525
|
// src/message-handler.ts
|
|
85502
85526
|
init_logger();
|
|
85503
85527
|
var ackLog = createLogger("ack");
|
|
85528
|
+
var PAUSED_SAFE_COMMANDS = new Set(["help", "release-notes", "usage"]);
|
|
85504
85529
|
var BOLD = String.raw`(?:\*{1,2}|_{1,2})?`;
|
|
85505
85530
|
var STATUS_POST_PATTERNS = [
|
|
85506
85531
|
/^⚠️\s+\S+ is not authorized\b/u,
|
|
@@ -85675,7 +85700,30 @@ async function handleMessage(client, session, post2, user, options) {
|
|
|
85675
85700
|
await client.createPost(`\uD83D\uDED1 ${formatter.formatBold("Session cancelled")} by ${formatter.formatUserMention(username)}`, threadRoot);
|
|
85676
85701
|
}
|
|
85677
85702
|
}
|
|
85703
|
+
return;
|
|
85704
|
+
}
|
|
85705
|
+
if (!client.isUserAllowed(username)) {
|
|
85706
|
+
logger?.debug?.(`!${pausedParsed.command} from unauthorized @${username} in paused thread ${threadRoot} — dropped`);
|
|
85707
|
+
return;
|
|
85708
|
+
}
|
|
85709
|
+
if (!PAUSED_SAFE_COMMANDS.has(pausedParsed.command)) {
|
|
85710
|
+
logger?.debug?.(`!${pausedParsed.command} from @${username} needs an active session; thread ${threadRoot} is paused — dropped`);
|
|
85711
|
+
return;
|
|
85678
85712
|
}
|
|
85713
|
+
const immediateCtx = {
|
|
85714
|
+
commandContext: "first-message",
|
|
85715
|
+
threadId: threadRoot,
|
|
85716
|
+
username,
|
|
85717
|
+
client,
|
|
85718
|
+
sessionManager: session,
|
|
85719
|
+
formatter,
|
|
85720
|
+
isAllowed: true,
|
|
85721
|
+
files: post2.metadata?.files
|
|
85722
|
+
};
|
|
85723
|
+
const immediate = await executeCommand(pausedParsed.command, pausedParsed.args, immediateCtx);
|
|
85724
|
+
if (immediate.handled)
|
|
85725
|
+
return;
|
|
85726
|
+
logger?.debug?.(`!${pausedParsed.command} from @${username} needs an active session; thread ${threadRoot} is paused — dropped`);
|
|
85679
85727
|
return;
|
|
85680
85728
|
}
|
|
85681
85729
|
const persistedSession = session.getPersistedSession(threadRoot, platformId);
|
|
@@ -85735,6 +85783,14 @@ async function handleMessage(client, session, post2, user, options) {
|
|
|
85735
85783
|
isAllowed: true,
|
|
85736
85784
|
files
|
|
85737
85785
|
};
|
|
85786
|
+
const firstMessageCommand = parseCommand(prompt);
|
|
85787
|
+
if (firstMessageCommand && !firstMessageCommand.args?.trim()) {
|
|
85788
|
+
const def = COMMAND_REGISTRY.find((c) => c.command === firstMessageCommand.command);
|
|
85789
|
+
if (def && !def.worksInFirstMessage) {
|
|
85790
|
+
logger?.debug?.(`!${firstMessageCommand.command} needs an active session; ${threadRoot} has none — dropped`);
|
|
85791
|
+
return;
|
|
85792
|
+
}
|
|
85793
|
+
}
|
|
85738
85794
|
let continueProcessing = true;
|
|
85739
85795
|
while (continueProcessing) {
|
|
85740
85796
|
continueProcessing = false;
|