claude-threads 1.8.2 → 1.9.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
@@ -5,6 +5,56 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.9.0] - 2026-04-24
9
+
10
+ ### Added
11
+ - **Three-way permission modes** — `default` | `auto` | `bypass`. Claude CLI 2.1.x introduced a classifier-based `auto` permission mode; claude-threads now exposes it alongside the historical `default` (MCP-prompt-everything) and `bypass` (`--dangerously-skip-permissions`) modes. Set via `permissionMode` in `config.yaml`, the `--permission-mode` CLI flag, or the `!permissions default|auto|bypass` in-session command (legacy `interactive`/`skip` aliases still work). Onboarding wizard now picks `auto` as the recommended default. UI toggle key `[p]` cycles through the three modes. (#343)
12
+ - **Security hardening: MCP config via owner-only tempfile** — the Claude subprocess's MCP permission config contains the bot's platform token. It used to be passed inline on `--mcp-config` argv, exposing the token in `ps`. Now written to a mode-`0600` tempfile and passed by path; cleaned up on Claude exit. Gated by `CLAUDE_THREADS_MCP_CONFIG_INLINE=1` rollback flag for one release. (#342)
13
+ - **Audit log for rejected reactions** — `SessionManager.handleReaction` now emits a structured `reaction.rejected` event when the allowlist check drops a reaction. Observable signal for probing attempts without changing enforcement behavior. (#342)
14
+ - **Bounded aggregate stderr cap across `ClaudeCli` instances** — per-instance 10KB cap stays; under aggregate pressure (>10MB) instances trim to 1KB so a runaway fleet cannot dominate the bot's heap. (#342)
15
+ - **Tunable `flushDelayMs`** — streaming cadence (default 500ms) is now configurable via `limits.flushDelayMs` in `config.yaml`. (#342)
16
+
17
+ ### Changed
18
+ - **Onboarding and UI speak the three-mode language.** The wizard question changed from `Require approval for Claude actions? (Y/n)` to a three-way picker, defaulting to `auto`. The keyboard `[p]erms` indicator in the footer cycles default → auto → bypass with color-coded severity (green/yellow/red) instead of a green/gray on/off chip.
19
+ - **`!permissions` command accepts all three modes** plus legacy aliases. `!permissions interactive` → `default`; `!permissions skip` → `bypass`. The confirmation post shows the canonical mode name and a one-sentence description of what it does.
20
+ - **Sticky message and session header** show the three-mode chip (`🔐 Default`, `⚡ Auto`, `⚠️ Bypass`) consistently. Previously the sticky used `⚡ Auto` to mean bypass.
21
+
22
+ ### Deprecated
23
+ - **`skipPermissions: boolean` in platform config** — keeps working as an alias. `permissionMode: 'default'|'auto'|'bypass'` is the new canonical field. Precedence: `permissionMode` wins when both are set. (#343)
24
+ - **`--skip-permissions` / `--no-skip-permissions` CLI flags** — kept as aliases for `--permission-mode bypass` / `--permission-mode default`. (#343)
25
+
26
+ ### Fixed
27
+ - **`content.ts` thread log lost exception text on updatePost failure** — the refactor that collapsed five try/catch blocks into a `tryUpdatePost` helper in #342 dropped the `error: String(err)` field from the flush-path thread log. Restored. (#342)
28
+ - **`!permissions <mode>` right after session start aborted Claude** with `No conversation found with session ID`. The respawn paths hardcoded `--resume`, but pre-first-turn sessions have no conversation to resume. Now gated on `session.lifecycle.hasClaudeResponded`. Same fix applied to plugin install/uninstall respawn. (#345)
29
+ - **`!help` showed stale `!permissions interactive\|skip`** with a pipe that rendered as the literal `\|` inside a Mattermost markdown table. Registry updated to `default / auto / bypass` (no table-breaking pipe) with a three-mode description. (#345)
30
+ - **Session header kept showing the bot-wide mode after `!permissions auto`** — Claude respawned with the correct flag but the session object didn't track the override, so the header read bot-wide. Added `Session.permissionModeOverride` and a single `effectivePermissionMode` helper that all call sites (header, `isSessionInteractive`, respawn on `!cd`/plugin/worktree) now route through. (#345)
31
+
32
+ ### Removed
33
+ - **`src/mattermost/api.ts`** — the standalone REST helpers folded into `src/platform/mattermost/permission-api.ts` (only consumer). Net removal: 194 lines of code + 459 lines of redundant tests; equivalent HTTP-level coverage now lives in `src/platform/mattermost/client.test.ts`. (#342)
34
+ - **`src/config.ts`** — 37 lines of re-exports. `src/config/migration.ts` renamed to `src/config/index.ts` so the config module's entry point reflects what it actually is. (#342)
35
+ - **Internal `skipPermissions` shadow fields** — removed from `SessionConfig`, `ClaudeCliOptions`, `StickyMessageConfig`, and a private `SessionManager` getter once the new `permissionMode` was plumbed end-to-end. (#343)
36
+
37
+ ### Internals
38
+ - **Test coverage floor raised** before the structural refactors above. New test files for MCP permission server, plugin handler, Mattermost client, and permission-API helpers. Existing `lifecycle.test.ts` and `manager.test.ts` expanded for branch coverage. Totals: 1970 → 2101 tests (+131). Coverage on `src/mcp/permission-server.ts`: 0% → 80% lines; `src/operations/plugin/handler.ts`: 0% → 100% funcs; `src/session/lifecycle.ts`: 21% → 31% lines. (#341)
39
+ - **Small testability refactor in `src/mcp/permission-server.ts`** — extracted `handlePermissionWith()` so the permission flow is unit-testable without spinning up the real `PermissionApi` or reading `process.env` at module load. No behavior change. (#341)
40
+ - **5 try/catch blocks in `src/operations/executors/content.ts`** collapsed into a `tryUpdatePost` helper with `onSuccess`/`onFailure` callbacks — keeps the three distinct failure-state reset variants explicit via callbacks rather than hiding them. (#342)
41
+ - **DRY permission-mode helpers**: `permissionModeDisplay`, `permissionModeDescription`, and `effectivePermissionMode` live in `src/config/types.ts` as single sources of truth. A `MODE_INFO: Record<PermissionMode, …>` table backs the display + description helpers. The original `permissionModeForRestart` helper was introduced in #343 and then collapsed into `effectivePermissionMode` in #345 once the precedence logic for "respawn mode" and "current effective mode" had converged. (#343, #345)
42
+
43
+ ## [1.8.3] - 2026-04-24
44
+
45
+ ### Fixed
46
+ - **Duplicate `claude.sendMessage()` on every session start** — `lifecycle.startSession()` was misreading `offerContextPrompt`'s return contract: the helper returns `false` after sending the message itself in the auto-include / no-context branches, but `lifecycle.ts` interpreted that as "didn't send, please send" and fired a duplicate. Every session start was sending the user's prompt to Claude twice. Net effect on production: ~2× the API turns at session start. Fix: trust the helper's return contract and don't double-send. (#340)
47
+ - **Listener leak on `disconnect()`** — `disconnect()` was synchronous: it called `ws.close()` and returned, but EventEmitter listeners stayed attached. Any in-flight `'message'` event the WebSocket queued just before close still fired the bot's `startSession`. Mostly invisible in production (shutdown paths don't reconnect immediately) but caused integration test bots to receive duplicate session-start events during back-to-back test transitions. `disconnect()` now removes all event listeners before closing, and returns `Promise<void>` resolving when the close handshake completes (1s safety timeout). Production callers in shutdown paths can fire-and-forget; tests can `await`. (#340)
48
+ - **Integration test flake (~30-40% pass-rate gap)** — multiple root causes addressed:
49
+ - Each integration-test bot now uses its own Mattermost user account from a 4-bot pool. Previously all test bots shared one token, so transient overlapping `disconnect()` / `connect()` windows delivered the same WebSocket events to multiple bots, producing duplicate session starts.
50
+ - Each pool bot uses a unique `platformId`. Module-level state in `src/operations/sticky-message/handler.ts` (a `Map<platformId, postId>`) was conflating bots when they all shared `platformId='test-mattermost'`, causing 403 permission errors on cross-bot post operations.
51
+ - Test helper `MattermostTestApi` now retries 500s with exponential backoff (mirroring the production client). Mattermost throws transient 500s on `/posts` due to a residual `pq: duplicate key` race even on 10.11.15; the test fixture used to throw on the first one.
52
+ - `bot.stop()` now awaits the WebSocket close handshake instead of a fixed sleep.
53
+ - CI workflow `--timeout` aligned with `package.json` script (`120000`); the previous hardcoded `60000` silently overrode test-level timeouts. (#340)
54
+
55
+ ### Changed
56
+ - **`PlatformClient.disconnect()` is now `Promise<void>`** instead of `void`. Existing callers in `src/index.ts` and `src/message-handler.ts` are shutdown paths that fire-and-forget; the change is source-compatible (the returned Promise can be ignored). (#340)
57
+
8
58
  ## [1.8.2] - 2026-04-22
9
59
 
10
60
  ### Breaking
package/README.md CHANGED
@@ -15,8 +15,7 @@
15
15
  [![CI](https://github.com/anneschuth/claude-threads/actions/workflows/ci.yml/badge.svg)](https://github.com/anneschuth/claude-threads/actions/workflows/ci.yml)
16
16
  [![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/anneschuth/4951f9235658e276208942986092e5ab/raw/coverage-badge.json)](https://github.com/anneschuth/claude-threads/actions/workflows/ci.yml)
17
17
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
18
- [![Node](https://img.shields.io/badge/Node-%3E%3D18-green.svg)](https://nodejs.org/)
19
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue.svg)](https://www.typescriptlang.org/)
18
+ [![Node](https://img.shields.io/node/v/claude-threads.svg)](https://nodejs.org/)
20
19
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/anneschuth/claude-threads/pulls)
21
20
 
22
21
  **Bring Claude Code to your team.** Run Claude Code on your machine, share it live in Mattermost or Slack. Colleagues can watch, collaborate, and run their own sessions—all from chat.
@@ -30,7 +29,7 @@
30
29
  - **Concurrent sessions** - Each thread gets its own Claude session
31
30
  - **Session persistence** - Sessions survive bot restarts
32
31
  - **Collaboration** - Invite others to participate in your session
33
- - **Interactive permissions** - Approve Claude's actions via emoji reactions
32
+ - **Permission modes** - Three-way control over Claude's tool-use: `default` (every action prompts for 👍/✅/👎 approval via emoji), `auto` (Claude's classifier auto-approves low-risk; high-risk still prompts — recommended), or `bypass` (no prompts, all tools allowed). Set via config, `--permission-mode` CLI flag, or in-session with `!permissions default|auto|bypass`.
34
33
  - **Git worktrees** - Isolate changes in separate branches
35
34
  - **File attachments** - Attach images, PDFs, and files for Claude to analyze
36
35
  - **Chrome automation** - Control Chrome browser for web tasks
@@ -60,7 +59,7 @@ The **interactive setup wizard** will guide you through everything:
60
59
 
61
60
  ### Prerequisites
62
61
 
63
- - **Bun** or **Node 18+** - [Install Bun](https://bun.sh/) or [Install Node](https://nodejs.org/)
62
+ - **Bun 1.2.21+** or **Node 20+** - [Install Bun](https://bun.sh/) or [Install Node](https://nodejs.org/)
64
63
  - **Claude Code CLI working** - test with `claude --version` (needs API key or subscription)
65
64
 
66
65
  ### Use