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.
- package/CHANGELOG.md +26 -0
- package/README.md +1 -0
- package/dist/index.js +1283 -606
- package/dist/mcp/mcp-server.js +425 -80
- package/docs/CONFIGURATION.md +72 -0
- package/package.json +1 -1
package/dist/mcp/mcp-server.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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
|
-
|
|
55476
|
-
|
|
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
|
-
|
|
55497
|
-
|
|
55498
|
-
|
|
55499
|
-
|
|
55500
|
-
|
|
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
|
-
|
|
56350
|
-
|
|
56351
|
-
|
|
56352
|
-
|
|
56353
|
-
|
|
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
|
|
57295
|
-
var sessionLog3 = createSessionLog(
|
|
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
|
|
57305
|
-
import { join as
|
|
57306
|
-
var
|
|
57307
|
-
var DEFAULT_CONFIG_DIR =
|
|
57308
|
-
var DEFAULT_FILE =
|
|
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
|
|
57312
|
-
var sessionLog4 = createSessionLog(
|
|
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
|
|
57662
|
+
var log22 = createLogger("branch");
|
|
57318
57663
|
|
|
57319
57664
|
// src/operations/worktree/handler.ts
|
|
57320
|
-
var
|
|
57321
|
-
var sessionLog5 = createSessionLog(
|
|
57665
|
+
var log23 = createLogger("worktree");
|
|
57666
|
+
var sessionLog5 = createSessionLog(log23);
|
|
57322
57667
|
// src/operations/events/handler.ts
|
|
57323
|
-
var
|
|
57324
|
-
var sessionLog6 = createSessionLog(
|
|
57668
|
+
var log24 = createLogger("events");
|
|
57669
|
+
var sessionLog6 = createSessionLog(log24);
|
|
57325
57670
|
// src/operations/monitor/handler.ts
|
|
57326
|
-
var
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 (!
|
|
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) {
|