claude-threads 1.24.3 → 1.25.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.
@@ -15725,7 +15725,7 @@ var require_websocket = __commonJS((exports, module) => {
15725
15725
  var http = __require("http");
15726
15726
  var net = __require("net");
15727
15727
  var tls = __require("tls");
15728
- var { randomBytes, createHash } = __require("crypto");
15728
+ var { randomBytes, createHash: createHash2 } = __require("crypto");
15729
15729
  var { Duplex, Readable } = __require("stream");
15730
15730
  var { URL: URL2 } = __require("url");
15731
15731
  var PerMessageDeflate = require_permessage_deflate();
@@ -16268,7 +16268,7 @@ var require_websocket = __commonJS((exports, module) => {
16268
16268
  abortHandshake(websocket, socket, "Invalid Upgrade header");
16269
16269
  return;
16270
16270
  }
16271
- const digest = createHash("sha1").update(key + GUID).digest("base64");
16271
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
16272
16272
  if (res.headers["sec-websocket-accept"] !== digest) {
16273
16273
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
16274
16274
  return;
@@ -16643,7 +16643,7 @@ var require_websocket_server = __commonJS((exports, module) => {
16643
16643
  var EventEmitter3 = __require("events");
16644
16644
  var http = __require("http");
16645
16645
  var { Duplex } = __require("stream");
16646
- var { createHash } = __require("crypto");
16646
+ var { createHash: createHash2 } = __require("crypto");
16647
16647
  var extension = require_extension();
16648
16648
  var PerMessageDeflate = require_permessage_deflate();
16649
16649
  var subprotocol = require_subprotocol();
@@ -16858,7 +16858,7 @@ var require_websocket_server = __commonJS((exports, module) => {
16858
16858
  }
16859
16859
  if (this._state > RUNNING)
16860
16860
  return abortHandshake(socket, 503);
16861
- const digest = createHash("sha1").update(key + GUID).digest("base64");
16861
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
16862
16862
  const headers = [
16863
16863
  "HTTP/1.1 101 Switching Protocols",
16864
16864
  "Upgrade: websocket",
@@ -55395,7 +55395,48 @@ function formatReleaseNotes(notes, formatter) {
55395
55395
  // src/utils/keep-alive.ts
55396
55396
  import { spawn } from "child_process";
55397
55397
  var log5 = createLogger("keepalive");
55398
-
55398
+ function keepAliveSpawnSpec(platform, parentPid) {
55399
+ switch (platform) {
55400
+ case "darwin":
55401
+ return {
55402
+ command: "caffeinate",
55403
+ args: ["-s", "-i", "-w", String(parentPid)],
55404
+ stdio: "ignore"
55405
+ };
55406
+ case "linux":
55407
+ return {
55408
+ command: "systemd-inhibit",
55409
+ args: [
55410
+ "--what=sleep:idle:handle-lid-switch",
55411
+ "--why=Claude Code session active",
55412
+ "--mode=block",
55413
+ "cat"
55414
+ ],
55415
+ stdio: ["pipe", "ignore", "ignore"]
55416
+ };
55417
+ default:
55418
+ return null;
55419
+ }
55420
+ }
55421
+ function linuxFallbackScript(parentPid) {
55422
+ return `while kill -0 ${parentPid} 2>/dev/null; do xdg-screensaver reset 2>/dev/null || true; sleep 60; done`;
55423
+ }
55424
+ function windowsScript(parentPid) {
55425
+ return `
55426
+ Add-Type -TypeDefinition @"
55427
+ using System;
55428
+ using System.Runtime.InteropServices;
55429
+ public class PowerState {
55430
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
55431
+ public static extern uint SetThreadExecutionState(uint esFlags);
55432
+ }
55433
+ "@
55434
+ # ES_CONTINUOUS | ES_SYSTEM_REQUIRED
55435
+ [PowerState]::SetThreadExecutionState(0x80000001) | Out-Null
55436
+ # Keep running until killed or the parent process exits
55437
+ while (Get-Process -Id ${parentPid} -ErrorAction SilentlyContinue) { Start-Sleep -Seconds 60 }
55438
+ `;
55439
+ }
55399
55440
  class KeepAliveManager {
55400
55441
  activeSessionCount = 0;
55401
55442
  keepAliveProcess = null;
@@ -55472,8 +55513,11 @@ class KeepAliveManager {
55472
55513
  }
55473
55514
  startMacOSKeepAlive() {
55474
55515
  try {
55475
- this.keepAliveProcess = spawn("caffeinate", ["-s", "-i"], {
55476
- stdio: "ignore",
55516
+ const spec = keepAliveSpawnSpec("darwin", process.pid);
55517
+ if (!spec)
55518
+ return;
55519
+ this.keepAliveProcess = spawn(spec.command, spec.args, {
55520
+ stdio: spec.stdio,
55477
55521
  detached: false
55478
55522
  });
55479
55523
  this.keepAliveProcess.on("error", (err) => {
@@ -55493,14 +55537,11 @@ class KeepAliveManager {
55493
55537
  }
55494
55538
  startLinuxKeepAlive() {
55495
55539
  try {
55496
- this.keepAliveProcess = spawn("systemd-inhibit", [
55497
- "--what=sleep:idle:handle-lid-switch",
55498
- "--why=Claude Code session active",
55499
- "--mode=block",
55500
- "sleep",
55501
- "infinity"
55502
- ], {
55503
- stdio: "ignore",
55540
+ const spec = keepAliveSpawnSpec("linux", process.pid);
55541
+ if (!spec)
55542
+ return;
55543
+ this.keepAliveProcess = spawn(spec.command, spec.args, {
55544
+ stdio: spec.stdio,
55504
55545
  detached: false
55505
55546
  });
55506
55547
  this.keepAliveProcess.on("error", (err) => {
@@ -55522,10 +55563,7 @@ class KeepAliveManager {
55522
55563
  }
55523
55564
  startLinuxKeepAliveFallback() {
55524
55565
  try {
55525
- this.keepAliveProcess = spawn("bash", [
55526
- "-c",
55527
- `while true; do xdg-screensaver reset 2>/dev/null || true; sleep 60; done`
55528
- ], {
55566
+ this.keepAliveProcess = spawn("bash", ["-c", linuxFallbackScript(process.pid)], {
55529
55567
  stdio: "ignore",
55530
55568
  detached: false
55531
55569
  });
@@ -55543,20 +55581,7 @@ class KeepAliveManager {
55543
55581
  }
55544
55582
  startWindowsKeepAlive() {
55545
55583
  try {
55546
- const script = `
55547
- Add-Type -TypeDefinition @"
55548
- using System;
55549
- using System.Runtime.InteropServices;
55550
- public class PowerState {
55551
- [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
55552
- public static extern uint SetThreadExecutionState(uint esFlags);
55553
- }
55554
- "@
55555
- # ES_CONTINUOUS | ES_SYSTEM_REQUIRED
55556
- [PowerState]::SetThreadExecutionState(0x80000001) | Out-Null
55557
- # Keep running until killed
55558
- while ($true) { Start-Sleep -Seconds 60 }
55559
- `;
55584
+ const script = windowsScript(process.pid);
55560
55585
  this.keepAliveProcess = spawn("powershell", ["-NoProfile", "-Command", script], {
55561
55586
  stdio: "ignore",
55562
55587
  detached: false,
@@ -56131,6 +56156,9 @@ function buildClaudeChildEnv(parentEnv, account, opts) {
56131
56156
  if (env.ENABLE_PROMPT_CACHING_1H === undefined) {
56132
56157
  env.ENABLE_PROMPT_CACHING_1H = "true";
56133
56158
  }
56159
+ if (opts?.disableAutoMemory) {
56160
+ env.CLAUDE_CODE_DISABLE_AUTO_MEMORY = "1";
56161
+ }
56134
56162
  if (opts?.decisionBridge && env.MCP_TOOL_TIMEOUT === undefined) {
56135
56163
  env.MCP_TOOL_TIMEOUT = "3600000";
56136
56164
  }
@@ -56145,6 +56173,21 @@ function buildClaudeChildEnv(parentEnv, account, opts) {
56145
56173
  }
56146
56174
  return env;
56147
56175
  }
56176
+ function buildInlineSettings(statusLineCommand, memory) {
56177
+ const settings = {};
56178
+ if (statusLineCommand) {
56179
+ settings.statusLine = {
56180
+ type: "command",
56181
+ command: statusLineCommand,
56182
+ padding: 0
56183
+ };
56184
+ }
56185
+ if (memory) {
56186
+ settings.autoMemoryEnabled = true;
56187
+ settings.autoMemoryDirectory = memory.autoMemoryDir;
56188
+ }
56189
+ return Object.keys(settings).length > 0 ? settings : null;
56190
+ }
56148
56191
  function runtimeForScriptPath(scriptPath) {
56149
56192
  return scriptPath.endsWith(".ts") ? process.execPath : "node";
56150
56193
  }
@@ -56342,18 +56385,16 @@ class ClaudeCli extends EventEmitter2 {
56342
56385
  if (this.options.appendSystemPrompt) {
56343
56386
  args.push("--append-system-prompt", this.options.appendSystemPrompt);
56344
56387
  }
56388
+ let statusLineCommand;
56345
56389
  if (this.options.sessionId) {
56346
56390
  this.statusFilePath = join4(tmpdir2(), `claude-threads-status-${this.options.sessionId}.json`);
56347
56391
  const statusLineWriterPath = this.getStatusLineWriterPath();
56348
56392
  const runtime = runtimeForScriptPath(statusLineWriterPath);
56349
- const statusLineSettings = {
56350
- statusLine: {
56351
- type: "command",
56352
- command: `${runtime} ${statusLineWriterPath} ${this.options.sessionId}`,
56353
- padding: 0
56354
- }
56355
- };
56356
- args.push("--settings", JSON.stringify(statusLineSettings));
56393
+ statusLineCommand = `${runtime} ${statusLineWriterPath} ${this.options.sessionId}`;
56394
+ }
56395
+ const settings = buildInlineSettings(statusLineCommand, this.options.memory);
56396
+ if (settings) {
56397
+ args.push("--settings", JSON.stringify(settings));
56357
56398
  }
56358
56399
  this.log.debug(`Starting: ${claudePath} ${args.slice(0, 5).join(" ")}...`);
56359
56400
  const childEnv = this.buildChildEnv();
@@ -56575,7 +56616,8 @@ class ClaudeCli extends EventEmitter2 {
56575
56616
  }
56576
56617
  buildChildEnv() {
56577
56618
  return buildClaudeChildEnv(process.env, this.options.account, {
56578
- decisionBridge: this.options.decisionBridgePath !== undefined
56619
+ decisionBridge: this.options.decisionBridgePath !== undefined,
56620
+ disableAutoMemory: this.options.memory === null
56579
56621
  });
56580
56622
  }
56581
56623
  getMcpServerPath() {
@@ -56723,6 +56765,25 @@ var COMMAND_REGISTRY = [
56723
56765
  audience: "user",
56724
56766
  claudeNotes: "User decisions, not yours"
56725
56767
  },
56768
+ {
56769
+ command: "remember",
56770
+ description: "Save a note to this channel's shared memory (visible to all future sessions here)",
56771
+ args: "<text>",
56772
+ category: "settings",
56773
+ audience: "user",
56774
+ claudeNotes: "User decisions, not yours"
56775
+ },
56776
+ {
56777
+ command: "memory",
56778
+ description: "Show channel memory; forget removes entries",
56779
+ args: "[forget <n|text> | forget all]",
56780
+ category: "settings",
56781
+ audience: "user",
56782
+ claudeNotes: "User decisions, not yours",
56783
+ subcommands: [
56784
+ { name: "forget", description: "Remove one entry (by number or matching text), or all", args: "<n|text> | all" }
56785
+ ]
56786
+ },
56726
56787
  {
56727
56788
  command: "update",
56728
56789
  description: "Show auto-update status",
@@ -56963,6 +57024,40 @@ var handleGitHubEmail = async (ctx, args) => {
56963
57024
  await ctx.sessionManager.setGitHubEmail(ctx.threadId, ctx.username, args);
56964
57025
  return { handled: true };
56965
57026
  };
57027
+ var handleRemember = async (ctx, args) => {
57028
+ if (ctx.commandContext === "first-message") {
57029
+ return { handled: false };
57030
+ }
57031
+ if (!ctx.isAllowed) {
57032
+ return { handled: true };
57033
+ }
57034
+ if (!args?.trim()) {
57035
+ await ctx.client.createPost(`⚠️ Usage: ${ctx.formatter.formatCode("!remember <text>")}`, ctx.threadId);
57036
+ return { handled: true };
57037
+ }
57038
+ await ctx.sessionManager.rememberEntry(ctx.threadId, args, ctx.username);
57039
+ return { handled: true };
57040
+ };
57041
+ var handleMemory = async (ctx, args) => {
57042
+ if (ctx.commandContext === "first-message") {
57043
+ return { handled: false };
57044
+ }
57045
+ if (!ctx.isAllowed) {
57046
+ return { handled: true };
57047
+ }
57048
+ const trimmed = args?.trim();
57049
+ if (!trimmed) {
57050
+ await ctx.sessionManager.showMemory(ctx.threadId, ctx.username);
57051
+ return { handled: true };
57052
+ }
57053
+ const forgetMatch = trimmed.match(/^forget\s+([\s\S]+)$/i);
57054
+ if (forgetMatch) {
57055
+ await ctx.sessionManager.forgetMemory(ctx.threadId, forgetMatch[1].trim(), ctx.username);
57056
+ return { handled: true };
57057
+ }
57058
+ await ctx.client.createPost(`⚠️ Usage: ${ctx.formatter.formatCode("!memory")} or ${ctx.formatter.formatCode("!memory forget <n|text>")} or ${ctx.formatter.formatCode("!memory forget all")}`, ctx.threadId);
57059
+ return { handled: true };
57060
+ };
56966
57061
  var handleCd = async (ctx, args) => {
56967
57062
  if (!args) {
56968
57063
  return { handled: false };
@@ -57152,6 +57247,8 @@ handlers.set("approve", handleApprove);
57152
57247
  handlers.set("invite", handleInvite);
57153
57248
  handlers.set("kick", handleKick);
57154
57249
  handlers.set("github-email", handleGitHubEmail);
57250
+ handlers.set("remember", handleRemember);
57251
+ handlers.set("memory", handleMemory);
57155
57252
  handlers.set("cd", handleCd);
57156
57253
  handlers.set("permissions", handlePermissions);
57157
57254
  handlers.set("mentions", handleMentions);
@@ -57290,9 +57387,257 @@ var log16 = createLogger("context");
57290
57387
  var sessionLog2 = createSessionLog(log16);
57291
57388
  var contextPromptTimeouts = new Map;
57292
57389
  var contextPromptFiles = new Map;
57390
+ // src/memory/store.ts
57391
+ import { createHash } from "crypto";
57392
+ import {
57393
+ chmodSync,
57394
+ existsSync as existsSync5,
57395
+ mkdirSync,
57396
+ readFileSync as readFileSync4,
57397
+ renameSync,
57398
+ realpathSync,
57399
+ writeFileSync as writeFileSync2
57400
+ } from "fs";
57401
+ import { homedir as homedir4 } from "os";
57402
+ import { basename as basename3, dirname as dirname7, join as join6, sep as sep2 } from "path";
57403
+ var log17 = createLogger("memory");
57404
+ var DEFAULT_ROOT = join6(homedir4(), ".config", "claude-threads", "memory");
57405
+ var CHANNEL_BLOCK_MAX_LINES = 200;
57406
+ var CHANNEL_BLOCK_MAX_BYTES = 25 * 1024;
57407
+ var CHANNEL_FILE_MAX_ENTRIES = 400;
57408
+ var MAX_ENTRY_LENGTH = 500;
57409
+ var FILE_HEADER = "# Channel memory — managed by claude-threads.";
57410
+ var ENTRY_RE = /^- \[(\d{4}-\d{2}-\d{2})\] \((@[^\s)]+|distilled)\) (.+)$/;
57411
+ function safeIdSegment(id) {
57412
+ return id.replace(/[^A-Za-z0-9._-]/g, "_");
57413
+ }
57414
+ function shortHash(value, length) {
57415
+ return createHash("sha256").update(value).digest("hex").slice(0, length);
57416
+ }
57417
+ function platformSegment(platformId) {
57418
+ return `${safeIdSegment(platformId) || "platform"}-${shortHash(platformId, 6)}`;
57419
+ }
57420
+ function normalizeForDedupe(text) {
57421
+ return text.toLowerCase().replace(/\s+/g, " ").replace(/[.!?\s]+$/g, "").trim();
57422
+ }
57423
+ function collapseEntryText(text) {
57424
+ return text.replace(/\s*[\r\n]+\s*/g, "; ").replace(/\s+/g, " ").trim();
57425
+ }
57426
+ function sanitizeEntryText(text) {
57427
+ return collapseEntryText(text).slice(0, MAX_ENTRY_LENGTH);
57428
+ }
57429
+ function formatEntryLine(entry) {
57430
+ const source = entry.source === "user" ? `@${entry.addedBy ?? "unknown"}` : "distilled";
57431
+ return `- [${entry.addedAt}] (${source}) ${entry.text}`;
57432
+ }
57433
+ function todayStamp() {
57434
+ return new Date().toISOString().slice(0, 10);
57435
+ }
57436
+
57437
+ class MemoryStore {
57438
+ root;
57439
+ locks = new Map;
57440
+ constructor(rootDir) {
57441
+ this.root = rootDir ?? process.env.CLAUDE_THREADS_MEMORY_DIR ?? DEFAULT_ROOT;
57442
+ }
57443
+ get rootDir() {
57444
+ return this.root;
57445
+ }
57446
+ channelMemoryPath(platformId) {
57447
+ return join6(this.root, platformSegment(platformId), "channel", "MEMORY.md");
57448
+ }
57449
+ repoMemoryDir(platformId, repoKey) {
57450
+ const dir = join6(this.root, platformSegment(platformId), "repos", repoKey);
57451
+ this.ensureDir(dir);
57452
+ return dir;
57453
+ }
57454
+ listChannelEntries(platformId) {
57455
+ return this.loadLines(platformId).map((l) => l.entry).filter((e) => e !== undefined);
57456
+ }
57457
+ addChannelEntries(platformId, entries) {
57458
+ return this.runExclusive(platformId, () => {
57459
+ const lines = this.loadLines(platformId);
57460
+ const result = { added: [], duplicates: [], superseded: [] };
57461
+ for (const candidate of entries) {
57462
+ const text = sanitizeEntryText(candidate.text);
57463
+ if (!text)
57464
+ continue;
57465
+ const normalized = normalizeForDedupe(text);
57466
+ const existing = lines.map((l) => l.entry).filter((e) => e !== undefined);
57467
+ const isDuplicate = existing.some((e) => {
57468
+ const en = normalizeForDedupe(e.text);
57469
+ if (en === normalized)
57470
+ return true;
57471
+ return candidate.source === "distilled" && en.includes(normalized);
57472
+ });
57473
+ if (isDuplicate) {
57474
+ result.duplicates.push(text);
57475
+ continue;
57476
+ }
57477
+ const canSupersede = (e) => e.source === "distilled" || candidate.source === "user" && e.source === "user" && e.addedBy === candidate.addedBy;
57478
+ for (let i = lines.length - 1;i >= 0; i--) {
57479
+ const e = lines[i].entry;
57480
+ if (e && canSupersede(e) && normalized.includes(normalizeForDedupe(e.text))) {
57481
+ result.superseded.push(e);
57482
+ lines.splice(i, 1);
57483
+ }
57484
+ }
57485
+ const entry = {
57486
+ text,
57487
+ addedAt: todayStamp(),
57488
+ source: candidate.source,
57489
+ addedBy: candidate.source === "user" ? candidate.addedBy : undefined
57490
+ };
57491
+ lines.push({ raw: formatEntryLine(entry), entry });
57492
+ result.added.push(entry);
57493
+ }
57494
+ if (result.added.length > 0) {
57495
+ this.enforceFileCap(lines);
57496
+ this.writeLines(platformId, lines);
57497
+ log17.debug(`Channel memory for ${platformId}: +${result.added.length} entries` + (result.duplicates.length ? ` (${result.duplicates.length} duplicates skipped)` : ""));
57498
+ }
57499
+ return result;
57500
+ });
57501
+ }
57502
+ forgetChannelEntry(platformId, selector) {
57503
+ return this.runExclusive(platformId, () => {
57504
+ const lines = this.loadLines(platformId);
57505
+ const entryLines = [];
57506
+ lines.forEach((l, i) => {
57507
+ if (l.entry)
57508
+ entryLines.push({ lineIndex: i, entry: l.entry });
57509
+ });
57510
+ if (entryLines.length === 0) {
57511
+ return { ok: false, reason: "empty", matches: [] };
57512
+ }
57513
+ let target;
57514
+ if (typeof selector === "number") {
57515
+ if (!Number.isInteger(selector) || selector < 1 || selector > entryLines.length) {
57516
+ return { ok: false, reason: "not-found", matches: [] };
57517
+ }
57518
+ target = entryLines[selector - 1];
57519
+ } else {
57520
+ const needle = selector.toLowerCase().trim();
57521
+ const matches = entryLines.filter((el) => el.entry.text.toLowerCase().includes(needle));
57522
+ if (matches.length === 0) {
57523
+ return { ok: false, reason: "not-found", matches: [] };
57524
+ }
57525
+ if (matches.length > 1) {
57526
+ return {
57527
+ ok: false,
57528
+ reason: "ambiguous",
57529
+ matches: matches.map((el) => el.entry)
57530
+ };
57531
+ }
57532
+ target = matches[0];
57533
+ }
57534
+ lines.splice(target.lineIndex, 1);
57535
+ this.writeLines(platformId, lines);
57536
+ log17.debug(`Channel memory for ${platformId}: removed one entry`);
57537
+ return { ok: true, removed: target.entry };
57538
+ });
57539
+ }
57540
+ clearChannel(platformId) {
57541
+ return this.runExclusive(platformId, () => {
57542
+ this.writeLines(platformId, []);
57543
+ log17.debug(`Channel memory for ${platformId}: cleared`);
57544
+ });
57545
+ }
57546
+ buildChannelMemoryBlock(platformId) {
57547
+ let lines;
57548
+ try {
57549
+ lines = this.loadLines(platformId);
57550
+ } catch (err) {
57551
+ log17.warn(`Failed to read channel memory for ${platformId}: ${err.message}`);
57552
+ return null;
57553
+ }
57554
+ if (lines.length === 0)
57555
+ return null;
57556
+ let truncated = false;
57557
+ const overCap = (ls) => {
57558
+ if (ls.length > CHANNEL_BLOCK_MAX_LINES)
57559
+ return true;
57560
+ const bytes = Buffer.byteLength(ls.map((l) => l.raw).join(`
57561
+ `), "utf-8");
57562
+ return bytes > CHANNEL_BLOCK_MAX_BYTES;
57563
+ };
57564
+ while (lines.length > 1 && overCap(lines)) {
57565
+ truncated = true;
57566
+ const distilledIdx = lines.findIndex((l) => l.entry?.source === "distilled");
57567
+ lines.splice(distilledIdx >= 0 ? distilledIdx : 0, 1);
57568
+ }
57569
+ const rendered = lines.map((l) => l.raw).join(`
57570
+ `);
57571
+ return truncated ? `${rendered}
57572
+ _(older entries omitted — \`!memory\` shows all)_` : rendered;
57573
+ }
57574
+ runExclusive(platformId, fn) {
57575
+ const tail = this.locks.get(platformId) ?? Promise.resolve();
57576
+ const next = tail.then(fn, fn);
57577
+ this.locks.set(platformId, next.catch(() => {
57578
+ return;
57579
+ }));
57580
+ return next;
57581
+ }
57582
+ loadLines(platformId) {
57583
+ const file2 = this.channelMemoryPath(platformId);
57584
+ if (!existsSync5(file2))
57585
+ return [];
57586
+ const raw = readFileSync4(file2, "utf-8");
57587
+ const lines = [];
57588
+ for (const line of raw.split(`
57589
+ `)) {
57590
+ const trimmed = line.trimEnd();
57591
+ if (!trimmed || trimmed === FILE_HEADER)
57592
+ continue;
57593
+ const m = trimmed.match(ENTRY_RE);
57594
+ if (m) {
57595
+ const source = m[2] === "distilled" ? "distilled" : "user";
57596
+ lines.push({
57597
+ raw: trimmed,
57598
+ entry: {
57599
+ addedAt: m[1],
57600
+ source,
57601
+ addedBy: source === "user" ? m[2].slice(1) : undefined,
57602
+ text: m[3]
57603
+ }
57604
+ });
57605
+ } else {
57606
+ lines.push({ raw: trimmed });
57607
+ }
57608
+ }
57609
+ return lines;
57610
+ }
57611
+ enforceFileCap(lines) {
57612
+ while (lines.length > CHANNEL_FILE_MAX_ENTRIES) {
57613
+ const distilledIdx = lines.findIndex((l) => l.entry?.source === "distilled");
57614
+ lines.splice(distilledIdx >= 0 ? distilledIdx : 0, 1);
57615
+ }
57616
+ }
57617
+ writeLines(platformId, lines) {
57618
+ const file2 = this.channelMemoryPath(platformId);
57619
+ this.ensureDir(dirname7(file2));
57620
+ const content = [FILE_HEADER, ...lines.map((l) => l.raw)].join(`
57621
+ `) + `
57622
+ `;
57623
+ const tempFile = `${file2}.tmp`;
57624
+ writeFileSync2(tempFile, content, { encoding: "utf-8", mode: 384 });
57625
+ renameSync(tempFile, file2);
57626
+ chmodSync(file2, 384);
57627
+ }
57628
+ ensureDir(dir) {
57629
+ if (!existsSync5(dir)) {
57630
+ mkdirSync(dir, { recursive: true, mode: 448 });
57631
+ }
57632
+ }
57633
+ }
57634
+
57635
+ // src/memory/distiller.ts
57636
+ var log18 = createLogger("memory");
57637
+
57293
57638
  // src/session/lifecycle.ts
57294
- var log17 = createLogger("lifecycle");
57295
- var sessionLog3 = createSessionLog(log17);
57639
+ var log19 = createLogger("lifecycle");
57640
+ var sessionLog3 = createSessionLog(log19);
57296
57641
  var CHAT_PLATFORM_PROMPT = generateChatPlatformPrompt();
57297
57642
  // src/update-notifier.ts
57298
57643
  var import_semver2 = __toESM(require_semver2(), 1);
@@ -57301,29 +57646,29 @@ var import_semver2 = __toESM(require_semver2(), 1);
57301
57646
  init_emoji();
57302
57647
 
57303
57648
  // src/persistence/github-emails-store.ts
57304
- import { homedir as homedir4 } from "os";
57305
- import { join as join6 } from "path";
57306
- var log18 = createLogger("gh-emails");
57307
- var DEFAULT_CONFIG_DIR = join6(homedir4(), ".config", "claude-threads");
57308
- var DEFAULT_FILE = join6(DEFAULT_CONFIG_DIR, "github-emails.yaml");
57649
+ import { homedir as homedir5 } from "os";
57650
+ import { join as join7 } from "path";
57651
+ var log20 = createLogger("gh-emails");
57652
+ var DEFAULT_CONFIG_DIR = join7(homedir5(), ".config", "claude-threads");
57653
+ var DEFAULT_FILE = join7(DEFAULT_CONFIG_DIR, "github-emails.yaml");
57309
57654
 
57310
57655
  // src/operations/commands/handler.ts
57311
- var log19 = createLogger("commands");
57312
- var sessionLog4 = createSessionLog(log19);
57656
+ var log21 = createLogger("commands");
57657
+ var sessionLog4 = createSessionLog(log21);
57313
57658
  // src/operations/suggestions/branch.ts
57314
57659
  import { exec as exec2 } from "child_process";
57315
57660
  import { promisify as promisify2 } from "util";
57316
57661
  var execAsync2 = promisify2(exec2);
57317
- var log20 = createLogger("branch");
57662
+ var log22 = createLogger("branch");
57318
57663
 
57319
57664
  // src/operations/worktree/handler.ts
57320
- var log21 = createLogger("worktree");
57321
- var sessionLog5 = createSessionLog(log21);
57665
+ var log23 = createLogger("worktree");
57666
+ var sessionLog5 = createSessionLog(log23);
57322
57667
  // src/operations/events/handler.ts
57323
- var log22 = createLogger("events");
57324
- var sessionLog6 = createSessionLog(log22);
57668
+ var log24 = createLogger("events");
57669
+ var sessionLog6 = createSessionLog(log24);
57325
57670
  // src/operations/monitor/handler.ts
57326
- var log23 = createLogger("monitor");
57671
+ var log25 = createLogger("monitor");
57327
57672
  var DEFAULT_INTERVAL_MS = 60 * 1000;
57328
57673
  // src/utils/websocket.ts
57329
57674
  var WS;
@@ -57405,7 +57750,7 @@ ${code}
57405
57750
 
57406
57751
  // src/platform/mattermost/upload.ts
57407
57752
  import { readFile } from "fs/promises";
57408
- var log24 = createLogger("mm-upload");
57753
+ var log26 = createLogger("mm-upload");
57409
57754
  async function uploadFileMattermost(args) {
57410
57755
  const { url: url2, token, channelId, threadId, filePath, filename, caption } = args;
57411
57756
  const buffer = await readFile(filePath);
@@ -57413,7 +57758,7 @@ async function uploadFileMattermost(args) {
57413
57758
  const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
57414
57759
  const formData = new FormData;
57415
57760
  formData.append("files", new Blob([arrayBuffer]), filename);
57416
- log24.debug(`POST /files (${buffer.length} bytes, ${filename})`);
57761
+ log26.debug(`POST /files (${buffer.length} bytes, ${filename})`);
57417
57762
  const uploadResponse = await fetch(uploadUrl, {
57418
57763
  method: "POST",
57419
57764
  headers: {
@@ -57437,7 +57782,7 @@ async function uploadFileMattermost(args) {
57437
57782
  root_id: threadId,
57438
57783
  file_ids: [fileInfo.id]
57439
57784
  };
57440
- log24.debug(`POST /posts (file_ids=[${fileInfo.id}])`);
57785
+ log26.debug(`POST /posts (file_ids=[${fileInfo.id}])`);
57441
57786
  const postResponse = await fetch(postUrl, {
57442
57787
  method: "POST",
57443
57788
  headers: {
@@ -57638,7 +57983,7 @@ class MattermostMcpPlatformApi {
57638
57983
  await updatePostRaw(this.apiConfig, postId, message);
57639
57984
  }
57640
57985
  async waitForReaction(postId, botUserId, timeoutMs) {
57641
- return new Promise((resolve5) => {
57986
+ return new Promise((resolve6) => {
57642
57987
  const wsUrl = this.config.url.replace(/^http/, "ws") + "/api/v4/websocket";
57643
57988
  mcpLogger.debug(`Connecting to WebSocket: ${wsUrl}`);
57644
57989
  const ws = new WS(wsUrl);
@@ -57653,7 +57998,7 @@ class MattermostMcpPlatformApi {
57653
57998
  mcpLogger.debug(`Reaction wait timed out after ${timeoutMs}ms`);
57654
57999
  resolved = true;
57655
58000
  cleanup();
57656
- resolve5(null);
58001
+ resolve6(null);
57657
58002
  }
57658
58003
  }, timeoutMs);
57659
58004
  ws.onopen = () => {
@@ -57681,7 +58026,7 @@ class MattermostMcpPlatformApi {
57681
58026
  resolved = true;
57682
58027
  clearTimeout(timeout);
57683
58028
  cleanup();
57684
- resolve5({
58029
+ resolve6({
57685
58030
  postId: reaction.post_id,
57686
58031
  userId: reaction.user_id,
57687
58032
  emojiName: reaction.emoji_name
@@ -57696,7 +58041,7 @@ class MattermostMcpPlatformApi {
57696
58041
  if (!resolved) {
57697
58042
  resolved = true;
57698
58043
  clearTimeout(timeout);
57699
- resolve5(null);
58044
+ resolve6(null);
57700
58045
  }
57701
58046
  };
57702
58047
  ws.onclose = () => {
@@ -57704,7 +58049,7 @@ class MattermostMcpPlatformApi {
57704
58049
  if (!resolved) {
57705
58050
  resolved = true;
57706
58051
  clearTimeout(timeout);
57707
- resolve5(null);
58052
+ resolve6(null);
57708
58053
  }
57709
58054
  };
57710
58055
  });
@@ -57931,7 +58276,7 @@ ${code}
57931
58276
 
57932
58277
  // src/platform/slack/upload.ts
57933
58278
  import { readFile as readFile2 } from "fs/promises";
57934
- var log25 = createLogger("slack-upload");
58279
+ var log27 = createLogger("slack-upload");
57935
58280
  var DEFAULT_API_URL = "https://slack.com/api";
57936
58281
  async function uploadFileSlack(args) {
57937
58282
  const { botToken, channelId, threadTs, filePath, filename, caption } = args;
@@ -57939,7 +58284,7 @@ async function uploadFileSlack(args) {
57939
58284
  const buffer = await readFile2(filePath);
57940
58285
  const params = new URLSearchParams({ filename, length: String(buffer.length) });
57941
58286
  const step1Url = `${apiUrl}/files.getUploadURLExternal?${params.toString()}`;
57942
- log25.debug(`GET files.getUploadURLExternal (${buffer.length} bytes, ${filename})`);
58287
+ log27.debug(`GET files.getUploadURLExternal (${buffer.length} bytes, ${filename})`);
57943
58288
  const step1Response = await fetch(step1Url, {
57944
58289
  method: "GET",
57945
58290
  headers: {
@@ -57957,7 +58302,7 @@ async function uploadFileSlack(args) {
57957
58302
  const uploadUrl = step1Data.upload_url;
57958
58303
  const fileId = step1Data.file_id;
57959
58304
  const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
57960
- log25.debug(`POST <upload_url>`);
58305
+ log27.debug(`POST <upload_url>`);
57961
58306
  const step2Response = await fetch(uploadUrl, {
57962
58307
  method: "POST",
57963
58308
  headers: {
@@ -57977,7 +58322,7 @@ async function uploadFileSlack(args) {
57977
58322
  if (caption !== undefined) {
57978
58323
  step3Body.initial_comment = caption;
57979
58324
  }
57980
- log25.debug(`POST files.completeUploadExternal (file_id=${fileId}, thread_ts=${threadTs})`);
58325
+ log27.debug(`POST files.completeUploadExternal (file_id=${fileId}, thread_ts=${threadTs})`);
57981
58326
  const step3Response = await fetch(`${apiUrl}/files.completeUploadExternal`, {
57982
58327
  method: "POST",
57983
58328
  headers: {
@@ -57995,7 +58340,7 @@ async function uploadFileSlack(args) {
57995
58340
  throw new Error(`Slack completeUploadExternal error: ${step3Data.error || "unknown"}`);
57996
58341
  }
57997
58342
  if (!step3Data.ts) {
57998
- log25.warn(`Slack completeUploadExternal returned no ts; using fileId ${fileId} as postId. ` + `Do not use this id for updatePost/addReaction.`);
58343
+ log27.warn(`Slack completeUploadExternal returned no ts; using fileId ${fileId} as postId. ` + `Do not use this id for updatePost/addReaction.`);
57999
58344
  }
58000
58345
  return { fileId, postId: step3Data.ts ?? fileId };
58001
58346
  }
@@ -58101,7 +58446,7 @@ class SlackMcpPlatformApi {
58101
58446
  });
58102
58447
  }
58103
58448
  async waitForReaction(postId, botUserId, timeoutMs) {
58104
- return new Promise((resolve5) => {
58449
+ return new Promise((resolve6) => {
58105
58450
  let resolved = false;
58106
58451
  let ws = null;
58107
58452
  const cleanup = () => {
@@ -58114,7 +58459,7 @@ class SlackMcpPlatformApi {
58114
58459
  mcpLogger.debug(`Reaction wait timed out after ${timeoutMs}ms`);
58115
58460
  resolved = true;
58116
58461
  cleanup();
58117
- resolve5(null);
58462
+ resolve6(null);
58118
58463
  }
58119
58464
  }, timeoutMs);
58120
58465
  this.getSocketModeUrl().then((wsUrl) => {
@@ -58152,7 +58497,7 @@ class SlackMcpPlatformApi {
58152
58497
  resolved = true;
58153
58498
  clearTimeout(timeout);
58154
58499
  cleanup();
58155
- resolve5({
58500
+ resolve6({
58156
58501
  postId: item.ts,
58157
58502
  userId,
58158
58503
  emojiName
@@ -58168,7 +58513,7 @@ class SlackMcpPlatformApi {
58168
58513
  resolved = true;
58169
58514
  clearTimeout(timeout);
58170
58515
  cleanup();
58171
- resolve5(null);
58516
+ resolve6(null);
58172
58517
  }
58173
58518
  }
58174
58519
  } catch (err) {
@@ -58181,7 +58526,7 @@ class SlackMcpPlatformApi {
58181
58526
  resolved = true;
58182
58527
  clearTimeout(timeout);
58183
58528
  cleanup();
58184
- resolve5(null);
58529
+ resolve6(null);
58185
58530
  }
58186
58531
  };
58187
58532
  ws.onclose = () => {
@@ -58189,7 +58534,7 @@ class SlackMcpPlatformApi {
58189
58534
  if (!resolved) {
58190
58535
  resolved = true;
58191
58536
  clearTimeout(timeout);
58192
- resolve5(null);
58537
+ resolve6(null);
58193
58538
  }
58194
58539
  };
58195
58540
  }).catch((err) => {
@@ -58197,7 +58542,7 @@ class SlackMcpPlatformApi {
58197
58542
  if (!resolved) {
58198
58543
  resolved = true;
58199
58544
  clearTimeout(timeout);
58200
- resolve5(null);
58545
+ resolve6(null);
58201
58546
  }
58202
58547
  });
58203
58548
  });
@@ -58388,11 +58733,11 @@ function createMcpPlatformApi(platformType, config3) {
58388
58733
 
58389
58734
  // src/mcp/path-validator.ts
58390
58735
  import { lstat, realpath, stat } from "fs/promises";
58391
- import { sep as sep2, isAbsolute } from "path";
58736
+ import { sep as sep3, isAbsolute as isAbsolute2 } from "path";
58392
58737
  function isUnderRoot(needle, root) {
58393
58738
  if (needle === root)
58394
58739
  return true;
58395
- const withSep = root.endsWith(sep2) ? root : root + sep2;
58740
+ const withSep = root.endsWith(sep3) ? root : root + sep3;
58396
58741
  return needle.startsWith(withSep);
58397
58742
  }
58398
58743
  var DANGEROUSLY_WIDE_ROOTS = new Set([
@@ -58417,7 +58762,7 @@ async function validateOutboundPath(inputPath, opts) {
58417
58762
  if (typeof inputPath !== "string" || inputPath.length === 0) {
58418
58763
  return { ok: false, reason: "path is required" };
58419
58764
  }
58420
- if (!isAbsolute(inputPath)) {
58765
+ if (!isAbsolute2(inputPath)) {
58421
58766
  return { ok: false, reason: "path must be absolute" };
58422
58767
  }
58423
58768
  if (!Number.isFinite(opts.maxBytes) || opts.maxBytes <= 0) {