claude-threads 1.22.0 → 1.23.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,24 @@ 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.23.0] - 2026-08-08
9
+
10
+ ### Fixed
11
+ - **A failed compaction no longer leaves a stale "🗜️ Compacting context..." post forever.** Captured against the real CLI (2.1.226, `real-cli-captures/compact-failed.jsonl`): a failed compact emits **no** `compact_boundary` — only a `status` event with `compact_result: "failed"` and a `compact_error` — so the in-progress post was never resolved. It now updates to "⚠️ Compaction failed (reason)". Long-lived bot threads auto-compact in production, so this state was reachable by simply keeping a session busy.
12
+
13
+ ### Added
14
+ - **Compaction completion shows real token counts.** The completion post now renders `pre → post` ("Context compacted (manual, 31k → 3k tokens)") using the `post_tokens` field verified in a new reference capture (`real-cli-captures/compact.jsonl`, recorded via a manual `/compact` driven through stream-json).
15
+ - **`auth_status` events are handled.** An auth error from the CLI mid-session (expired OAuth, revoked key) now posts a warning to the thread instead of vanishing; progress-only auth updates are logged. Shape taken from the Agent SDK's published types (`SDKAuthStatusMessage`, `@anthropic-ai/claude-agent-sdk` 0.3.226) — deliberately not capture-backed, since provoking a real auth failure requires a broken environment.
16
+ - **The mock's `persistent-session` scenario now carries the telemetry noise real streams have** (`thinking_tokens`, `post_turn_summary`, `active_goal`), pinning deliberately that the bot tolerates unconsumed event types — previously that tolerance was only proven incidentally. New `compaction`/`compaction-failed` scenarios and an integration suite cover the compaction lifecycle end to end on both platforms.
17
+
18
+ ## [1.22.1] - 2026-08-08
19
+
20
+ ### Fixed
21
+ - **Permission prompts work in source/dev mode (`bun run dev`).** The per-session MCP config pointed at `mcp/mcp-server.js`, which only exists in a dist build — running the bot from source gave every session an MCP server path that could never spawn, silently breaking interactive permissions, `send_file`, and the decision bridge in dev mode. The path resolution now falls back to the TypeScript source and runs it under the current runtime (bun) instead of node. Built installs are unaffected.
22
+
23
+ ### Changed
24
+ - **The integration-test mock Claude CLI now speaks the modern CLI dialect, verified against verbatim real-CLI captures.** The old mock emitted an event protocol modern CLIs no longer use (top-level `tool_result` events, `TodoWrite` task lists, plan/question tools that never touched the permission system, a process that died after every turn) — so ~120 integration tests were exercising a dialect the real CLI abandoned, and the last three releases' bugs lived exactly in that blind spot. The mock was rewritten against 15 captured reference flows from the real CLI (2.1.225), committed under `tests/integration/fixtures/real-cli-captures/` with a re-capture harness (`tests/e2e-real-cli/capture-events.ts`): `system/init` and `rate_limit_event` at startup, tool results as `tool_result` blocks in `user` events, TaskCreate/TaskUpdate task tracking with result-text id resolution, one `result` per turn with the process staying alive, and SIGINT aborting in-flight tools before exiting — all shapes taken from the captures. **Interactive scenarios now spawn the real MCP permission server and block ExitPlanMode/AskUserQuestion on `permission_prompt`, so the integration suite exercises the production permission-prompt → decision-bridge → reaction-UI path end to end** — including new regression tests pinning that no duplicate generic permission prompt appears next to the plan/question UI (the 1.21.2 bug class). Captures also documented that bypass mode exposes neither ExitPlanMode nor AskUserQuestion on modern CLIs, so the plan/question suites now run with interactive permissions like production. All 21 integration suites pass against the new mock.
25
+
8
26
  ## [1.22.0] - 2026-08-08
9
27
 
10
28
  ### Changed
package/dist/index.js CHANGED
@@ -55998,6 +55998,9 @@ function buildClaudeChildEnv(parentEnv, account, opts) {
55998
55998
  }
55999
55999
  return env;
