claude-threads 1.16.0 → 1.16.2
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 +20 -0
- package/dist/index.js +84 -23
- package/dist/mcp/mcp-server.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.16.2] - 2026-05-31
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Sturdier retry budget for the Mattermost post-save race.** Under load Mattermost can return a burst of 500s on `POST /posts` (the threads write race: duplicate key on `threads_pkey` / `app.post.save.app_error`) when several posts stream to the same thread. The retry budget was 3 attempts with plain exponential backoff; a heavy burst exhausted it and dropped a post, which surfaced as flaky task-list / sticky / context-prompt behavior. The budget is now 6 attempts with a capped (2s), equal-jittered backoff. The cap keeps the total wait bounded so a long retry chain can't itself stall things, and the jitter decorrelates concurrent posts that would otherwise re-collide on the same row lock every round. (#394)
|
|
14
|
+
- **Two remaining memory leaks after #351.** First, `MessageManager.dispose()` cleared the post tracker but never called `this.events.removeAllListeners()` on the per-session emitter. Each session attaches a handful of listeners (`question:complete`, `task:update`, `approval:complete`, and the rest), and their closures kept session state reachable after the session ended, so the heap grew with every session. `dispose()` now removes the listeners before resetting. Second, React 19 enables user timing when both `console.timeStamp` and `performance.measure` exist (the case on Node.js 25+), so every component re-render calls `performance.measure()` with a structured-clone'd prop-diff detail that Node buffers indefinitely (~50-205 KB per entry, ~2 GB after a long uptime). Nothing in the bot reads those entries, so a guarded `setInterval` clears them every 60 seconds with `.unref()` so it never blocks a clean exit. (#394)
|
|
15
|
+
|
|
16
|
+
## [1.16.1] - 2026-05-22
|
|
17
|
+
|
|
18
|
+
### Security
|
|
19
|
+
- **Fail-closed authorization on the user-driven paths that invoke Claude.** A confirmed bypass let an unauthorized Slack user (not in `allowedUsers`, non-empty allowlist) reach Claude: the bot posted both the "not authorized" warning and a real answer in the same thread. Rather than chase the one leaking caller, authorization is now deny-by-default at the functions that spawn or message Claude on a user's behalf: `startSession`, `sendFollowUp`, and `resumePausedSession`, plus the resume-from-reaction path. A single helper, `isAuthorizedForSession`, encodes every tier (global allowlist, empty-allowlist allow-all, per-session owner and invited users) and refuses a missing, empty, or `unknown` username. Every user-facing path now routes through that one helper instead of duplicating the check. The biggest gap was message-driven resume, which ran purely from persisted state with no identity check; it now takes a `username` and verifies it against the persisted session allowlist before resuming. The two system-triggered resume paths (bulk restore after a bot restart) stay ungated by design, since they carry no user identity. Legitimate username-less follow-ups (passthrough slash commands like `/context`, already authorized upstream) pass an explicit `system` flag so they are not caught. The mid-session approval flow is untouched: it still adds approved users to the session allowlist, so the check passes once approval is granted. (#388)
|
|
20
|
+
- **hono 4.12.18 → 4.12.21** picks up four upstream security fixes: GHSA-2gcr-mfcq-wcc3 (`app.mount()` stripped the mount prefix from the raw, undecoded pathname, so percent-encoded paths could be cut at the wrong offset and reach a sub-app with the wrong path), GHSA-xrhx-7g5j-rcj5 (`hono/ip-restriction` compared IPs by string equality, so non-canonical IPv6 forms such as compressed or hex IPv4-mapped addresses slipped past static deny rules), GHSA-3hrh-pfw6-9m5x (the cookie helper didn't sanitize `sameSite`/`priority`, allowing Set-Cookie injection via `;`, `\r`, `\n`), and GHSA-f577-qrjj-4474 (`hono/jwt` accepted any two-part Authorization header regardless of scheme, not just `Bearer`). claude-threads reaches hono through `@hono/node-server` on the inbound webhook surface, so the mount-prefix and Set-Cookie fixes are the relevant ones. (#386)
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- **Production deps** bumped: `@hono/node-server` 2.0.2 → 2.0.3 (preserves headers mutated after raw Response construction), `express-rate-limit` 8.5.1 → 8.5.2 (simplified IPv6 key generation). Lockfile-only. (#386)
|
|
24
|
+
- **Dev deps** bumped: `@types/bun` 1.3.13 → 1.3.14, `@types/node` 25.7.0 → 25.9.1, `@types/react` 19.2.14 → 19.2.15, `eslint` 10.3.0 → 10.4.0 (adds `includeIgnoreFile()` and a `for-direction` sequence-expression check, both additive), `lint-staged` 17.0.4 → 17.0.5, `typescript-eslint` 8.59.3 → 8.59.4 (fixes a `no-floating-promises` stack overflow on recursive types). Lockfile-only. (#385)
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **Multiple pasted screenshots no longer silently vanish.** When you paste several clipboard images into one chat message, the platforms hand them all the same filename (`image.png`). The save path wrote each one with the `wx` (`O_EXCL`) flag, so the second and later files hit `EEXIST`, got caught, and were reported as a download failure, so they never reached Claude. Saves now dedupe filenames within a single message, inserting a numeric suffix before the extension (`image.png`, `image_1.png`, `image_2.png`); files without an extension get `report`, `report_1`, and so on. The unique name is resolved before the write, so the `wx` flag still does its job against symlink races. (#387)
|
|
28
|
+
- **Inbound attachments are no longer capped at 100 MB.** A hard 100 MB ceiling on incoming files meant anything larger was skipped with a "too large" warning, which surprised people sharing big assets. The cap is gone; an attachment of any reported size is downloaded and written to disk. One tradeoff worth knowing: `downloadFile` still buffers the whole file in memory via `arrayBuffer()`, so a very large attachment is held in RAM while it's written. Streaming that download is a separate change. (#387)
|
|
29
|
+
|
|
10
30
|
## [1.16.0] - 2026-05-14
|
|
11
31
|
|
|
12
32
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -50355,6 +50355,24 @@ function sanitizeFilename(name) {
|
|
|
50355
50355
|
}
|
|
50356
50356
|
return cleaned;
|
|
50357
50357
|
}
|
|
50358
|
+
function dedupeFilename(name, used) {
|
|
50359
|
+
if (!used.has(name)) {
|
|
50360
|
+
used.add(name);
|
|
50361
|
+
return name;
|
|
50362
|
+
}
|
|
50363
|
+
const lastDot = name.lastIndexOf(".");
|
|
50364
|
+
const hasExt = lastDot > 0;
|
|
50365
|
+
const stem = hasExt ? name.slice(0, lastDot) : name;
|
|
50366
|
+
const ext = hasExt ? name.slice(lastDot) : "";
|
|
50367
|
+
let counter = 1;
|
|
50368
|
+
let candidate = `${stem}_${counter}${ext}`;
|
|
50369
|
+
while (used.has(candidate)) {
|
|
50370
|
+
counter += 1;
|
|
50371
|
+
candidate = `${stem}_${counter}${ext}`;
|
|
50372
|
+
}
|
|
50373
|
+
used.add(candidate);
|
|
50374
|
+
return candidate;
|
|
50375
|
+
}
|
|
50358
50376
|
function formatBytes(bytes) {
|
|
50359
50377
|
if (bytes < 1024)
|
|
50360
50378
|
return `${bytes} B`;
|
|
@@ -50589,8 +50607,9 @@ class MattermostClient extends BasePlatformClient {
|
|
|
50589
50607
|
this.emit("channel_post", normalizedPost, user);
|
|
50590
50608
|
}
|
|
50591
50609
|
}
|
|
50592
|
-
MAX_RETRIES =
|
|
50610
|
+
MAX_RETRIES = 6;
|
|
50593
50611
|
RETRY_DELAY_MS = 500;
|
|
50612
|
+
RETRY_DELAY_CAP_MS = 2000;
|
|
50594
50613
|
async api(method, path, body, retryCount = 0, options) {
|
|
50595
50614
|
const url = `${this.url}/api/v4${path}`;
|
|
50596
50615
|
log3.debug(`API ${method} ${path}`);
|
|
@@ -50605,7 +50624,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
50605
50624
|
if (!response.ok) {
|
|
50606
50625
|
const text = await response.text();
|
|
50607
50626
|
if (response.status === 500 && retryCount < this.MAX_RETRIES) {
|
|
50608
|
-
const delay = this.
|
|
50627
|
+
const delay = this.retryDelayMs(retryCount);
|
|
50609
50628
|
log3.warn(`API ${method} ${path} failed with 500, retrying in ${delay}ms (attempt ${retryCount + 1}/${this.MAX_RETRIES})`);
|
|
50610
50629
|
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
50611
50630
|
return this.api(method, path, body, retryCount + 1, options);
|
|
@@ -50621,6 +50640,11 @@ class MattermostClient extends BasePlatformClient {
|
|
|
50621
50640
|
log3.debug(`API ${method} ${path} → ${response.status}`);
|
|
50622
50641
|
return response.json();
|
|
50623
50642
|
}
|
|
50643
|
+
retryDelayMs(retryCount) {
|
|
50644
|
+
const ceil = Math.min(this.RETRY_DELAY_MS * Math.pow(2, retryCount), this.RETRY_DELAY_CAP_MS);
|
|
50645
|
+
const half = ceil / 2;
|
|
50646
|
+
return Math.floor(half + Math.random() * half);
|
|
50647
|
+
}
|
|
50624
50648
|
async getBotUser() {
|
|
50625
50649
|
const user = await this.api("GET", "/users/me");
|
|
50626
50650
|
this.botUserId = user.id;
|
|
@@ -53585,6 +53609,18 @@ function getSessionStatus(session) {
|
|
|
53585
53609
|
return "idle";
|
|
53586
53610
|
}
|
|
53587
53611
|
|
|
53612
|
+
// src/session/authorization.ts
|
|
53613
|
+
function isAuthorizedForSession(check) {
|
|
53614
|
+
const { username, platform, sessionAllowedUsers } = check;
|
|
53615
|
+
if (!username || username === "unknown") {
|
|
53616
|
+
return false;
|
|
53617
|
+
}
|
|
53618
|
+
if (platform.isUserAllowed(username)) {
|
|
53619
|
+
return true;
|
|
53620
|
+
}
|
|
53621
|
+
return sessionAllowedUsers?.has(username) ?? false;
|
|
53622
|
+
}
|
|
53623
|
+
|
|
53588
53624
|
// src/claude/cli.ts
|
|
53589
53625
|
init_spawn();
|
|
53590
53626
|
init_logger();
|
|
@@ -54818,7 +54854,7 @@ function createPassthroughHandler(slashCommand) {
|
|
|
54818
54854
|
return { handled: false };
|
|
54819
54855
|
}
|
|
54820
54856
|
if (ctx.isAllowed) {
|
|
54821
|
-
await ctx.sessionManager.sendFollowUp(ctx.threadId, `/${slashCommand}
|
|
54857
|
+
await ctx.sessionManager.sendFollowUp(ctx.threadId, `/${slashCommand}`, undefined, undefined, undefined, { system: true });
|
|
54822
54858
|
}
|
|
54823
54859
|
return { handled: true };
|
|
54824
54860
|
};
|
|
@@ -54866,7 +54902,7 @@ async function handleDynamicSlashCommand(command, args, ctx) {
|
|
|
54866
54902
|
}
|
|
54867
54903
|
if (ctx.isAllowed) {
|
|
54868
54904
|
const fullCommand = args ? `/${command} ${args}` : `/${command}`;
|
|
54869
|
-
await ctx.sessionManager.sendFollowUp(ctx.threadId, fullCommand);
|
|
54905
|
+
await ctx.sessionManager.sendFollowUp(ctx.threadId, fullCommand, undefined, undefined, undefined, { system: true });
|
|
54870
54906
|
}
|
|
54871
54907
|
return { handled: true };
|
|
54872
54908
|
}
|
|
@@ -55387,7 +55423,6 @@ import { lstat, mkdir as mkdir2, mkdtemp, rm as rm2, writeFile as writeFile2 } f
|
|
|
55387
55423
|
import { tmpdir as tmpdir2 } from "os";
|
|
55388
55424
|
import { join as join9 } from "path";
|
|
55389
55425
|
var log18 = createLogger("streaming");
|
|
55390
|
-
var MAX_UPLOAD_SIZE = 100 * 1024 * 1024;
|
|
55391
55426
|
var UPLOAD_ROOT_DIR = "claude-threads-uploads";
|
|
55392
55427
|
function safeIdSegment(id) {
|
|
55393
55428
|
return id.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
@@ -55427,18 +55462,11 @@ async function saveFilesToUploadDir(platform, uploadDir, files, debug = false) {
|
|
|
55427
55462
|
return { saved, skipped };
|
|
55428
55463
|
}
|
|
55429
55464
|
const messageDir = await mkdtemp(join9(uploadDir, `${Date.now().toString(36)}-`));
|
|
55465
|
+
const usedNames = new Set;
|
|
55430
55466
|
for (const file of files) {
|
|
55431
|
-
if (file.size > MAX_UPLOAD_SIZE) {
|
|
55432
|
-
skipped.push({
|
|
55433
|
-
name: file.name,
|
|
55434
|
-
reason: `File too large (${formatBytes(file.size)} > ${formatBytes(MAX_UPLOAD_SIZE)} limit)`,
|
|
55435
|
-
suggestion: "Split the file or share it via an external link"
|
|
55436
|
-
});
|
|
55437
|
-
continue;
|
|
55438
|
-
}
|
|
55439
55467
|
try {
|
|
55440
55468
|
const buffer = await platform.downloadFile(file.id);
|
|
55441
|
-
const safeName = sanitizeFilename(file.name);
|
|
55469
|
+
const safeName = dedupeFilename(sanitizeFilename(file.name), usedNames);
|
|
55442
55470
|
const absolutePath = join9(messageDir, safeName);
|
|
55443
55471
|
await writeFile2(absolutePath, buffer, { mode: 384, flag: "wx" });
|
|
55444
55472
|
saved.push({
|
|
@@ -63774,6 +63802,7 @@ class MessageManager {
|
|
|
63774
63802
|
dispose() {
|
|
63775
63803
|
this.cancelScheduledFlush();
|
|
63776
63804
|
this.postTracker.clearSession(this.sessionId);
|
|
63805
|
+
this.events.removeAllListeners();
|
|
63777
63806
|
this.reset();
|
|
63778
63807
|
}
|
|
63779
63808
|
}
|
|
@@ -66728,6 +66757,10 @@ async function startSession(options, username, displayName, replyToPostId, platf
|
|
|
66728
66757
|
if (!platform) {
|
|
66729
66758
|
throw new Error(`Platform '${platformId}' not found. Call addPlatform() first.`);
|
|
66730
66759
|
}
|
|
66760
|
+
if (!isAuthorizedForSession({ username, platform, sessionAllowedUsers: undefined })) {
|
|
66761
|
+
log30.warn(`auth.denied.startSession: @${username || "unknown"} not authorized to start session in ${threadId.substring(0, 8)}...`);
|
|
66762
|
+
return;
|
|
66763
|
+
}
|
|
66731
66764
|
const activeOrPending = ctx.state.sessions.size + pendingStartsCount;
|
|
66732
66765
|
if (activeOrPending >= ctx.config.maxSessions) {
|
|
66733
66766
|
const formatter2 = platform.getFormatter();
|
|
@@ -67095,9 +67128,15 @@ ${failFormatter.formatItalic("Your previous conversation context is preserved, b
|
|
|
67095
67128
|
await ctx.ops.updateStickyMessage();
|
|
67096
67129
|
}
|
|
67097
67130
|
}
|
|
67098
|
-
async function sendFollowUp(session, message, files, ctx, username, displayName) {
|
|
67131
|
+
async function sendFollowUp(session, message, files, ctx, username, displayName, options) {
|
|
67099
67132
|
if (!session.claude.isRunning())
|
|
67100
67133
|
return;
|
|
67134
|
+
if (!options?.system) {
|
|
67135
|
+
if (!isAuthorizedForSession({ username, platform: session.platform, sessionAllowedUsers: session.sessionAllowedUsers })) {
|
|
67136
|
+
sessionLog6(session).warn(`auth.denied.sendFollowUp: @${username || "unknown"} not authorized`);
|
|
67137
|
+
return;
|
|
67138
|
+
}
|
|
67139
|
+
}
|
|
67101
67140
|
if (session.needsContextPromptOnNextMessage) {
|
|
67102
67141
|
session.needsContextPromptOnNextMessage = false;
|
|
67103
67142
|
await session.messageManager?.prepareForUserMessage();
|
|
@@ -67120,7 +67159,7 @@ async function sendFollowUp(session, message, files, ctx, username, displayName)
|
|
|
67120
67159
|
session.messageCount++;
|
|
67121
67160
|
await session.messageManager.handleUserMessage(messageToSend, files, username, displayName);
|
|
67122
67161
|
}
|
|
67123
|
-
async function resumePausedSession(threadId, message, files, ctx) {
|
|
67162
|
+
async function resumePausedSession(threadId, message, files, ctx, username) {
|
|
67124
67163
|
const persisted = ctx.state.sessionStore.load();
|
|
67125
67164
|
const state = findPersistedByThreadId(persisted, threadId);
|
|
67126
67165
|
if (!state) {
|
|
@@ -67128,6 +67167,16 @@ async function resumePausedSession(threadId, message, files, ctx) {
|
|
|
67128
67167
|
return;
|
|
67129
67168
|
}
|
|
67130
67169
|
const shortId = threadId.substring(0, 8);
|
|
67170
|
+
const platform = ctx.state.platforms.get(state.platformId);
|
|
67171
|
+
if (!platform) {
|
|
67172
|
+
log30.warn(`auth.denied.resume: platform '${state.platformId}' not found for ${shortId}...`);
|
|
67173
|
+
return;
|
|
67174
|
+
}
|
|
67175
|
+
const sessionAllowedUsers = new Set(state.sessionAllowedUsers || [state.startedBy].filter(Boolean));
|
|
67176
|
+
if (!isAuthorizedForSession({ username, platform, sessionAllowedUsers })) {
|
|
67177
|
+
log30.warn(`auth.denied.resume: @${username || "unknown"} not authorized to resume ${shortId}...`);
|
|
67178
|
+
return;
|
|
67179
|
+
}
|
|
67131
67180
|
log30.info(`\uD83D\uDD04 Resuming paused session ${shortId}... for new message`);
|
|
67132
67181
|
await resumeSession(state, ctx);
|
|
67133
67182
|
const session = ctx.ops.findSessionByThreadId(threadId);
|
|
@@ -67645,9 +67694,9 @@ async function tryResumeFromReaction(deps, platformId, postId, username) {
|
|
|
67645
67694
|
const sessionId = `${platformId}:${persistedSession.threadId}`;
|
|
67646
67695
|
if (deps.registry.hasById(sessionId))
|
|
67647
67696
|
return false;
|
|
67648
|
-
const allowedUsers = new Set(persistedSession.sessionAllowedUsers);
|
|
67649
67697
|
const platform = deps.platforms.get(platformId);
|
|
67650
|
-
|
|
67698
|
+
const sessionAllowedUsers = new Set(persistedSession.sessionAllowedUsers || [persistedSession.startedBy].filter(Boolean));
|
|
67699
|
+
if (!platform || !isAuthorizedForSession({ username, platform, sessionAllowedUsers })) {
|
|
67651
67700
|
if (platform) {
|
|
67652
67701
|
await platform.createPost(`⚠️ @${username} is not authorized to resume this session`, persistedSession.threadId);
|
|
67653
67702
|
}
|
|
@@ -68233,11 +68282,11 @@ class SessionManager extends EventEmitter4 {
|
|
|
68233
68282
|
}
|
|
68234
68283
|
return;
|
|
68235
68284
|
}
|
|
68236
|
-
async sendFollowUp(threadId, message, files, username, displayName) {
|
|
68285
|
+
async sendFollowUp(threadId, message, files, username, displayName, options) {
|
|
68237
68286
|
const session = this.findSessionByThreadId(threadId);
|
|
68238
68287
|
if (!session || !session.claude.isRunning())
|
|
68239
68288
|
return;
|
|
68240
|
-
await sendFollowUp(session, message, files, this.getContext(), username, displayName);
|
|
68289
|
+
await sendFollowUp(session, message, files, this.getContext(), username, displayName, options);
|
|
68241
68290
|
}
|
|
68242
68291
|
isSessionActive() {
|
|
68243
68292
|
return this.registry.size > 0;
|
|
@@ -68251,8 +68300,8 @@ class SessionManager extends EventEmitter4 {
|
|
|
68251
68300
|
return false;
|
|
68252
68301
|
return this.registry.getPersistedByThreadId(threadId) !== undefined;
|
|
68253
68302
|
}
|
|
68254
|
-
async resumePausedSession(threadId, message, files) {
|
|
68255
|
-
await resumePausedSession(threadId, message, files, this.getContext());
|
|
68303
|
+
async resumePausedSession(threadId, message, files, username) {
|
|
68304
|
+
await resumePausedSession(threadId, message, files, this.getContext(), username);
|
|
68256
68305
|
}
|
|
68257
68306
|
getPersistedSession(threadId) {
|
|
68258
68307
|
return this.registry.getPersistedByThreadId(threadId);
|
|
@@ -68691,6 +68740,17 @@ Mention me to start a session in this worktree.`, threadId);
|
|
|
68691
68740
|
this.registry.clear();
|
|
68692
68741
|
}
|
|
68693
68742
|
}
|
|
68743
|
+
// src/utils/perf-cleanup.ts
|
|
68744
|
+
function startReactMeasureCleanup(intervalMs = 60000) {
|
|
68745
|
+
if (typeof performance === "undefined" || typeof performance.clearMeasures !== "function") {
|
|
68746
|
+
return null;
|
|
68747
|
+
}
|
|
68748
|
+
const timer = setInterval(() => {
|
|
68749
|
+
performance.clearMeasures();
|
|
68750
|
+
}, intervalMs);
|
|
68751
|
+
timer.unref?.();
|
|
68752
|
+
return timer;
|
|
68753
|
+
}
|
|
68694
68754
|
// src/ui/providers/ink-provider.ts
|
|
68695
68755
|
var import_react62 = __toESM(require_react(), 1);
|
|
68696
68756
|
|
|
@@ -78305,7 +78365,7 @@ async function handleMessage(client, session, post2, user, options) {
|
|
|
78305
78365
|
}
|
|
78306
78366
|
const files2 = post2.metadata?.files;
|
|
78307
78367
|
if (content || files2?.length) {
|
|
78308
|
-
await session.resumePausedSession(threadRoot, content, files2);
|
|
78368
|
+
await session.resumePausedSession(threadRoot, content, files2, username);
|
|
78309
78369
|
}
|
|
78310
78370
|
return;
|
|
78311
78371
|
}
|
|
@@ -79483,6 +79543,7 @@ async function startWithoutDaemon() {
|
|
|
79483
79543
|
}
|
|
79484
79544
|
}
|
|
79485
79545
|
let triggerShutdown = null;
|
|
79546
|
+
startReactMeasureCleanup();
|
|
79486
79547
|
const updateState = loadUpdateState();
|
|
79487
79548
|
const restoredSettings = updateState.justUpdated ? updateState.runtimeSettings : undefined;
|
|
79488
79549
|
if (restoredSettings) {
|
package/dist/mcp/mcp-server.js
CHANGED
|
@@ -51043,7 +51043,6 @@ function formatBytes(bytes) {
|
|
|
51043
51043
|
|
|
51044
51044
|
// src/operations/streaming/handler.ts
|
|
51045
51045
|
var log2 = createLogger("streaming");
|
|
51046
|
-
var MAX_UPLOAD_SIZE = 100 * 1024 * 1024;
|
|
51047
51046
|
async function postSkippedFilesFeedback(platform, threadId, skipped) {
|
|
51048
51047
|
if (skipped.length === 0)
|
|
51049
51048
|
return;
|
|
@@ -51554,6 +51553,7 @@ class MessageManager {
|
|
|
51554
51553
|
dispose() {
|
|
51555
51554
|
this.cancelScheduledFlush();
|
|
51556
51555
|
this.postTracker.clearSession(this.sessionId);
|
|
51556
|
+
this.events.removeAllListeners();
|
|
51557
51557
|
this.reset();
|
|
51558
51558
|
}
|
|
51559
51559
|
}
|
|
@@ -55810,7 +55810,7 @@ function createPassthroughHandler(slashCommand) {
|
|
|
55810
55810
|
return { handled: false };
|
|
55811
55811
|
}
|
|
55812
55812
|
if (ctx.isAllowed) {
|
|
55813
|
-
await ctx.sessionManager.sendFollowUp(ctx.threadId, `/${slashCommand}
|
|
55813
|
+
await ctx.sessionManager.sendFollowUp(ctx.threadId, `/${slashCommand}`, undefined, undefined, undefined, { system: true });
|
|
55814
55814
|
}
|
|
55815
55815
|
return { handled: true };
|
|
55816
55816
|
};
|