claude-threads 1.15.1 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/index.js +238 -48
- package/dist/mcp/mcp-server.js +129 -41
- package/docs/CONFIGURATION.md +21 -0
- package/package.json +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.16.0] - 2026-05-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Per-platform channel-verbosity controls.** New `sessionHeader` and `stickyMessage` settings, both `full` (default) / `minimal` (one-line status bar) / `hidden` (no post). Reachable three ways: setup wizard ("How verbose should the bot be in this channel?"), CLI flags (`--session-header`, `--sticky-message`, applied to every platform), or per-platform YAML for split values. `hidden` for the header means Claude's reply is the first message in the thread; `hidden` for the sticky stops the `channel_post` bump entirely. Update notices still ride along in `minimal`. Session-header mode persists per session — resume preserves the user's choice. Old `sessions.json` defaults to `full`. The pre-existing top-level `stickyMessage: { description, footer }` block is unchanged. (#384, closes #383)
|
|
14
|
+
|
|
15
|
+
### Security
|
|
16
|
+
- **`ws` 8.20.0 → 8.20.1** picks up a fix for an uninitialized memory disclosure in `websocket.close()`. Triggered when a `TypedArray` (e.g. `Float32Array`) is passed as the `reason` argument instead of a string or `Buffer` — uninitialized memory was leaked to the remote peer. claude-threads doesn't pass typed arrays to `close()` anywhere we control, but the Mattermost client and inbound webhook surface use `ws` as a transitive dependency. (#382)
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **Production deps** bumped: `@hono/node-server` 2.0.1 → 2.0.2 (serve-static fixes), `react` 19.2.5 → 19.2.6 (RSC type hardening), `semver` 7.7.4 → 7.8.0 (new `truncate` function), `ink-scroll-view` 0.3.6 → 0.3.7. Lockfile-only. (#382)
|
|
20
|
+
- **Dev deps** bumped: `@types/node` 25.6.0 → 25.7.0, `lint-staged` 16.4.0 → 17.0.4, `typescript-eslint` 8.59.2 → 8.59.3. lint-staged 17 drops Node 20 support, but it's a dev dep — the bot's runtime floor is unchanged. Lockfile-only. (#381)
|
|
21
|
+
|
|
22
|
+
## [1.15.2] - 2026-05-06
|
|
23
|
+
|
|
24
|
+
### Security
|
|
25
|
+
- **hono 4.12.15 → 4.12.17** picks up two upstream security fixes — GHSA-69xw-7hcm-h432 (unvalidated JSX tag names in `hono/jsx` could allow HTML injection) and GHSA-9vqf-7f2p-gf9v (`bodyLimit()` could be bypassed for chunked / unknown-length requests). claude-threads doesn't render JSX from untrusted input today, but the bodyLimit fix is load-bearing for the inbound webhook surface. (#377)
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- **Production deps** bumped: `@hono/node-server` 2.0.0 → 2.0.1 (forwards Hono response headers during WebSocket upgrade), `express-rate-limit` 8.4.1 → 8.5.0 (async store init), `zod` 4.3.6 → 4.4.3 (correctness fixes for `preprocess`, `catch`, and discriminated unions on absent object keys). Lockfile-only. (#377)
|
|
29
|
+
- **Dev deps** bumped: `eslint` 10.2.1 → 10.3.0, `typescript-eslint` 8.59.1 → 8.59.2. Lockfile-only. (#376)
|
|
30
|
+
|
|
10
31
|
## [1.15.1] - 2026-05-05
|
|
11
32
|
|
|
12
33
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -48344,6 +48344,18 @@ var jsYaml = {
|
|
|
48344
48344
|
};
|
|
48345
48345
|
|
|
48346
48346
|
// src/config/types.ts
|
|
48347
|
+
var OVERHEAD_VISIBILITY_VALUES = ["full", "minimal", "hidden"];
|
|
48348
|
+
var DEFAULT_OVERHEAD_VISIBILITY = "full";
|
|
48349
|
+
function isOverheadVisibility(value) {
|
|
48350
|
+
return typeof value === "string" && OVERHEAD_VISIBILITY_VALUES.includes(value);
|
|
48351
|
+
}
|
|
48352
|
+
function resolveOverheadVisibility(value, fieldPath) {
|
|
48353
|
+
if (value === undefined || value === null)
|
|
48354
|
+
return DEFAULT_OVERHEAD_VISIBILITY;
|
|
48355
|
+
if (isOverheadVisibility(value))
|
|
48356
|
+
return value;
|
|
48357
|
+
throw new Error(`Invalid ${fieldPath}: expected one of ${OVERHEAD_VISIBILITY_VALUES.join(", ")}, got ${JSON.stringify(value)}`);
|
|
48358
|
+
}
|
|
48347
48359
|
var LIMITS_DEFAULTS = {
|
|
48348
48360
|
maxSessions: 5,
|
|
48349
48361
|
sessionTimeoutMinutes: 30,
|
|
@@ -48648,6 +48660,26 @@ var PERMISSION_MODE_CHOICES = [
|
|
|
48648
48660
|
function permissionModeChoiceIndex(mode) {
|
|
48649
48661
|
return PERMISSION_MODE_CHOICES.findIndex((c) => c.value === mode);
|
|
48650
48662
|
}
|
|
48663
|
+
var OVERHEAD_VISIBILITY_CHOICES = [
|
|
48664
|
+
{
|
|
48665
|
+
title: "Full (default)",
|
|
48666
|
+
value: "full",
|
|
48667
|
+
description: "Per-thread session header + channel sticky with active-sessions list"
|
|
48668
|
+
},
|
|
48669
|
+
{
|
|
48670
|
+
title: "Minimal",
|
|
48671
|
+
value: "minimal",
|
|
48672
|
+
description: "One-line status bar only — drops the table and sessions list"
|
|
48673
|
+
},
|
|
48674
|
+
{
|
|
48675
|
+
title: "Hidden",
|
|
48676
|
+
value: "hidden",
|
|
48677
|
+
description: "No header post, no sticky — Claude's reply is the first message in the thread"
|
|
48678
|
+
}
|
|
48679
|
+
];
|
|
48680
|
+
function overheadVisibilityChoiceIndex(mode) {
|
|
48681
|
+
return OVERHEAD_VISIBILITY_CHOICES.findIndex((c) => c.value === mode);
|
|
48682
|
+
}
|
|
48651
48683
|
var __dirname2 = dirname2(fileURLToPath(import.meta.url));
|
|
48652
48684
|
var SLACK_MANIFEST_PATH = join2(__dirname2, "..", "docs", "slack-app-manifest.yaml");
|
|
48653
48685
|
var onCancel = () => {
|
|
@@ -49417,6 +49449,10 @@ async function setupMattermostPlatform(id, existing) {
|
|
|
49417
49449
|
permissionMode: existingMattermost.permissionMode,
|
|
49418
49450
|
skipPermissions: existingMattermost.skipPermissions
|
|
49419
49451
|
}) : "auto";
|
|
49452
|
+
const existingHeader = existingMattermost?.sessionHeader;
|
|
49453
|
+
const existingSticky = existingMattermost?.stickyMessage;
|
|
49454
|
+
const hasSplitVerbosity = existingHeader !== undefined && existingSticky !== undefined && existingHeader !== existingSticky;
|
|
49455
|
+
let lastChannelVerbosity = existingHeader ?? existingSticky ?? DEFAULT_OVERHEAD_VISIBILITY;
|
|
49420
49456
|
while (true) {
|
|
49421
49457
|
console.log("");
|
|
49422
49458
|
console.log(dim(" Now enter your Mattermost credentials:"));
|
|
@@ -49521,6 +49557,20 @@ async function setupMattermostPlatform(id, existing) {
|
|
|
49521
49557
|
choices: PERMISSION_MODE_CHOICES,
|
|
49522
49558
|
initial: permissionModeChoiceIndex(lastPermissionMode)
|
|
49523
49559
|
}, { onCancel });
|
|
49560
|
+
let channelVerbosity = lastChannelVerbosity;
|
|
49561
|
+
if (hasSplitVerbosity) {
|
|
49562
|
+
console.log("");
|
|
49563
|
+
console.log(dim(` Channel verbosity: keeping split values from current config ` + `(sessionHeader=${existingHeader}, stickyMessage=${existingSticky}). ` + `Edit YAML directly to change.`));
|
|
49564
|
+
} else {
|
|
49565
|
+
const result = await import_prompts.default({
|
|
49566
|
+
type: "select",
|
|
49567
|
+
name: "channelVerbosity",
|
|
49568
|
+
message: "How verbose should the bot be in this channel?",
|
|
49569
|
+
choices: OVERHEAD_VISIBILITY_CHOICES,
|
|
49570
|
+
initial: overheadVisibilityChoiceIndex(lastChannelVerbosity)
|
|
49571
|
+
}, { onCancel });
|
|
49572
|
+
channelVerbosity = result.channelVerbosity;
|
|
49573
|
+
}
|
|
49524
49574
|
lastUrl = basicSettings.url;
|
|
49525
49575
|
lastDisplayName = basicSettings.displayName;
|
|
49526
49576
|
lastToken = finalToken;
|
|
@@ -49528,6 +49578,7 @@ async function setupMattermostPlatform(id, existing) {
|
|
|
49528
49578
|
lastBotName = basicSettings.botName;
|
|
49529
49579
|
lastAllowedUsers = allowedUsers.join(",");
|
|
49530
49580
|
lastPermissionMode = permissionMode;
|
|
49581
|
+
lastChannelVerbosity = channelVerbosity;
|
|
49531
49582
|
console.log("");
|
|
49532
49583
|
console.log(dim(" Validating credentials..."));
|
|
49533
49584
|
const validationResult = await validateMattermostCredentials(basicSettings.url, finalToken, basicSettings.channelId);
|
|
@@ -49586,7 +49637,8 @@ async function setupMattermostPlatform(id, existing) {
|
|
|
49586
49637
|
channelId: basicSettings.channelId,
|
|
49587
49638
|
botName: basicSettings.botName,
|
|
49588
49639
|
allowedUsers,
|
|
49589
|
-
permissionMode: lastPermissionMode
|
|
49640
|
+
permissionMode: lastPermissionMode,
|
|
49641
|
+
...hasSplitVerbosity ? { sessionHeader: existingHeader, stickyMessage: existingSticky } : lastChannelVerbosity !== DEFAULT_OVERHEAD_VISIBILITY ? { sessionHeader: lastChannelVerbosity, stickyMessage: lastChannelVerbosity } : {}
|
|
49590
49642
|
};
|
|
49591
49643
|
}
|
|
49592
49644
|
}
|
|
@@ -49703,6 +49755,10 @@ async function setupSlackPlatform(id, existing) {
|
|
|
49703
49755
|
permissionMode: existingSlack.permissionMode,
|
|
49704
49756
|
skipPermissions: existingSlack.skipPermissions
|
|
49705
49757
|
}) : "auto";
|
|
49758
|
+
const existingHeader = existingSlack?.sessionHeader;
|
|
49759
|
+
const existingSticky = existingSlack?.stickyMessage;
|
|
49760
|
+
const hasSplitVerbosity = existingHeader !== undefined && existingSticky !== undefined && existingHeader !== existingSticky;
|
|
49761
|
+
let lastChannelVerbosity = existingHeader ?? existingSticky ?? DEFAULT_OVERHEAD_VISIBILITY;
|
|
49706
49762
|
while (true) {
|
|
49707
49763
|
console.log("");
|
|
49708
49764
|
console.log(dim(" Now enter your Slack credentials:"));
|
|
@@ -49818,6 +49874,20 @@ async function setupSlackPlatform(id, existing) {
|
|
|
49818
49874
|
choices: PERMISSION_MODE_CHOICES,
|
|
49819
49875
|
initial: permissionModeChoiceIndex(lastPermissionMode)
|
|
49820
49876
|
}, { onCancel });
|
|
49877
|
+
let channelVerbosity = lastChannelVerbosity;
|
|
49878
|
+
if (hasSplitVerbosity) {
|
|
49879
|
+
console.log("");
|
|
49880
|
+
console.log(dim(` Channel verbosity: keeping split values from current config ` + `(sessionHeader=${existingHeader}, stickyMessage=${existingSticky}). ` + `Edit YAML directly to change.`));
|
|
49881
|
+
} else {
|
|
49882
|
+
const result = await import_prompts.default({
|
|
49883
|
+
type: "select",
|
|
49884
|
+
name: "channelVerbosity",
|
|
49885
|
+
message: "How verbose should the bot be in this channel?",
|
|
49886
|
+
choices: OVERHEAD_VISIBILITY_CHOICES,
|
|
49887
|
+
initial: overheadVisibilityChoiceIndex(lastChannelVerbosity)
|
|
49888
|
+
}, { onCancel });
|
|
49889
|
+
channelVerbosity = result.channelVerbosity;
|
|
49890
|
+
}
|
|
49821
49891
|
lastDisplayName = basicSettings.displayName;
|
|
49822
49892
|
lastBotToken = finalBotToken;
|
|
49823
49893
|
lastAppToken = finalAppToken;
|
|
@@ -49825,6 +49895,7 @@ async function setupSlackPlatform(id, existing) {
|
|
|
49825
49895
|
lastBotName = basicSettings.botName;
|
|
49826
49896
|
lastAllowedUsers = allowedUsers.join(",");
|
|
49827
49897
|
lastPermissionMode = permissionMode;
|
|
49898
|
+
lastChannelVerbosity = channelVerbosity;
|
|
49828
49899
|
console.log("");
|
|
49829
49900
|
console.log(dim(" Validating credentials..."));
|
|
49830
49901
|
const validationResult = await validateSlackCredentials(finalBotToken, finalAppToken, basicSettings.channelId);
|
|
@@ -49889,7 +49960,8 @@ async function setupSlackPlatform(id, existing) {
|
|
|
49889
49960
|
channelId: basicSettings.channelId,
|
|
49890
49961
|
botName: basicSettings.botName,
|
|
49891
49962
|
allowedUsers,
|
|
49892
|
-
permissionMode: lastPermissionMode
|
|
49963
|
+
permissionMode: lastPermissionMode,
|
|
49964
|
+
...hasSplitVerbosity ? { sessionHeader: existingHeader, stickyMessage: existingSticky } : lastChannelVerbosity !== DEFAULT_OVERHEAD_VISIBILITY ? { sessionHeader: lastChannelVerbosity, stickyMessage: lastChannelVerbosity } : {}
|
|
49893
49965
|
};
|
|
49894
49966
|
}
|
|
49895
49967
|
}
|
|
@@ -63980,6 +64052,13 @@ async function buildStickyMessage(sessions, platformId, config, formatter, getTh
|
|
|
63980
64052
|
const otherPlatformSessions = allSessions.filter((s) => s.platformId !== platformId);
|
|
63981
64053
|
const totalCount = allSessions.length;
|
|
63982
64054
|
const statusBar = await buildStatusBar(totalCount, config, formatter, platformId);
|
|
64055
|
+
if (config.overhead === "minimal") {
|
|
64056
|
+
return [
|
|
64057
|
+
formatter.formatHorizontalRule(),
|
|
64058
|
+
statusBar
|
|
64059
|
+
].join(`
|
|
64060
|
+
`);
|
|
64061
|
+
}
|
|
63983
64062
|
const activeSessionIds = new Set(sessions.keys());
|
|
63984
64063
|
const historySessions = sessionStore ? sessionStore.getHistory(platformId, activeSessionIds).slice(0, 5) : [];
|
|
63985
64064
|
if (totalCount === 0) {
|
|
@@ -64119,7 +64198,35 @@ async function validateLastMessageIds(platform, sessions) {
|
|
|
64119
64198
|
});
|
|
64120
64199
|
await Promise.all(validationPromises);
|
|
64121
64200
|
}
|
|
64201
|
+
var hiddenCleanupDone = new Set;
|
|
64202
|
+
function clearHiddenCleanupTracking(platformId) {
|
|
64203
|
+
hiddenCleanupDone.delete(platformId);
|
|
64204
|
+
}
|
|
64122
64205
|
async function updateStickyMessageImpl(platform, sessions, config) {
|
|
64206
|
+
if (config.overhead === "hidden") {
|
|
64207
|
+
if (!hiddenCleanupDone.has(platform.platformId)) {
|
|
64208
|
+
hiddenCleanupDone.add(platform.platformId);
|
|
64209
|
+
const existing = stickyPostIds.get(platform.platformId);
|
|
64210
|
+
if (existing) {
|
|
64211
|
+
log21.info(`sticky[${platform.platformId}] hidden mode: removing leftover ${formatShortId(existing)}`);
|
|
64212
|
+
try {
|
|
64213
|
+
await platform.unpinPost(existing);
|
|
64214
|
+
} catch {}
|
|
64215
|
+
try {
|
|
64216
|
+
await platform.deletePost(existing);
|
|
64217
|
+
} catch {}
|
|
64218
|
+
stickyPostIds.delete(platform.platformId);
|
|
64219
|
+
if (sessionStore) {
|
|
64220
|
+
sessionStore.removeStickyPostId(platform.platformId);
|
|
64221
|
+
}
|
|
64222
|
+
}
|
|
64223
|
+
try {
|
|
64224
|
+
const botUser = await platform.getBotUser();
|
|
64225
|
+
cleanupOldStickyMessages(platform, botUser.id, false, new Set).catch(() => {});
|
|
64226
|
+
} catch {}
|
|
64227
|
+
}
|
|
64228
|
+
return;
|
|
64229
|
+
}
|
|
64123
64230
|
const platformSessions = [...sessions.values()].filter((s) => s.platformId === platform.platformId);
|
|
64124
64231
|
log21.debug(`updateStickyMessage for ${platform.platformId}, ${platformSessions.length} sessions`);
|
|
64125
64232
|
for (const s of platformSessions) {
|
|
@@ -64199,8 +64306,11 @@ async function updateStickyMessageImpl(platform, sessions, config) {
|
|
|
64199
64306
|
log21.error(`Failed to update sticky message for ${platform.platformId}`, err instanceof Error ? err : undefined);
|
|
64200
64307
|
}
|
|
64201
64308
|
}
|
|
64202
|
-
async function updateAllStickyMessages(platforms, sessions, config) {
|
|
64203
|
-
const updates = [...platforms.values()].map((platform) =>
|
|
64309
|
+
async function updateAllStickyMessages(platforms, sessions, config, overheadByPlatform) {
|
|
64310
|
+
const updates = [...platforms.values()].map((platform) => {
|
|
64311
|
+
const overhead = overheadByPlatform?.get(platform.platformId) ?? config.overhead;
|
|
64312
|
+
return updateStickyMessage(platform, sessions, { ...config, overhead });
|
|
64313
|
+
});
|
|
64204
64314
|
await Promise.all(updates);
|
|
64205
64315
|
}
|
|
64206
64316
|
function markNeedsBump(platformId) {
|
|
@@ -65986,49 +66096,62 @@ async function requestMessageApproval(session, username, message, ctx) {
|
|
|
65986
66096
|
fromUser: username
|
|
65987
66097
|
});
|
|
65988
66098
|
}
|
|
65989
|
-
async function
|
|
65990
|
-
if (!session.sessionStartPostId)
|
|
65991
|
-
return;
|
|
66099
|
+
async function buildSessionHeaderStatusBar(session, ctx) {
|
|
65992
66100
|
const formatter = session.platform.getFormatter();
|
|
65993
|
-
const worktreeContext = session.worktreeInfo ? { path: session.worktreeInfo.worktreePath, branch: session.worktreeInfo.branch } : undefined;
|
|
65994
|
-
const shortDir = shortenPath(session.workingDir, undefined, worktreeContext);
|
|
65995
66101
|
const effectiveMode = effectivePermissionMode({
|
|
65996
66102
|
override: session.permissionModeOverride,
|
|
65997
66103
|
sessionHasInteractiveOverride: session.forceInteractivePermissions,
|
|
65998
66104
|
botWideMode: ctx.config.permissionMode
|
|
65999
66105
|
});
|
|
66000
66106
|
const permMode = permissionModeDisplay(effectiveMode).chip;
|
|
66001
|
-
const
|
|
66002
|
-
|
|
66003
|
-
const versionStr = formatVersionString();
|
|
66004
|
-
statusItems.push(formatter.formatCode(versionStr));
|
|
66107
|
+
const items = [];
|
|
66108
|
+
items.push(formatter.formatCode(formatVersionString()));
|
|
66005
66109
|
if (session.usageStats) {
|
|
66006
66110
|
const stats = session.usageStats;
|
|
66007
|
-
|
|
66111
|
+
items.push(formatter.formatCode(`\uD83E\uDD16 ${stats.modelDisplayName}`));
|
|
66008
66112
|
const contextPercent = Math.round(stats.contextTokens / stats.contextWindowSize * 100);
|
|
66009
|
-
|
|
66010
|
-
|
|
66011
|
-
statusItems.push(formatter.formatCode(`\uD83D\uDCB0 $${stats.totalCostUSD.toFixed(2)}`));
|
|
66113
|
+
items.push(formatter.formatCode(`${formatContextBar(contextPercent)} ${contextPercent}%`));
|
|
66114
|
+
items.push(formatter.formatCode(`\uD83D\uDCB0 $${stats.totalCostUSD.toFixed(2)}`));
|
|
66012
66115
|
}
|
|
66013
|
-
|
|
66116
|
+
items.push(formatter.formatCode(permMode));
|
|
66014
66117
|
if (session.messageManager?.getPendingApproval()?.type === "plan") {
|
|
66015
|
-
|
|
66118
|
+
items.push(formatter.formatCode("\uD83D\uDCCB Plan pending"));
|
|
66016
66119
|
} else if (session.planApproved) {
|
|
66017
|
-
|
|
66120
|
+
items.push(formatter.formatCode("\uD83D\uDD28 Implementing"));
|
|
66018
66121
|
}
|
|
66019
66122
|
if (ctx.config.chromeEnabled) {
|
|
66020
|
-
|
|
66123
|
+
items.push(formatter.formatCode("\uD83C\uDF10 Chrome"));
|
|
66021
66124
|
}
|
|
66022
66125
|
if (keepAlive.isActive()) {
|
|
66023
|
-
|
|
66126
|
+
items.push(formatter.formatCode("\uD83D\uDC93 Keep-alive"));
|
|
66024
66127
|
}
|
|
66025
66128
|
const battery = await formatBatteryStatus();
|
|
66026
66129
|
if (battery) {
|
|
66027
|
-
|
|
66130
|
+
items.push(formatter.formatCode(battery));
|
|
66028
66131
|
}
|
|
66029
|
-
|
|
66030
|
-
|
|
66031
|
-
|
|
66132
|
+
items.push(formatter.formatCode(`⏱️ ${formatUptime(session.startedAt)}`));
|
|
66133
|
+
return items.join(" · ");
|
|
66134
|
+
}
|
|
66135
|
+
async function updateSessionHeader(session, ctx) {
|
|
66136
|
+
if (session.sessionHeaderMode === "hidden")
|
|
66137
|
+
return;
|
|
66138
|
+
if (!session.sessionStartPostId)
|
|
66139
|
+
return;
|
|
66140
|
+
const formatter = session.platform.getFormatter();
|
|
66141
|
+
const statusBar = await buildSessionHeaderStatusBar(session, ctx);
|
|
66142
|
+
const updateInfo = getUpdateInfo();
|
|
66143
|
+
const updateNotice = updateInfo ? `> ⚠️ ${formatter.formatBold("Update available:")} v${updateInfo.current} → v${updateInfo.latest} - Run ${formatter.formatCode("bun install -g claude-threads")}
|
|
66144
|
+
|
|
66145
|
+
` : undefined;
|
|
66146
|
+
if (session.sessionHeaderMode === "minimal") {
|
|
66147
|
+
const msg2 = [updateNotice, statusBar].filter(Boolean).join(`
|
|
66148
|
+
`);
|
|
66149
|
+
await updatePost(session, session.sessionStartPostId, msg2);
|
|
66150
|
+
return;
|
|
66151
|
+
}
|
|
66152
|
+
const worktreeContext = session.worktreeInfo ? { path: session.worktreeInfo.worktreePath, branch: session.worktreeInfo.branch } : undefined;
|
|
66153
|
+
const shortDir = shortenPath(session.workingDir, undefined, worktreeContext);
|
|
66154
|
+
const otherParticipants = [...session.sessionAllowedUsers].filter((u) => u !== session.startedBy).map((u) => formatter.formatUserMention(u)).join(", ");
|
|
66032
66155
|
const items = [];
|
|
66033
66156
|
if (session.sessionTitle) {
|
|
66034
66157
|
items.push(["\uD83D\uDCDD", "Topic", session.sessionTitle]);
|
|
@@ -66074,10 +66197,6 @@ async function updateSessionHeader(session, ctx) {
|
|
|
66074
66197
|
const logPath = getLogFilePath(session.platform.platformId, session.claudeSessionId);
|
|
66075
66198
|
const shortLogPath = logPath.replace(process.env.HOME || "", "~");
|
|
66076
66199
|
items.push(["\uD83D\uDCCB", "Log File", formatter.formatCode(shortLogPath)]);
|
|
66077
|
-
const updateInfo = getUpdateInfo();
|
|
66078
|
-
const updateNotice = updateInfo ? `> ⚠️ ${formatter.formatBold("Update available:")} v${updateInfo.current} → v${updateInfo.latest} - Run ${formatter.formatCode("bun install -g claude-threads")}
|
|
66079
|
-
|
|
66080
|
-
` : undefined;
|
|
66081
66200
|
const msg = [
|
|
66082
66201
|
updateNotice,
|
|
66083
66202
|
statusBar,
|
|
@@ -66085,8 +66204,7 @@ async function updateSessionHeader(session, ctx) {
|
|
|
66085
66204
|
formatter.formatKeyValueList(items)
|
|
66086
66205
|
].filter((item) => item !== null && item !== undefined).join(`
|
|
66087
66206
|
`);
|
|
66088
|
-
|
|
66089
|
-
await updatePost(session, postId, msg);
|
|
66207
|
+
await updatePost(session, session.sessionStartPostId, msg);
|
|
66090
66208
|
}
|
|
66091
66209
|
async function showUpdateStatus(session, updateManager, ctx) {
|
|
66092
66210
|
const formatter = session.platform.getFormatter();
|
|
@@ -66586,6 +66704,17 @@ function maybeInjectMetadataReminder(message, session, ctx, fullSession) {
|
|
|
66586
66704
|
}
|
|
66587
66705
|
return message;
|
|
66588
66706
|
}
|
|
66707
|
+
function resumeSessionHeaderMode(persisted, platformConfigured) {
|
|
66708
|
+
return persisted ?? platformConfigured ?? DEFAULT_OVERHEAD_VISIBILITY;
|
|
66709
|
+
}
|
|
66710
|
+
function resolveSessionHeaderMode(configured, replyToPostId, platformId) {
|
|
66711
|
+
const mode = configured ?? DEFAULT_OVERHEAD_VISIBILITY;
|
|
66712
|
+
if (mode === "hidden" && !replyToPostId) {
|
|
66713
|
+
log30.error(`sessionHeader: hidden requires a replyToPostId for ${platformId}; ` + `downgrading this session to 'minimal' so the header post is still short.`);
|
|
66714
|
+
return "minimal";
|
|
66715
|
+
}
|
|
66716
|
+
return mode;
|
|
66717
|
+
}
|
|
66589
66718
|
async function startSession(options, username, displayName, replyToPostId, platformId, ctx, triggeringPostId, initialOptions) {
|
|
66590
66719
|
const threadId = replyToPostId || "";
|
|
66591
66720
|
const existingSessionId = ctx.ops.getSessionId(platformId, threadId);
|
|
@@ -66611,13 +66740,18 @@ async function startSession(options, username, displayName, replyToPostId, platf
|
|
|
66611
66740
|
return;
|
|
66612
66741
|
}
|
|
66613
66742
|
pendingStartsCount++;
|
|
66743
|
+
const sessionHeaderMode = resolveSessionHeaderMode(ctx.ops.getPlatformOverhead(platformId).sessionHeader, replyToPostId, platformId);
|
|
66614
66744
|
const startFormatter = platform.getFormatter();
|
|
66615
|
-
const
|
|
66616
|
-
|
|
66617
|
-
|
|
66618
|
-
|
|
66745
|
+
const skipHeaderPost = sessionHeaderMode === "hidden";
|
|
66746
|
+
let startPost;
|
|
66747
|
+
if (!skipHeaderPost) {
|
|
66748
|
+
startPost = await withErrorHandling(() => platform.createPost(startFormatter.formatItalic("Claude Threads session starting..."), replyToPostId), { action: "Create session post" });
|
|
66749
|
+
if (!startPost) {
|
|
66750
|
+
releasePendingStart();
|
|
66751
|
+
return;
|
|
66752
|
+
}
|
|
66619
66753
|
}
|
|
66620
|
-
const actualThreadId = replyToPostId || startPost.id;
|
|
66754
|
+
const actualThreadId = replyToPostId || (startPost ? startPost.id : "");
|
|
66621
66755
|
const sessionId = ctx.ops.getSessionId(platformId, actualThreadId);
|
|
66622
66756
|
platform.sendTyping(actualThreadId);
|
|
66623
66757
|
const claudeSessionId = randomUUID4();
|
|
@@ -66631,13 +66765,23 @@ async function startSession(options, username, displayName, replyToPostId, platf
|
|
|
66631
66765
|
const requestedDir = initialOptions.workingDir.startsWith("~") ? initialOptions.workingDir.replace("~", process.env.HOME || "") : initialOptions.workingDir;
|
|
66632
66766
|
const resolvedDir = resolve6(requestedDir);
|
|
66633
66767
|
if (!existsSync12(resolvedDir)) {
|
|
66634
|
-
|
|
66768
|
+
const msg = `❌ Directory does not exist: ${formatter.formatCode(initialOptions.workingDir)}`;
|
|
66769
|
+
if (startPost) {
|
|
66770
|
+
await platform.updatePost(startPost.id, msg);
|
|
66771
|
+
} else {
|
|
66772
|
+
await platform.createPost(msg, replyToPostId);
|
|
66773
|
+
}
|
|
66635
66774
|
releasePendingStart();
|
|
66636
66775
|
return;
|
|
66637
66776
|
}
|
|
66638
66777
|
const { statSync: statSync4 } = await import("fs");
|
|
66639
66778
|
if (!statSync4(resolvedDir).isDirectory()) {
|
|
66640
|
-
|
|
66779
|
+
const msg = `❌ Not a directory: ${formatter.formatCode(initialOptions.workingDir)}`;
|
|
66780
|
+
if (startPost) {
|
|
66781
|
+
await platform.updatePost(startPost.id, msg);
|
|
66782
|
+
} else {
|
|
66783
|
+
await platform.createPost(msg, replyToPostId);
|
|
66784
|
+
}
|
|
66641
66785
|
releasePendingStart();
|
|
66642
66786
|
return;
|
|
66643
66787
|
}
|
|
@@ -66695,7 +66839,8 @@ async function startSession(options, username, displayName, replyToPostId, platf
|
|
|
66695
66839
|
sessionAllowedUsers: new Set([username]),
|
|
66696
66840
|
forceInteractivePermissions,
|
|
66697
66841
|
permissionModeOverride: sessionPermissionModeOverride,
|
|
66698
|
-
sessionStartPostId: startPost.id,
|
|
66842
|
+
sessionStartPostId: startPost ? startPost.id : null,
|
|
66843
|
+
sessionHeaderMode,
|
|
66699
66844
|
timers: createSessionTimers(),
|
|
66700
66845
|
lifecycle: createSessionLifecycle(),
|
|
66701
66846
|
timeoutWarningPosted: false,
|
|
@@ -66714,7 +66859,9 @@ async function startSession(options, username, displayName, replyToPostId, platf
|
|
|
66714
66859
|
});
|
|
66715
66860
|
mutableSessions(ctx).set(sessionId, session);
|
|
66716
66861
|
releasePendingStart();
|
|
66717
|
-
|
|
66862
|
+
if (startPost) {
|
|
66863
|
+
ctx.ops.registerPost(startPost.id, actualThreadId);
|
|
66864
|
+
}
|
|
66718
66865
|
ctx.ops.emitSessionAdd(session);
|
|
66719
66866
|
sessionLog6(session).info(`▶ Session started by @${username}`);
|
|
66720
66867
|
fireMetadataSuggestions(session, options.prompt, ctx);
|
|
@@ -66847,6 +66994,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
|
|
|
66847
66994
|
sessionAllowedUsers: new Set(state.sessionAllowedUsers),
|
|
66848
66995
|
forceInteractivePermissions: state.forceInteractivePermissions ?? false,
|
|
66849
66996
|
sessionStartPostId: state.sessionStartPostId ?? null,
|
|
66997
|
+
sessionHeaderMode: resumeSessionHeaderMode(state.sessionHeaderMode, ctx.ops.getPlatformOverhead(platformId).sessionHeader),
|
|
66850
66998
|
timers: createSessionTimers(),
|
|
66851
66999
|
lifecycle: createResumedLifecycle(state.resumeFailCount ?? 0),
|
|
66852
67000
|
timeoutWarningPosted: false,
|
|
@@ -67581,6 +67729,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
67581
67729
|
isShuttingDown = false;
|
|
67582
67730
|
customDescription;
|
|
67583
67731
|
customFooter;
|
|
67732
|
+
platformOverhead = new Map;
|
|
67584
67733
|
autoUpdateManager = null;
|
|
67585
67734
|
accountPool;
|
|
67586
67735
|
constructor(workingDir, permissionModeOrSkipFlag = "default", chromeEnabled = false, worktreeMode = "prompt", sessionsPath, threadLogsEnabled = true, threadLogsRetentionDays = 30, limits, claudeAccounts) {
|
|
@@ -67612,8 +67761,12 @@ class SessionManager extends EventEmitter4 {
|
|
|
67612
67761
|
cleanupWorktrees: this.limits.cleanupWorktrees
|
|
67613
67762
|
});
|
|
67614
67763
|
}
|
|
67615
|
-
addPlatform(platformId, client) {
|
|
67764
|
+
addPlatform(platformId, client, overhead) {
|
|
67616
67765
|
this.platforms.set(platformId, client);
|
|
67766
|
+
this.platformOverhead.set(platformId, {
|
|
67767
|
+
sessionHeader: overhead?.sessionHeader ?? DEFAULT_OVERHEAD_VISIBILITY,
|
|
67768
|
+
stickyMessage: overhead?.stickyMessage ?? DEFAULT_OVERHEAD_VISIBILITY
|
|
67769
|
+
});
|
|
67617
67770
|
client.on("message", (post2, user) => this.handleMessage(platformId, post2, user));
|
|
67618
67771
|
client.on("reaction", (reaction, user) => {
|
|
67619
67772
|
if (user) {
|
|
@@ -67626,6 +67779,9 @@ class SessionManager extends EventEmitter4 {
|
|
|
67626
67779
|
}
|
|
67627
67780
|
});
|
|
67628
67781
|
client.on("channel_post", () => {
|
|
67782
|
+
if (this.platformOverhead.get(platformId)?.stickyMessage === "hidden") {
|
|
67783
|
+
return;
|
|
67784
|
+
}
|
|
67629
67785
|
markNeedsBump(platformId);
|
|
67630
67786
|
this.updateStickyMessage();
|
|
67631
67787
|
});
|
|
@@ -67633,6 +67789,8 @@ class SessionManager extends EventEmitter4 {
|
|
|
67633
67789
|
}
|
|
67634
67790
|
removePlatform(platformId) {
|
|
67635
67791
|
this.platforms.delete(platformId);
|
|
67792
|
+
this.platformOverhead.delete(platformId);
|
|
67793
|
+
clearHiddenCleanupTracking(platformId);
|
|
67636
67794
|
}
|
|
67637
67795
|
setAutoUpdateManager(manager) {
|
|
67638
67796
|
this.autoUpdateManager = manager;
|
|
@@ -67718,7 +67876,11 @@ class SessionManager extends EventEmitter4 {
|
|
|
67718
67876
|
getClaudeAccount: (id) => this.accountPool.get(id),
|
|
67719
67877
|
releaseClaudeAccount: (id) => this.accountPool.release(id),
|
|
67720
67878
|
markClaudeAccountCooling: (id, untilMs) => this.accountPool.markCooling(id, untilMs),
|
|
67721
|
-
getClaudeAccountPoolStatus: () => this.accountPool.status()
|
|
67879
|
+
getClaudeAccountPoolStatus: () => this.accountPool.status(),
|
|
67880
|
+
getPlatformOverhead: (pid) => this.platformOverhead.get(pid) ?? {
|
|
67881
|
+
sessionHeader: DEFAULT_OVERHEAD_VISIBILITY,
|
|
67882
|
+
stickyMessage: DEFAULT_OVERHEAD_VISIBILITY
|
|
67883
|
+
}
|
|
67722
67884
|
};
|
|
67723
67885
|
return createSessionContext(config, state, ops);
|
|
67724
67886
|
}
|
|
@@ -67878,7 +68040,8 @@ class SessionManager extends EventEmitter4 {
|
|
|
67878
68040
|
pullRequestUrl: session.pullRequestUrl,
|
|
67879
68041
|
messageCount: session.messageCount,
|
|
67880
68042
|
resumeFailCount: session.lifecycle.resumeFailCount,
|
|
67881
|
-
claudeAccountId: session.claudeAccountId
|
|
68043
|
+
claudeAccountId: session.claudeAccountId,
|
|
68044
|
+
sessionHeaderMode: session.sessionHeaderMode
|
|
67882
68045
|
};
|
|
67883
68046
|
this.sessionStore.save(session.sessionId, state);
|
|
67884
68047
|
}
|
|
@@ -67895,6 +68058,10 @@ class SessionManager extends EventEmitter4 {
|
|
|
67895
68058
|
});
|
|
67896
68059
|
}
|
|
67897
68060
|
async updateStickyMessage() {
|
|
68061
|
+
const overheadByPlatform = new Map;
|
|
68062
|
+
for (const [platformId, overhead] of this.platformOverhead) {
|
|
68063
|
+
overheadByPlatform.set(platformId, overhead.stickyMessage);
|
|
68064
|
+
}
|
|
67898
68065
|
await updateAllStickyMessages(this.platforms, this.registry.getSessions(), {
|
|
67899
68066
|
maxSessions: this.limits.maxSessions,
|
|
67900
68067
|
chromeEnabled: this.chromeEnabled,
|
|
@@ -67905,7 +68072,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
67905
68072
|
description: this.customDescription,
|
|
67906
68073
|
footer: this.customFooter,
|
|
67907
68074
|
accountPoolStatus: this.accountPool.isEmpty ? undefined : this.accountPool.status()
|
|
67908
|
-
});
|
|
68075
|
+
}, overheadByPlatform);
|
|
67909
68076
|
}
|
|
67910
68077
|
async updateAllStickyMessages() {
|
|
67911
68078
|
await this.updateStickyMessage();
|
|
@@ -79149,7 +79316,7 @@ function wirePlatformEvents(platformId, client, session, ui) {
|
|
|
79149
79316
|
ui.addLog({ level: "error", component: platformId, message });
|
|
79150
79317
|
});
|
|
79151
79318
|
}
|
|
79152
|
-
program.name("claude-threads").version(VERSION).description("Share Claude Code sessions in Mattermost").option("--url <url>", "Mattermost server URL").option("--token <token>", "Mattermost bot token").option("--channel <id>", "Mattermost channel ID").option("--bot-name <name>", "Bot mention name (default: claude-code)").option("--allowed-users <users>", "Comma-separated allowed usernames").option("--permission-mode <mode>", "Permission mode: default | auto | bypass (default: from config)").option("--skip-permissions", "[deprecated] Alias for --permission-mode bypass").option("--no-skip-permissions", "[deprecated] Alias for --permission-mode default").option("--chrome", "Enable Claude in Chrome integration").option("--no-chrome", "Disable Claude in Chrome integration").option("--worktree-mode <mode>", "Git worktree mode: off, prompt, require (default: prompt)").option("--keep-alive", "Enable system sleep prevention (default: enabled)").option("--no-keep-alive", "Disable system sleep prevention").option("--setup", "Run interactive setup wizard (reconfigure existing settings)").option("--debug", "Enable debug logging").option("--skip-version-check", "Skip Claude CLI version compatibility check").option("--auto-restart", "Enable auto-restart on updates (default when autoUpdate enabled)").option("--no-auto-restart", "Disable auto-restart on updates").option("--headless", "Run without interactive UI (logs to stdout)").parse();
|
|
79319
|
+
program.name("claude-threads").version(VERSION).description("Share Claude Code sessions in Mattermost").option("--url <url>", "Mattermost server URL").option("--token <token>", "Mattermost bot token").option("--channel <id>", "Mattermost channel ID").option("--bot-name <name>", "Bot mention name (default: claude-code)").option("--allowed-users <users>", "Comma-separated allowed usernames").option("--permission-mode <mode>", "Permission mode: default | auto | bypass (default: from config)").option("--skip-permissions", "[deprecated] Alias for --permission-mode bypass").option("--no-skip-permissions", "[deprecated] Alias for --permission-mode default").option("--chrome", "Enable Claude in Chrome integration").option("--no-chrome", "Disable Claude in Chrome integration").option("--worktree-mode <mode>", "Git worktree mode: off, prompt, require (default: prompt)").option("--session-header <mode>", "Per-thread session header: full | minimal | hidden. Overrides per-platform config.").option("--sticky-message <mode>", "Channel sticky message: full | minimal | hidden. Overrides per-platform config.").option("--keep-alive", "Enable system sleep prevention (default: enabled)").option("--no-keep-alive", "Disable system sleep prevention").option("--setup", "Run interactive setup wizard (reconfigure existing settings)").option("--debug", "Enable debug logging").option("--skip-version-check", "Skip Claude CLI version compatibility check").option("--auto-restart", "Enable auto-restart on updates (default when autoUpdate enabled)").option("--no-auto-restart", "Disable auto-restart on updates").option("--headless", "Run without interactive UI (logs to stdout)").parse();
|
|
79153
79320
|
var opts = program.opts();
|
|
79154
79321
|
var forcedInteractive = !!process.env.CLAUDE_THREADS_INTERACTIVE;
|
|
79155
79322
|
var isHeadless = opts.headless || !forcedInteractive && (!process.stdout.isTTY || !process.stdin.isTTY);
|
|
@@ -79237,6 +79404,14 @@ async function startWithoutDaemon() {
|
|
|
79237
79404
|
console.error(red(` ❌ Invalid --permission-mode: "${opts.permissionMode}". Must be one of: default, auto, bypass.`));
|
|
79238
79405
|
process.exit(1);
|
|
79239
79406
|
}
|
|
79407
|
+
if (opts.sessionHeader !== undefined && !isOverheadVisibility(opts.sessionHeader)) {
|
|
79408
|
+
console.error(red(` ❌ Invalid --session-header: "${opts.sessionHeader}". Must be one of: ${OVERHEAD_VISIBILITY_VALUES.join(", ")}.`));
|
|
79409
|
+
process.exit(1);
|
|
79410
|
+
}
|
|
79411
|
+
if (opts.stickyMessage !== undefined && !isOverheadVisibility(opts.stickyMessage)) {
|
|
79412
|
+
console.error(red(` ❌ Invalid --sticky-message: "${opts.stickyMessage}". Must be one of: ${OVERHEAD_VISIBILITY_VALUES.join(", ")}.`));
|
|
79413
|
+
process.exit(1);
|
|
79414
|
+
}
|
|
79240
79415
|
const cliArgs = {
|
|
79241
79416
|
url: opts.url,
|
|
79242
79417
|
token: opts.token,
|
|
@@ -79247,7 +79422,9 @@ async function startWithoutDaemon() {
|
|
|
79247
79422
|
permissionMode: opts.permissionMode,
|
|
79248
79423
|
chrome: opts.chrome,
|
|
79249
79424
|
worktreeMode: opts.worktreeMode,
|
|
79250
|
-
keepAlive: opts.keepAlive
|
|
79425
|
+
keepAlive: opts.keepAlive,
|
|
79426
|
+
sessionHeader: opts.sessionHeader,
|
|
79427
|
+
stickyMessage: opts.stickyMessage
|
|
79251
79428
|
};
|
|
79252
79429
|
if (opts.setup) {
|
|
79253
79430
|
await runOnboarding(true);
|
|
@@ -79268,6 +79445,16 @@ async function startWithoutDaemon() {
|
|
|
79268
79445
|
if (cliArgs.keepAlive !== undefined) {
|
|
79269
79446
|
newConfig.keepAlive = cliArgs.keepAlive;
|
|
79270
79447
|
}
|
|
79448
|
+
if (cliArgs.sessionHeader !== undefined) {
|
|
79449
|
+
for (const p of newConfig.platforms) {
|
|
79450
|
+
p.sessionHeader = cliArgs.sessionHeader;
|
|
79451
|
+
}
|
|
79452
|
+
}
|
|
79453
|
+
if (cliArgs.stickyMessage !== undefined) {
|
|
79454
|
+
for (const p of newConfig.platforms) {
|
|
79455
|
+
p.stickyMessage = cliArgs.stickyMessage;
|
|
79456
|
+
}
|
|
79457
|
+
}
|
|
79271
79458
|
const keepAliveEnabled = newConfig.keepAlive !== false;
|
|
79272
79459
|
if (!newConfig.platforms || newConfig.platforms.length === 0) {
|
|
79273
79460
|
throw new Error("No platforms configured. Run with --setup to configure.");
|
|
@@ -79441,7 +79628,10 @@ async function startWithoutDaemon() {
|
|
|
79441
79628
|
});
|
|
79442
79629
|
const client = createPlatformClient(platformConfig);
|
|
79443
79630
|
platforms.set(platformConfig.id, client);
|
|
79444
|
-
session.addPlatform(platformConfig.id, client
|
|
79631
|
+
session.addPlatform(platformConfig.id, client, {
|
|
79632
|
+
sessionHeader: resolveOverheadVisibility(platformConfig.sessionHeader, `platforms[${platformConfig.id}].sessionHeader`),
|
|
79633
|
+
stickyMessage: resolveOverheadVisibility(platformConfig.stickyMessage, `platforms[${platformConfig.id}].stickyMessage`)
|
|
79634
|
+
});
|
|
79445
79635
|
wirePlatformEvents(platformConfig.id, client, session, ui);
|
|
79446
79636
|
}
|
|
79447
79637
|
const enabledPlatforms = Array.from(platforms.entries()).filter(([id]) => platformEnabledState.get(id) ?? true);
|
package/dist/mcp/mcp-server.js
CHANGED
|
@@ -3035,6 +3035,9 @@ var require_data = __commonJS((exports, module) => {
|
|
|
3035
3035
|
var require_utils = __commonJS((exports, module) => {
|
|
3036
3036
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3037
3037
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
3038
|
+
var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
|
|
3039
|
+
var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
|
|
3040
|
+
var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
|
|
3038
3041
|
function stringArrayToHexStripped(input) {
|
|
3039
3042
|
let acc = "";
|
|
3040
3043
|
let code = 0;
|
|
@@ -3228,27 +3231,77 @@ var require_utils = __commonJS((exports, module) => {
|
|
|
3228
3231
|
}
|
|
3229
3232
|
return output.join("");
|
|
3230
3233
|
}
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3234
|
+
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3235
|
+
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3236
|
+
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
3237
|
+
function reescapeHostDelimiters(host, isIP) {
|
|
3238
|
+
const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
3239
|
+
re.lastIndex = 0;
|
|
3240
|
+
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
3241
|
+
}
|
|
3242
|
+
function normalizePercentEncoding(input, decodeUnreserved = false) {
|
|
3243
|
+
if (input.indexOf("%") === -1) {
|
|
3244
|
+
return input;
|
|
3241
3245
|
}
|
|
3242
|
-
|
|
3243
|
-
|
|
3246
|
+
let output = "";
|
|
3247
|
+
for (let i = 0;i < input.length; i++) {
|
|
3248
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3249
|
+
const hex = input.slice(i + 1, i + 3);
|
|
3250
|
+
if (isHexPair(hex)) {
|
|
3251
|
+
const normalizedHex = hex.toUpperCase();
|
|
3252
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3253
|
+
if (decodeUnreserved && isUnreserved(decoded)) {
|
|
3254
|
+
output += decoded;
|
|
3255
|
+
} else {
|
|
3256
|
+
output += "%" + normalizedHex;
|
|
3257
|
+
}
|
|
3258
|
+
i += 2;
|
|
3259
|
+
continue;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
output += input[i];
|
|
3244
3263
|
}
|
|
3245
|
-
|
|
3246
|
-
|
|
3264
|
+
return output;
|
|
3265
|
+
}
|
|
3266
|
+
function normalizePathEncoding(input) {
|
|
3267
|
+
let output = "";
|
|
3268
|
+
for (let i = 0;i < input.length; i++) {
|
|
3269
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3270
|
+
const hex = input.slice(i + 1, i + 3);
|
|
3271
|
+
if (isHexPair(hex)) {
|
|
3272
|
+
const normalizedHex = hex.toUpperCase();
|
|
3273
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3274
|
+
if (decoded !== "." && isUnreserved(decoded)) {
|
|
3275
|
+
output += decoded;
|
|
3276
|
+
} else {
|
|
3277
|
+
output += "%" + normalizedHex;
|
|
3278
|
+
}
|
|
3279
|
+
i += 2;
|
|
3280
|
+
continue;
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
if (isPathCharacter(input[i])) {
|
|
3284
|
+
output += input[i];
|
|
3285
|
+
} else {
|
|
3286
|
+
output += escape(input[i]);
|
|
3287
|
+
}
|
|
3247
3288
|
}
|
|
3248
|
-
|
|
3249
|
-
|
|
3289
|
+
return output;
|
|
3290
|
+
}
|
|
3291
|
+
function escapePreservingEscapes(input) {
|
|
3292
|
+
let output = "";
|
|
3293
|
+
for (let i = 0;i < input.length; i++) {
|
|
3294
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3295
|
+
const hex = input.slice(i + 1, i + 3);
|
|
3296
|
+
if (isHexPair(hex)) {
|
|
3297
|
+
output += "%" + hex.toUpperCase();
|
|
3298
|
+
i += 2;
|
|
3299
|
+
continue;
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
output += escape(input[i]);
|
|
3250
3303
|
}
|
|
3251
|
-
return
|
|
3304
|
+
return output;
|
|
3252
3305
|
}
|
|
3253
3306
|
function recomposeAuthority(component) {
|
|
3254
3307
|
const uriTokens = [];
|
|
@@ -3263,7 +3316,7 @@ var require_utils = __commonJS((exports, module) => {
|
|
|
3263
3316
|
if (ipV6res.isIPV6 === true) {
|
|
3264
3317
|
host = `[${ipV6res.escapedHost}]`;
|
|
3265
3318
|
} else {
|
|
3266
|
-
host =
|
|
3319
|
+
host = reescapeHostDelimiters(host, false);
|
|
3267
3320
|
}
|
|
3268
3321
|
}
|
|
3269
3322
|
uriTokens.push(host);
|
|
@@ -3277,7 +3330,10 @@ var require_utils = __commonJS((exports, module) => {
|
|
|
3277
3330
|
module.exports = {
|
|
3278
3331
|
nonSimpleDomain,
|
|
3279
3332
|
recomposeAuthority,
|
|
3280
|
-
|
|
3333
|
+
reescapeHostDelimiters,
|
|
3334
|
+
normalizePercentEncoding,
|
|
3335
|
+
normalizePathEncoding,
|
|
3336
|
+
escapePreservingEscapes,
|
|
3281
3337
|
removeDotSegments,
|
|
3282
3338
|
isIPv4,
|
|
3283
3339
|
isUUID,
|
|
@@ -3462,11 +3518,11 @@ var require_schemes = __commonJS((exports, module) => {
|
|
|
3462
3518
|
|
|
3463
3519
|
// node_modules/fast-uri/index.js
|
|
3464
3520
|
var require_fast_uri = __commonJS((exports, module) => {
|
|
3465
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority,
|
|
3521
|
+
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3466
3522
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3467
3523
|
function normalize(uri, options) {
|
|
3468
3524
|
if (typeof uri === "string") {
|
|
3469
|
-
uri =
|
|
3525
|
+
uri = normalizeString(uri, options);
|
|
3470
3526
|
} else if (typeof uri === "object") {
|
|
3471
3527
|
uri = parse6(serialize(uri, options), options);
|
|
3472
3528
|
}
|
|
@@ -3532,19 +3588,9 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
3532
3588
|
return target;
|
|
3533
3589
|
}
|
|
3534
3590
|
function equal(uriA, uriB, options) {
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
} else if (typeof uriA === "object") {
|
|
3539
|
-
uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
|
|
3540
|
-
}
|
|
3541
|
-
if (typeof uriB === "string") {
|
|
3542
|
-
uriB = unescape(uriB);
|
|
3543
|
-
uriB = serialize(normalizeComponentEncoding(parse6(uriB, options), true), { ...options, skipEscape: true });
|
|
3544
|
-
} else if (typeof uriB === "object") {
|
|
3545
|
-
uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
|
|
3546
|
-
}
|
|
3547
|
-
return uriA.toLowerCase() === uriB.toLowerCase();
|
|
3591
|
+
const normalizedA = normalizeComparableURI(uriA, options);
|
|
3592
|
+
const normalizedB = normalizeComparableURI(uriB, options);
|
|
3593
|
+
return normalizedA !== undefined && normalizedB !== undefined && normalizedA.toLowerCase() === normalizedB.toLowerCase();
|
|
3548
3594
|
}
|
|
3549
3595
|
function serialize(cmpts, opts) {
|
|
3550
3596
|
const component = {
|
|
@@ -3570,12 +3616,12 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
3570
3616
|
schemeHandler.serialize(component, options);
|
|
3571
3617
|
if (component.path !== undefined) {
|
|
3572
3618
|
if (!options.skipEscape) {
|
|
3573
|
-
component.path =
|
|
3619
|
+
component.path = escapePreservingEscapes(component.path);
|
|
3574
3620
|
if (component.scheme !== undefined) {
|
|
3575
3621
|
component.path = component.path.split("%3A").join(":");
|
|
3576
3622
|
}
|
|
3577
3623
|
} else {
|
|
3578
|
-
component.path =
|
|
3624
|
+
component.path = normalizePercentEncoding(component.path);
|
|
3579
3625
|
}
|
|
3580
3626
|
}
|
|
3581
3627
|
if (options.reference !== "suffix" && component.scheme) {
|
|
@@ -3610,7 +3656,16 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
3610
3656
|
return uriTokens.join("");
|
|
3611
3657
|
}
|
|
3612
3658
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3613
|
-
function
|
|
3659
|
+
function getParseError(parsed, matches) {
|
|
3660
|
+
if (matches[2] !== undefined && parsed.path && parsed.path[0] !== "/") {
|
|
3661
|
+
return 'URI path must start with "/" when authority is present.';
|
|
3662
|
+
}
|
|
3663
|
+
if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
|
|
3664
|
+
return "URI port is malformed.";
|
|
3665
|
+
}
|
|
3666
|
+
return;
|
|
3667
|
+
}
|
|
3668
|
+
function parseWithStatus(uri, opts) {
|
|
3614
3669
|
const options = Object.assign({}, opts);
|
|
3615
3670
|
const parsed = {
|
|
3616
3671
|
scheme: undefined,
|
|
@@ -3621,6 +3676,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
3621
3676
|
query: undefined,
|
|
3622
3677
|
fragment: undefined
|
|
3623
3678
|
};
|
|
3679
|
+
let malformedAuthorityOrPort = false;
|
|
3624
3680
|
let isIP = false;
|
|
3625
3681
|
if (options.reference === "suffix") {
|
|
3626
3682
|
if (options.scheme) {
|
|
@@ -3641,6 +3697,11 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
3641
3697
|
if (isNaN(parsed.port)) {
|
|
3642
3698
|
parsed.port = matches[5];
|
|
3643
3699
|
}
|
|
3700
|
+
const parseError = getParseError(parsed, matches);
|
|
3701
|
+
if (parseError !== undefined) {
|
|
3702
|
+
parsed.error = parsed.error || parseError;
|
|
3703
|
+
malformedAuthorityOrPort = true;
|
|
3704
|
+
}
|
|
3644
3705
|
if (parsed.host) {
|
|
3645
3706
|
const ipv4result = isIPv4(parsed.host);
|
|
3646
3707
|
if (ipv4result === false) {
|
|
@@ -3679,14 +3740,18 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
3679
3740
|
parsed.scheme = unescape(parsed.scheme);
|
|
3680
3741
|
}
|
|
3681
3742
|
if (parsed.host !== undefined) {
|
|
3682
|
-
parsed.host = unescape(parsed.host);
|
|
3743
|
+
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
|
|
3683
3744
|
}
|
|
3684
3745
|
}
|
|
3685
3746
|
if (parsed.path) {
|
|
3686
|
-
parsed.path =
|
|
3747
|
+
parsed.path = normalizePathEncoding(parsed.path);
|
|
3687
3748
|
}
|
|
3688
3749
|
if (parsed.fragment) {
|
|
3689
|
-
|
|
3750
|
+
try {
|
|
3751
|
+
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
|
|
3752
|
+
} catch {
|
|
3753
|
+
parsed.error = parsed.error || "URI malformed";
|
|
3754
|
+
}
|
|
3690
3755
|
}
|
|
3691
3756
|
}
|
|
3692
3757
|
if (schemeHandler && schemeHandler.parse) {
|
|
@@ -3695,7 +3760,29 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
3695
3760
|
} else {
|
|
3696
3761
|
parsed.error = parsed.error || "URI can not be parsed.";
|
|
3697
3762
|
}
|
|
3698
|
-
return parsed;
|
|
3763
|
+
return { parsed, malformedAuthorityOrPort };
|
|
3764
|
+
}
|
|
3765
|
+
function parse6(uri, opts) {
|
|
3766
|
+
return parseWithStatus(uri, opts).parsed;
|
|
3767
|
+
}
|
|
3768
|
+
function normalizeString(uri, opts) {
|
|
3769
|
+
return normalizeStringWithStatus(uri, opts).normalized;
|
|
3770
|
+
}
|
|
3771
|
+
function normalizeStringWithStatus(uri, opts) {
|
|
3772
|
+
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
|
|
3773
|
+
return {
|
|
3774
|
+
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
|
|
3775
|
+
malformedAuthorityOrPort
|
|
3776
|
+
};
|
|
3777
|
+
}
|
|
3778
|
+
function normalizeComparableURI(uri, opts) {
|
|
3779
|
+
if (typeof uri === "string") {
|
|
3780
|
+
const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
|
|
3781
|
+
return malformedAuthorityOrPort ? undefined : normalized;
|
|
3782
|
+
}
|
|
3783
|
+
if (typeof uri === "object") {
|
|
3784
|
+
return serialize(uri, opts);
|
|
3785
|
+
}
|
|
3699
3786
|
}
|
|
3700
3787
|
var fastUri = {
|
|
3701
3788
|
SCHEMES,
|
|
@@ -54449,6 +54536,7 @@ var pausedPlatforms = new Map;
|
|
|
54449
54536
|
var lastCleanupTime = new Map;
|
|
54450
54537
|
var CLEANUP_THROTTLE_MS = 5 * 60 * 1000;
|
|
54451
54538
|
var CLEANUP_MAX_AGE_MS = 60 * 60 * 1000;
|
|
54539
|
+
var hiddenCleanupDone = new Set;
|
|
54452
54540
|
// node_modules/@redactpii/node/lib/index.mjs
|
|
54453
54541
|
class Redactor {
|
|
54454
54542
|
apiKey;
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -57,6 +57,8 @@ platforms:
|
|
|
57
57
|
| `botName` | No | Mention name (default: `claude-code`) |
|
|
58
58
|
| `allowedUsers` | No | List of usernames who can use the bot |
|
|
59
59
|
| `skipPermissions` | No | Auto-approve actions (default: `false`) |
|
|
60
|
+
| `sessionHeader` | No | Per-thread header visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no header post) |
|
|
61
|
+
| `stickyMessage` | No | Channel sticky visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no sticky, no bumping) |
|
|
60
62
|
|
|
61
63
|
### Slack
|
|
62
64
|
|
|
@@ -71,6 +73,23 @@ platforms:
|
|
|
71
73
|
| `botName` | No | Mention name (default: `claude`) |
|
|
72
74
|
| `allowedUsers` | No | List of Slack usernames |
|
|
73
75
|
| `skipPermissions` | No | Auto-approve actions (default: `false`) |
|
|
76
|
+
| `sessionHeader` | No | Per-thread header visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no header post) |
|
|
77
|
+
| `stickyMessage` | No | Channel sticky visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no sticky, no bumping) |
|
|
78
|
+
|
|
79
|
+
### Quieting the bot's overhead messages
|
|
80
|
+
|
|
81
|
+
Both the per-thread session header and the channel sticky message default to `full` for backward compatibility. To strip them down on a noisy channel, set the per-platform fields in `config.yaml`:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
platforms:
|
|
85
|
+
- id: mattermost-main
|
|
86
|
+
type: mattermost
|
|
87
|
+
# ... credentials ...
|
|
88
|
+
sessionHeader: hidden # no header post — Claude's reply is the first message in the thread
|
|
89
|
+
stickyMessage: minimal # one-line status bar at the channel bottom, no sessions list
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Note: the per-platform `stickyMessage: <mode>` field is distinct from the top-level `Config.stickyMessage: { description, footer }` block, which still customizes the full sticky for platforms not in `hidden` mode.
|
|
74
93
|
|
|
75
94
|
## Claude Accounts (optional, multi-account mode)
|
|
76
95
|
|
|
@@ -139,6 +158,8 @@ Options:
|
|
|
139
158
|
--chrome Enable Chrome integration
|
|
140
159
|
--no-chrome Disable Chrome integration
|
|
141
160
|
--worktree-mode <mode> Git worktree mode: off, prompt, require
|
|
161
|
+
--session-header <mode> Per-thread header: full | minimal | hidden (overrides per-platform config)
|
|
162
|
+
--sticky-message <mode> Channel sticky: full | minimal | hidden (overrides per-platform config)
|
|
142
163
|
--setup Re-run setup wizard
|
|
143
164
|
--debug Enable debug logging
|
|
144
165
|
--version Show version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-threads",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Share Claude Code sessions live in a Mattermost channel with interactive features",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"commander": "^14.0.2",
|
|
72
72
|
"diff": "^8.0.3",
|
|
73
73
|
"express-rate-limit": "^8.3.0",
|
|
74
|
-
"hono": "^4.12.
|
|
74
|
+
"hono": "^4.12.18",
|
|
75
75
|
"ink": "^6.6.0",
|
|
76
76
|
"ink-scroll-view": "^0.3.5",
|
|
77
77
|
"js-yaml": "^4.1.1",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"@types/ws": "^8.18.0",
|
|
95
95
|
"eslint": "^10.1.0",
|
|
96
96
|
"husky": "^9.1.7",
|
|
97
|
-
"lint-staged": "^
|
|
97
|
+
"lint-staged": "^17.0.4",
|
|
98
98
|
"prettier": "^3.4.2",
|
|
99
99
|
"typescript": "^6.0.2",
|
|
100
100
|
"typescript-eslint": "^8.57.2"
|
|
@@ -115,7 +115,8 @@
|
|
|
115
115
|
"express-rate-limit": "$express-rate-limit",
|
|
116
116
|
"flatted": ">=3.4.0",
|
|
117
117
|
"picomatch": ">=2.3.2",
|
|
118
|
-
"path-to-regexp": ">=8.4.0"
|
|
118
|
+
"path-to-regexp": ">=8.4.0",
|
|
119
|
+
"fast-uri": ">=3.1.2"
|
|
119
120
|
},
|
|
120
121
|
"resolutions": {
|
|
121
122
|
"hono": "$hono",
|
|
@@ -123,6 +124,7 @@
|
|
|
123
124
|
"express-rate-limit": "$express-rate-limit",
|
|
124
125
|
"flatted": ">=3.4.0",
|
|
125
126
|
"picomatch": ">=2.3.2",
|
|
126
|
-
"path-to-regexp": ">=8.4.0"
|
|
127
|
+
"path-to-regexp": ">=8.4.0",
|
|
128
|
+
"fast-uri": ">=3.1.2"
|
|
127
129
|
}
|
|
128
130
|
}
|