56000
56000
  }
56001
+ function runtimeForScriptPath(scriptPath) {
56002
+ return scriptPath.endsWith(".ts") ? process.execPath : "node";
56003
+ }
56001
56004
  function isErrorResultEvent(event) {
56002
56005
  const ev = event;
56003
56006
  if (typeof ev.subtype === "string" && ev.subtype.startsWith("error"))
@@ -56060,7 +56063,7 @@ function buildPermissionArgs(opts) {
56060
56063
  mcpServers: {
56061
56064
  "claude-threads-mcp": {
56062
56065
  type: "stdio",
56063
- command: "node",
56066
+ command: runtimeForScriptPath(opts.mcpServerPath),
56064
56067
  args: [opts.mcpServerPath],
56065
56068
  env: mcpEnv
56066
56069
  }
@@ -56195,10 +56198,11 @@ class ClaudeCli extends EventEmitter2 {
56195
56198
  if (this.options.sessionId) {
56196
56199
  this.statusFilePath = join5(tmpdir(), `claude-threads-status-${this.options.sessionId}.json`);
56197
56200
  const statusLineWriterPath = this.getStatusLineWriterPath();
56201
+ const runtime = runtimeForScriptPath(statusLineWriterPath);
56198
56202
  const statusLineSettings = {
56199
56203
  statusLine: {
56200
56204
  type: "command",
56201
- command: `node ${statusLineWriterPath} ${this.options.sessionId}`,
56205
+ command: `${runtime} ${statusLineWriterPath} ${this.options.sessionId}`,
56202
56206
  padding: 0
56203
56207
  }
56204
56208
  };
@@ -56427,7 +56431,15 @@ class ClaudeCli extends EventEmitter2 {
56427
56431
  if (existsSync7(bundledPath)) {
56428
56432
  return bundledPath;
56429
56433
  }
56430
- return resolve3(__dirname4, "..", "mcp", "mcp-server.js");
56434
+ const sourceLayoutPath = resolve3(__dirname4, "..", "mcp", "mcp-server.js");
56435
+ if (existsSync7(sourceLayoutPath)) {
56436
+ return sourceLayoutPath;
56437
+ }
56438
+ const tsPath = resolve3(__dirname4, "..", "mcp", "mcp-server.ts");
56439
+ if (existsSync7(tsPath)) {
56440
+ return tsPath;
56441
+ }
56442
+ return sourceLayoutPath;
56431
56443
  }
56432
56444
  getStatusLineWriterPath() {
56433
56445
  const __filename2 = fileURLToPath3(import.meta.url);
@@ -56436,7 +56448,15 @@ class ClaudeCli extends EventEmitter2 {
56436
56448
  if (existsSync7(bundledPath)) {
56437
56449
  return bundledPath;
56438
56450
  }
56439
- return resolve3(__dirname4, "..", "statusline", "writer.js");
56451
+ const sourceLayoutPath = resolve3(__dirname4, "..", "statusline", "writer.js");
56452
+ if (existsSync7(sourceLayoutPath)) {
56453
+ return sourceLayoutPath;
56454
+ }
56455
+ const tsPath = resolve3(__dirname4, "..", "statusline", "writer.ts");
56456
+ if (existsSync7(tsPath)) {
56457
+ return tsPath;
56458
+ }
56459
+ return sourceLayoutPath;
56440
56460
  }
56441
56461
  }
56442
56462
 
@@ -68828,10 +68848,25 @@ function handleEventPreProcessing(session, event, ctx) {
68828
68848
  if (e.subtype === "status" && e.status === "compacting") {
68829
68849
  handleCompactionStart(session, ctx);
68830
68850
  }
68851
+ if (e.subtype === "status" && e.compact_result === "failed") {
68852
+ handleCompactionFailed(session, e.compact_error, ctx);
68853
+ }
68831
68854
  if (e.subtype === "compact_boundary") {
68832
68855
  handleCompactionComplete(session, e.compact_metadata, ctx);
68833
68856
  }
68834
68857
  }
68858
+ if (event.type === "auth_status") {
68859
+ const e = event;
68860
+ if (e.error) {
68861
+ sessionLog3(session).warn(`\uD83D\uDD10 Claude CLI auth error: ${e.error}`);
68862
+ if (session.lastAuthErrorPosted !== e.error) {
68863
+ session.lastAuthErrorPosted = e.error;
68864
+ withErrorHandling(() => post(session, "warning", `\uD83D\uDD10 Claude CLI authentication problem: ${e.error}`), { action: "Post auth status warning", session });
68865
+ }
68866
+ } else {
68867
+ sessionLog3(session).info(`\uD83D\uDD10 Claude CLI auth status: authenticating=${e.isAuthenticating ?? false}${e.output?.length ? ` (${e.output[e.output.length - 1]})` : ""}`);
68868
+ }
68869
+ }
68835
68870
  if (event.type === "assistant" && !isSidechainEvent(event)) {
68836
68871
  const msg = event.message;
68837
68872
  if (Array.isArray(msg?.content)) {
@@ -68888,21 +68923,46 @@ function handleEventPostProcessing(session, event, ctx) {
68888
68923
  }
68889
68924
  }
68890
68925
  }
68891
- async function handleCompactionStart(session, _ctx) {
68892
- await session.messageManager?.closeCurrentPost();
68926
+ function handleCompactionStart(session, _ctx) {
68927
+ session.compactionPostPromise = (async () => {
68928
+ await session.messageManager?.closeCurrentPost();
68929
+ const formatter = session.platform.getFormatter();
68930
+ const message = `\uD83D\uDDDC️ ${formatter.formatBold("Compacting context...")} ${formatter.formatItalic("(freeing up memory)")}`;
68931
+ const compactionPost = await withErrorHandling(() => post(session, "info", message), { action: "Post compaction start", session });
68932
+ if (compactionPost) {
68933
+ session.compactionPostId = compactionPost.id;
68934
+ }
68935
+ })();
68936
+ }
68937
+ async function awaitCompactionStartPost(session) {
68938
+ if (session.compactionPostPromise) {
68939
+ await session.compactionPostPromise.catch(() => {});
68940
+ session.compactionPostPromise = undefined;
68941
+ }
68942
+ }
68943
+ async function handleCompactionFailed(session, compactError, _ctx) {
68944
+ await awaitCompactionStartPost(session);
68893
68945
  const formatter = session.platform.getFormatter();
68894
- const message = `\uD83D\uDDDC️ ${formatter.formatBold("Compacting context...")} ${formatter.formatItalic("(freeing up memory)")}`;
68895
- const compactionPost = await withErrorHandling(() => post(session, "info", message), { action: "Post compaction start", session });
68896
- if (compactionPost) {
68897
- session.compactionPostId = compactionPost.id;
68946
+ const reason = compactError || "unknown error";
68947
+ const message = `⚠️ ${formatter.formatBold("Compaction failed")} ${formatter.formatItalic(`(${reason})`)}`;
68948
+ const startPostId = session.compactionPostId;
68949
+ if (startPostId) {
68950
+ await withErrorHandling(() => updatePost(session, startPostId, message), { action: "Update compaction post (failed)", session });
68951
+ session.compactionPostId = undefined;
68952
+ } else {
68953
+ await withErrorHandling(() => post(session, "info", message), { action: "Post compaction failure", session });
68898
68954
  }
68899
68955
  }
68900
68956
  async function handleCompactionComplete(session, compactMetadata, _ctx) {
68957
+ await awaitCompactionStartPost(session);
68901
68958
  const metadata = compactMetadata;
68902
68959
  const trigger = metadata?.trigger || "auto";
68903
68960
  const preTokens = metadata?.pre_tokens;
68961
+ const postTokens = metadata?.post_tokens;
68904
68962
  let info = trigger === "manual" ? "manual" : "auto";
68905
- if (preTokens && preTokens > 0) {
68963
+ if (preTokens && preTokens > 0 && postTokens && postTokens > 0) {
68964
+ info += `, ${Math.round(preTokens / 1000)}k → ${Math.max(1, Math.round(postTokens / 1000))}k tokens`;
68965
+ } else if (preTokens && preTokens > 0) {
68906
68966
  info += `, ${Math.round(preTokens / 1000)}k tokens`;
68907
68967
  }
68908
68968
  const formatter = session.platform.getFormatter();
@@ -56106,6 +56106,9 @@ function buildClaudeChildEnv(parentEnv, account, opts) {
56106
56106
  }
56107
56107
  return env;
56108
56108
  }
56109
+ function runtimeForScriptPath(scriptPath) {
56110
+ return scriptPath.endsWith(".ts") ? process.execPath : "node";
56111
+ }
56109
56112
  function isErrorResultEvent(event) {
56110
56113
  const ev = event;
56111
56114
  if (typeof ev.subtype === "string" && ev.subtype.startsWith("error"))
@@ -56168,7 +56171,7 @@ function buildPermissionArgs(opts) {
56168
56171
  mcpServers: {
56169
56172
  "claude-threads-mcp": {
56170
56173
  type: "stdio",
56171
- command: "node",
56174
+ command: runtimeForScriptPath(opts.mcpServerPath),
56172
56175
  args: [opts.mcpServerPath],
56173
56176
  env: mcpEnv
56174
56177
  }
@@ -56303,10 +56306,11 @@ class ClaudeCli extends EventEmitter2 {
56303
56306
  if (this.options.sessionId) {
56304
56307
  this.statusFilePath = join4(tmpdir2(), `claude-threads-status-${this.options.sessionId}.json`);
56305
56308
  const statusLineWriterPath = this.getStatusLineWriterPath();
56309
+ const runtime = runtimeForScriptPath(statusLineWriterPath);
56306
56310
  const statusLineSettings = {
56307
56311
  statusLine: {
56308
56312
  type: "command",
56309
- command: `node ${statusLineWriterPath} ${this.options.sessionId}`,
56313
+ command: `${runtime} ${statusLineWriterPath} ${this.options.sessionId}`,
56310
56314
  padding: 0
56311
56315
  }
56312
56316
  };
@@ -56535,7 +56539,15 @@ class ClaudeCli extends EventEmitter2 {
56535
56539
  if (existsSync4(bundledPath)) {
56536
56540
  return bundledPath;
56537
56541
  }
56538
- return resolve4(__dirname4, "..", "mcp", "mcp-server.js");
56542
+ const sourceLayoutPath = resolve4(__dirname4, "..", "mcp", "mcp-server.js");
56543
+ if (existsSync4(sourceLayoutPath)) {
56544
+ return sourceLayoutPath;
56545
+ }
56546
+ const tsPath = resolve4(__dirname4, "..", "mcp", "mcp-server.ts");
56547
+ if (existsSync4(tsPath)) {
56548
+ return tsPath;
56549
+ }
56550
+ return sourceLayoutPath;
56539
56551
  }
56540
56552
  getStatusLineWriterPath() {
56541
56553
  const __filename2 = fileURLToPath3(import.meta.url);
@@ -56544,7 +56556,15 @@ class ClaudeCli extends EventEmitter2 {
56544
56556
  if (existsSync4(bundledPath)) {
56545
56557
  return bundledPath;
56546
56558
  }
56547
- return resolve4(__dirname4, "..", "statusline", "writer.js");
56559
+ const sourceLayoutPath = resolve4(__dirname4, "..", "statusline", "writer.js");
56560
+ if (existsSync4(sourceLayoutPath)) {
56561
+ return sourceLayoutPath;
56562
+ }
56563
+ const tsPath = resolve4(__dirname4, "..", "statusline", "writer.ts");
56564
+ if (existsSync4(tsPath)) {
56565
+ return tsPath;
56566
+ }
56567
+ return sourceLayoutPath;
56548
56568
  }
56549
56569
  }
56550
56570
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "1.22.0",
3
+ "version": "1.23.0",
4
4
  "description": "Run Claude Code from Slack or Mattermost. Sessions stream live into threads where your whole team can watch and steer.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",