claude-threads 1.16.3 → 1.17.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 CHANGED
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.17.0] - 2026-06-05
11
+
12
+ ### Added
13
+ - **`!mentions` quiet mode: respond only when @mentioned.** New per-session toggle for holding side conversations inside a bot thread without the bot replying to every message. `!mentions on` makes the bot ignore thread replies that don't explicitly @mention it; `!mentions off` (or a bare `!mentions` to flip the current value) turns it back off. Commands and pending worktree-branch-name prompts always bypass the gate, so `!mentions off` (and answering a worktree prompt) works even while quiet mode is on. When quiet mode is on, the session header shows a row noting it, so a returning user can see why the bot is staying quiet. The setting is owned by the session owner or a globally allowed user and persists across a bot restart. A global `respondOnlyWhenMentioned: true` in `config.yaml` (also offered in the onboarding wizard) seeds quiet mode on every new thread, so users who mostly want quiet threads don't have to run `!mentions on` each time; each session still keeps its own value and can override per-thread. The default everywhere is unchanged (the bot treats every approved-user reply as input), so existing threads and configs are unaffected. (#402)
14
+
15
+ ### Dependencies
16
+ - **Production:** `js-yaml` 4.1.1 → 4.2.0, `react` 19.2.6 → 19.2.7. (#404)
17
+ - **Dev:** `typescript-eslint` 8.60.0 → 8.60.1. (#403)
18
+
10
19
  ## [1.16.3] - 2026-05-31
11
20
 
12
21
  ### Changed
package/README.md CHANGED
@@ -85,6 +85,7 @@ Type `!help` in any session thread:
85
85
  | `!compact` | Compress context to free up space |
86
86
  | `!cd <path>` | Change working directory (restarts Claude) |
87
87
  | `!permissions <mode>` | Set permission mode: `default` / `auto` / `bypass` |
88
+ | `!mentions [on\|off]` | Quiet mode: only respond when @mentioned (bare `!mentions` toggles) |
88
89
  | `!worktree <branch>` | Create and switch to a git worktree (also: `list`, `switch`, `remove`, `cleanup`, `off`) |
89
90
  | `!plugin <list\|install\|uninstall> [name]` | Manage Claude Code plugins (restarts Claude) |
90
91
  | `!invite @user` | Invite a user to this session (added as `Co-Authored-By:` on commits) |
package/dist/index.js CHANGED
@@ -54157,6 +54157,13 @@ async function runOnboarding(reconfigure = false) {
54157
54157
  { title: "Require", value: "require", description: "Always require branch name before starting" }
54158
54158
  ],
54159
54159
  initial: existingConfig?.worktreeMode === "off" ? 1 : existingConfig?.worktreeMode === "require" ? 2 : 0
54160
+ },
54161
+ {
54162
+ type: "confirm",
54163
+ name: "respondOnlyWhenMentioned",
54164
+ message: "Respond only when @mentioned?",
54165
+ initial: existingConfig?.respondOnlyWhenMentioned || false,
54166
+ hint: "New threads start in quiet mode; users can still toggle per-thread with !mentions"
54160
54167
  }
54161
54168
  ], { onCancel });
54162
54169
  const config = {
@@ -54164,6 +54171,9 @@ async function runOnboarding(reconfigure = false) {
54164
54171
  ...globalSettings,
54165
54172
  platforms: []
54166
54173
  };
54174
+ if (!config.respondOnlyWhenMentioned) {
54175
+ delete config.respondOnlyWhenMentioned;
54176
+ }
54167
54177
  console.log("");
54168
54178
  console.log(bold(" Platform Setup"));
54169
54179
  console.log("");
@@ -54274,7 +54284,7 @@ async function runReconfigureFlow(existingConfig) {
54274
54284
  {
54275
54285
  title: "Global settings",
54276
54286
  value: "global",
54277
- description: `workingDir, chrome, worktreeMode`
54287
+ description: `workingDir, chrome, worktreeMode, mentions`
54278
54288
  }
54279
54289
  ];
54280
54290
  for (let i2 = 0;i2 < config.platforms.length; i2++) {
@@ -54321,9 +54331,19 @@ async function runReconfigureFlow(existingConfig) {
54321
54331
  { title: "Require", value: "require", description: "Always require branch name before starting" }
54322
54332
  ],
54323
54333
  initial: config.worktreeMode === "off" ? 1 : config.worktreeMode === "require" ? 2 : 0
54334
+ },
54335
+ {
54336
+ type: "confirm",
54337
+ name: "respondOnlyWhenMentioned",
54338
+ message: "Respond only when @mentioned?",
54339
+ initial: config.respondOnlyWhenMentioned || false,
54340
+ hint: "New threads start in quiet mode; users can still toggle per-thread with !mentions"
54324
54341
  }
54325
54342
  ], { onCancel });
54326
54343
  config = { ...config, ...globalSettings };
54344
+ if (!config.respondOnlyWhenMentioned) {
54345
+ delete config.respondOnlyWhenMentioned;
54346
+ }
54327
54347
  console.log(green(" ✓ Global settings updated"));
54328
54348
  } else if (action === "add-new") {
54329
54349
  console.log("");
@@ -54452,6 +54472,7 @@ async function showConfigSummary(config) {
54452
54472
  console.log(dim(` Working Directory: ${config.workingDir}`));
54453
54473
  console.log(dim(` Chrome Integration: ${config.chrome ? "Enabled" : "Disabled"}`));
54454
54474
  console.log(dim(` Worktree Mode: ${config.worktreeMode}`));
54475
+ console.log(dim(` Respond Only When Mentioned: ${config.respondOnlyWhenMentioned ? "Enabled" : "Disabled"}`));
54455
54476
  console.log("");
54456
54477
  console.log(dim(` Platforms (${config.platforms.length}):`));
54457
54478
  for (const platform of config.platforms) {
@@ -59463,6 +59484,14 @@ var COMMAND_REGISTRY = [
59463
59484
  worksInFirstMessage: true,
59464
59485
  isStackable: true
59465
59486
  },
59487
+ {
59488
+ command: "mentions",
59489
+ description: "Toggle quiet mode: only respond when @mentioned by name",
59490
+ args: "on / off",
59491
+ category: "settings",
59492
+ audience: "user",
59493
+ claudeNotes: "User decisions, not yours"
59494
+ },
59466
59495
  {
59467
59496
  command: "update",
59468
59497
  description: "Show auto-update status",
@@ -59572,6 +59601,7 @@ var COMMAND_PATTERNS = [
59572
59601
  ["invite", /^!invite\s+@?([\w.-]+)\s*$/i],
59573
59602
  ["kick", /^!kick\s+@?([\w.-]+)\s*$/i],
59574
59603
  ["permissions", /^!permissions?\s+(default|auto|bypass|interactive|skip)\s*$/i],
59604
+ ["mentions", /^!mentions(?:\s+(on|off))?\s*$/i],
59575
59605
  ["update", /^!update(?:\s+(now|defer))?\s*$/i],
59576
59606
  ["context", /^!context\s*$/i],
59577
59607
  ["cost", /^!cost\s*$/i],
@@ -59956,6 +59986,15 @@ var handlePermissions = async (ctx, args) => {
59956
59986
  await ctx.sessionManager.setSessionPermissionMode(ctx.threadId, ctx.username, mode);
59957
59987
  return { handled: true };
59958
59988
  };
59989
+ var handleMentions = async (ctx, args) => {
59990
+ if (ctx.commandContext === "first-message") {
59991
+ return { handled: false };
59992
+ }
59993
+ if (ctx.isAllowed) {
59994
+ await ctx.sessionManager.setRespondOnlyWhenMentioned(ctx.threadId, ctx.username, args);
59995
+ }
59996
+ return { handled: true };
59997
+ };
59959
59998
  var handleWorktree = async (ctx, args) => {
59960
59999
  const parts = args?.split(/\s+/) || [];
59961
60000
  const subcommandOrBranch = parts[0]?.toLowerCase();
@@ -60089,6 +60128,7 @@ handlers.set("kick", handleKick);
60089
60128
  handlers.set("github-email", handleGitHubEmail);
60090
60129
  handlers.set("cd", handleCd);
60091
60130
  handlers.set("permissions", handlePermissions);
60131
+ handlers.set("mentions", handleMentions);
60092
60132
  handlers.set("worktree", handleWorktree);
60093
60133
  handlers.set("bug", handleBug);
60094
60134
  handlers.set("plugin", handlePlugin);
@@ -71262,6 +71302,31 @@ async function kickUser(session, kickedUser, kickedBy, ctx) {
71262
71302
  sessionLog5(session).warn(`\uD83D\uDEAB @${kickedUser} was not in session`);
71263
71303
  }
71264
71304
  }
71305
+ async function setRespondOnlyWhenMentioned(session, username, arg, ctx) {
71306
+ if (!await requireSessionOwner(session, username, "change session settings")) {
71307
+ return;
71308
+ }
71309
+ const normalized = arg?.toLowerCase();
71310
+ let enabled;
71311
+ if (normalized === "on") {
71312
+ enabled = true;
71313
+ } else if (normalized === "off") {
71314
+ enabled = false;
71315
+ } else {
71316
+ enabled = !session.respondOnlyWhenMentioned;
71317
+ }
71318
+ session.respondOnlyWhenMentioned = enabled;
71319
+ ctx.ops.persistSession(session);
71320
+ const botName = session.platform.getBotName();
71321
+ if (enabled) {
71322
+ await post(session, "success", `Quiet mode on — I'll only respond when you @mention me (\`@${botName}\`). Use \`!mentions off\` to turn this off.`);
71323
+ } else {
71324
+ await post(session, "success", `Quiet mode off — I'll respond to every message in this thread again.`);
71325
+ }
71326
+ sessionLog5(session).info(`\uD83D\uDD15 respondOnlyWhenMentioned=${enabled} by @${username}`);
71327
+ session.threadLogger?.logCommand("mentions", enabled ? "on" : "off", username);
71328
+ await updateSessionHeader(session, ctx);
71329
+ }
71265
71330
  async function setGitHubEmail(session, username, arg, ctx) {
71266
71331
  const formatter = session.platform.getFormatter();
71267
71332
  const trimmed = arg?.trim();
@@ -71436,6 +71501,9 @@ async function updateSessionHeader(session, ctx) {
71436
71501
  if (otherParticipants) {
71437
71502
  items.push(["\uD83D\uDC65", "Participants", otherParticipants]);
71438
71503
  }
71504
+ if (session.respondOnlyWhenMentioned) {
71505
+ items.push(["\uD83D\uDD15", "Replies", `only when ${formatter.formatBold("@mentioned")} (${formatter.formatCode("!mentions off")} to disable)`]);
71506
+ }
71439
71507
  if (session.claudeAccountId) {
71440
71508
  const account = ctx.ops.getClaudeAccount(session.claudeAccountId);
71441
71509
  const label = account?.displayName ?? session.claudeAccountId;
@@ -72090,6 +72158,7 @@ async function startSession(options, username, displayName, replyToPostId, platf
72090
72158
  planApproved: false,
72091
72159
  sessionAllowedUsers: new Set([username]),
72092
72160
  forceInteractivePermissions,
72161
+ respondOnlyWhenMentioned: ctx.config.respondOnlyWhenMentioned ?? false,
72093
72162
  permissionModeOverride: sessionPermissionModeOverride,
72094
72163
  sessionStartPostId: startPost ? startPost.id : null,
72095
72164
  sessionHeaderMode,
@@ -72245,6 +72314,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
72245
72314
  planApproved: state.planApproved ?? false,
72246
72315
  sessionAllowedUsers: new Set(state.sessionAllowedUsers),
72247
72316
  forceInteractivePermissions: state.forceInteractivePermissions ?? false,
72317
+ respondOnlyWhenMentioned: state.respondOnlyWhenMentioned ?? false,
72248
72318
  sessionStartPostId: state.sessionStartPostId ?? null,
72249
72319
  sessionHeaderMode: resumeSessionHeaderMode(state.sessionHeaderMode, ctx.ops.getPlatformOverhead(platformId).sessionHeader),
72250
72320
  timers: createSessionTimers(),
@@ -72982,6 +73052,7 @@ class SessionManager extends EventEmitter4 {
72982
73052
  permissionMode;
72983
73053
  chromeEnabled;
72984
73054
  worktreeMode;
73055
+ respondOnlyWhenMentioned;
72985
73056
  threadLogsEnabled;
72986
73057
  threadLogsRetentionDays;
72987
73058
  limits;
@@ -73000,12 +73071,13 @@ class SessionManager extends EventEmitter4 {
73000
73071
  platformOverhead = new Map;
73001
73072
  autoUpdateManager = null;
73002
73073
  accountPool;
73003
- constructor(workingDir, permissionModeOrSkipFlag = "default", chromeEnabled = false, worktreeMode = "prompt", sessionsPath, threadLogsEnabled = true, threadLogsRetentionDays = 30, limits, claudeAccounts) {
73074
+ constructor(workingDir, permissionModeOrSkipFlag = "default", chromeEnabled = false, worktreeMode = "prompt", sessionsPath, threadLogsEnabled = true, threadLogsRetentionDays = 30, limits, claudeAccounts, respondOnlyWhenMentioned = false) {
73004
73075
  super();
73005
73076
  this.workingDir = workingDir;
73006
73077
  this.permissionMode = typeof permissionModeOrSkipFlag === "boolean" ? permissionModeOrSkipFlag ? "bypass" : "default" : permissionModeOrSkipFlag;
73007
73078
  this.chromeEnabled = chromeEnabled;
73008
73079
  this.worktreeMode = worktreeMode;
73080
+ this.respondOnlyWhenMentioned = respondOnlyWhenMentioned;
73009
73081
  this.threadLogsEnabled = threadLogsEnabled;
73010
73082
  this.threadLogsRetentionDays = threadLogsRetentionDays;
73011
73083
  this.limits = resolveLimits(limits);
@@ -73093,6 +73165,7 @@ class SessionManager extends EventEmitter4 {
73093
73165
  workingDir: this.workingDir,
73094
73166
  permissionMode: this.permissionMode,
73095
73167
  chromeEnabled: this.chromeEnabled,
73168
+ respondOnlyWhenMentioned: this.respondOnlyWhenMentioned,
73096
73169
  debug: this.debug,
73097
73170
  maxSessions: this.limits.maxSessions,
73098
73171
  threadLogsEnabled: this.threadLogsEnabled,
@@ -73286,6 +73359,7 @@ class SessionManager extends EventEmitter4 {
73286
73359
  planApproved: session.planApproved,
73287
73360
  sessionAllowedUsers: [...session.sessionAllowedUsers],
73288
73361
  forceInteractivePermissions: session.forceInteractivePermissions,
73362
+ respondOnlyWhenMentioned: session.respondOnlyWhenMentioned,
73289
73363
  sessionStartPostId: session.sessionStartPostId,
73290
73364
  tasksPostId: taskListSnapshot?.postId ?? null,
73291
73365
  lastTasksContent: taskListSnapshot?.content ?? null,
@@ -73583,6 +73657,12 @@ class SessionManager extends EventEmitter4 {
73583
73657
  return;
73584
73658
  await setGitHubEmail(session, username, arg, this.getContext());
73585
73659
  }
73660
+ async setRespondOnlyWhenMentioned(threadId, username, arg) {
73661
+ const session = this.findSessionByThreadId(threadId);
73662
+ if (!session)
73663
+ return;
73664
+ await setRespondOnlyWhenMentioned(session, username, arg, this.getContext());
73665
+ }
73586
73666
  async setSessionPermissionMode(threadId, username, mode) {
73587
73667
  const session = this.findSessionByThreadId(threadId);
73588
73668
  if (!session)
@@ -83543,6 +83623,9 @@ async function handleMessage(client, session, post2, user, options) {
83543
83623
  return;
83544
83624
  }
83545
83625
  }
83626
+ if (activeSession.respondOnlyWhenMentioned && !client.isBotMentioned(message)) {
83627
+ return;
83628
+ }
83546
83629
  if (!session.isUserAllowedInSession(threadRoot, username)) {
83547
83630
  if (content)
83548
83631
  await session.requestMessageApproval(threadRoot, username, content);
@@ -84878,7 +84961,7 @@ async function startWithoutDaemon() {
84878
84961
  keepAlive.setEnabled(keepAliveEnabled);
84879
84962
  const threadLogsEnabled = config.threadLogs?.enabled ?? true;
84880
84963
  const threadLogsRetentionDays = config.threadLogs?.retentionDays ?? 30;
84881
- const session = new SessionManager(workingDir, initialPermissionMode, config.chrome, config.worktreeMode, undefined, threadLogsEnabled, threadLogsRetentionDays, config.limits, config.claudeAccounts);
84964
+ const session = new SessionManager(workingDir, initialPermissionMode, config.chrome, config.worktreeMode, undefined, threadLogsEnabled, threadLogsRetentionDays, config.limits, config.claudeAccounts, config.respondOnlyWhenMentioned);
84882
84965
  if (config.stickyMessage) {
84883
84966
  session.setStickyMessageCustomization(config.stickyMessage.description, config.stickyMessage.footer);
84884
84967
  }
@@ -55520,6 +55520,14 @@ var COMMAND_REGISTRY = [
55520
55520
  worksInFirstMessage: true,
55521
55521
  isStackable: true
55522
55522
  },
55523
+ {
55524
+ command: "mentions",
55525
+ description: "Toggle quiet mode: only respond when @mentioned by name",
55526
+ args: "on / off",
55527
+ category: "settings",
55528
+ audience: "user",
55529
+ claudeNotes: "User decisions, not yours"
55530
+ },
55523
55531
  {
55524
55532
  command: "update",
55525
55533
  description: "Show auto-update status",
@@ -55790,6 +55798,15 @@ var handlePermissions = async (ctx, args) => {
55790
55798
  await ctx.sessionManager.setSessionPermissionMode(ctx.threadId, ctx.username, mode);
55791
55799
  return { handled: true };
55792
55800
  };
55801
+ var handleMentions = async (ctx, args) => {
55802
+ if (ctx.commandContext === "first-message") {
55803
+ return { handled: false };
55804
+ }
55805
+ if (ctx.isAllowed) {
55806
+ await ctx.sessionManager.setRespondOnlyWhenMentioned(ctx.threadId, ctx.username, args);
55807
+ }
55808
+ return { handled: true };
55809
+ };
55793
55810
  var handleWorktree = async (ctx, args) => {
55794
55811
  const parts = args?.split(/\s+/) || [];
55795
55812
  const subcommandOrBranch = parts[0]?.toLowerCase();
@@ -55923,6 +55940,7 @@ handlers.set("kick", handleKick);
55923
55940
  handlers.set("github-email", handleGitHubEmail);
55924
55941
  handlers.set("cd", handleCd);
55925
55942
  handlers.set("permissions", handlePermissions);
55943
+ handlers.set("mentions", handleMentions);
55926
55944
  handlers.set("worktree", handleWorktree);
55927
55945
  handlers.set("bug", handleBug);
55928
55946
  handlers.set("plugin", handlePlugin);
@@ -9,6 +9,7 @@ version: 1
9
9
  workingDir: /home/user/repos/myproject
10
10
  chrome: false
11
11
  worktreeMode: prompt
12
+ respondOnlyWhenMentioned: false
12
13
 
13
14
  platforms:
14
15
  # Mattermost
@@ -41,6 +42,7 @@ platforms:
41
42
  | `workingDir` | Default working directory for Claude | Current directory |
42
43
  | `chrome` | Enable Chrome integration | `false` |
43
44
  | `worktreeMode` | Git worktree mode: `off`, `prompt`, or `require` | `prompt` |
45
+ | `respondOnlyWhenMentioned` | Start new threads in quiet mode, where the bot only replies to messages that @mention it. Users can still toggle per-thread with `!mentions`. | `false` |
44
46
 
45
47
  ## Platform Settings
46
48
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "1.16.3",
3
+ "version": "1.17.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",