claude-threads 1.32.1 → 1.33.1
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 +15 -0
- package/dist/index.js +837 -760
- package/dist/mcp/mcp-server.js +746 -265
- package/docs/CONFIGURATION.md +8 -0
- package/docs/app-user-posts-spec.md +79 -0
- package/package.json +3 -3
package/docs/CONFIGURATION.md
CHANGED
|
@@ -554,6 +554,14 @@ claudeAccounts:
|
|
|
554
554
|
| `apiKey` | One of | Anthropic API key. Billed against that key; session history stays under the bot's default `HOME`. |
|
|
555
555
|
| `displayName` | No | Human-readable label in UI (defaults to `id`) |
|
|
556
556
|
|
|
557
|
+
⚠️ An account with `home` owns the **whole** Claude profile, not just its
|
|
558
|
+
credentials: user settings, hooks and global MCP configuration are read from
|
|
559
|
+
that home too. Anything the daemon inherited that could point elsewhere — an
|
|
560
|
+
API key, a bearer token, `CLAUDE_CONFIG_DIR` — is cleared for that child, or
|
|
561
|
+
the account you selected would be silently overridden by the one the daemon
|
|
562
|
+
runs as. Daemon-level hooks and settings therefore do **not** carry into a
|
|
563
|
+
pooled account; put them in the account's own home.
|
|
564
|
+
|
|
557
565
|
Exactly one of `home` or `apiKey` should be set per account. Persisted sessions record which account they ran under and resume on the same one.
|
|
558
566
|
|
|
559
567
|
## Environment Variables
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Posts made through the app's own user token are the person's messages
|
|
2
|
+
|
|
3
|
+
Upstream discussion: anneschuth/claude-threads#526.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
A message posted with `chat.postMessage` and a **user token of this app**
|
|
8
|
+
(`xoxp-…`, obtained when a person authorises the app) shows up in Slack as
|
|
9
|
+
that person, and Slack delivers it with `user` = the person **and**
|
|
10
|
+
`bot_id` + `app_id` of the app. Today the Slack client drops every message
|
|
11
|
+
that carries a `bot_id`, so such a post never reaches Claude, silently.
|
|
12
|
+
|
|
13
|
+
With this change the client treats a message as bot-authored only when
|
|
14
|
+
|
|
15
|
+
- its `user` is the bot's own user (the daemon's replies), or
|
|
16
|
+
- it carries a `bot_id` and is **not** a post from this app, from this
|
|
17
|
+
workspace, with a `user`.
|
|
18
|
+
|
|
19
|
+
So a post relayed through this app's own user token is handled exactly like
|
|
20
|
+
the person typing it: same session routing, same allow-list, same everything.
|
|
21
|
+
Other apps' bot posts, classic bot posts without a `user`, the daemon's own
|
|
22
|
+
replies, and a bot copy of this same app installed in another workspace that
|
|
23
|
+
shares a Slack Connect channel (same `app_id`, other `team`) stay ignored,
|
|
24
|
+
as before.
|
|
25
|
+
|
|
26
|
+
The rule is applied in the three places the filter lives: live events,
|
|
27
|
+
missed-message recovery after a reconnect, and thread history with
|
|
28
|
+
`excludeBotMessages`.
|
|
29
|
+
|
|
30
|
+
## Why
|
|
31
|
+
|
|
32
|
+
Any integration that speaks *for* a person — a voice front desk, a phone
|
|
33
|
+
bridge, an approval UI — posts on the person's behalf with the app's user
|
|
34
|
+
token, because that is the only way to keep attribution honest and the bot's
|
|
35
|
+
consent gate (channel membership, allow-lists) intact. Slack marks every
|
|
36
|
+
API-posted message with the app's `bot_id`; there is no way to post a
|
|
37
|
+
"pure" human message through the API. Without this exception the daemon
|
|
38
|
+
can never be driven by such an integration.
|
|
39
|
+
|
|
40
|
+
Origin: the VVS live voice desk (`voice/` on the `local/voice-desk` branch
|
|
41
|
+
of the kaza fork) relayed a spoken question into a task channel as the
|
|
42
|
+
speaker, and the daemon ACKed the event and dropped it at the `bot_id`
|
|
43
|
+
check with no log line. Seen 2026-09-02, message
|
|
44
|
+
`{user: U…, bot_id: B0BUDMNPD26, app_id: A0BSH7L7N9E, bot_profile: {…}}`.
|
|
45
|
+
|
|
46
|
+
## How
|
|
47
|
+
|
|
48
|
+
- The client learns its workspace from `auth.test` (`team_id`, already
|
|
49
|
+
called for the bot user) and its own app id from the Socket Mode `hello`
|
|
50
|
+
(`connection_info.app_id`) and, belt and braces, from every `events_api`
|
|
51
|
+
envelope (`payload.api_app_id`). Secondary clients on a shared socket get
|
|
52
|
+
it handed over with each injected event. No new config, no new API call.
|
|
53
|
+
- One predicate, `isBotAuthored(message)`, replaces the three inline
|
|
54
|
+
`user === botUserId || bot_id` checks.
|
|
55
|
+
- `app_id` is added to the `SlackEvent` and `SlackMessage` types; it was
|
|
56
|
+
already on the wire.
|
|
57
|
+
- Until app and team ids are known (only possible before the first `hello`), the
|
|
58
|
+
old rule applies: anything with a `bot_id` is ignored. Conservative, and
|
|
59
|
+
the window is the connection handshake.
|
|
60
|
+
|
|
61
|
+
Why `app_id` and not `bot_id`: the `bot_id` on a user-token post is a
|
|
62
|
+
per-authorisation bot profile (`B0BUDMNPD26` above), different from the
|
|
63
|
+
bot token's own `bot_id` from `auth.test` (`B0BSH828Z6U`). `app_id` is the
|
|
64
|
+
one stable identity.
|
|
65
|
+
|
|
66
|
+
## Decisions
|
|
67
|
+
|
|
68
|
+
| Decision | Why |
|
|
69
|
+
|---|---|
|
|
70
|
+
| No config flag | with `app_id` and `team` checked, only this installation's own user tokens qualify; nothing to opt out of |
|
|
71
|
+
| `team` must match too (Codex review) | Slack Connect lets the same app be installed on both sides of a shared channel; the other copy's bot shares our `app_id` but not our `team`, and two daemons answering each other is the one loop this must never open |
|
|
72
|
+
| Learn the app id from the socket, not config | zero setup; every install already receives it |
|
|
73
|
+
| Old behaviour until the app id is known | never widen the filter on a guess |
|
|
74
|
+
|
|
75
|
+
## Lessons learned
|
|
76
|
+
|
|
77
|
+
- A dropped event at the "ignore ourselves" check leaves no trace in the
|
|
78
|
+
log. Reproducing needed `conversations.history` on the exact `ts` to see
|
|
79
|
+
the stored message's fields.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-threads",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.1",
|
|
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",
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"flatted": ">=3.4.0",
|
|
123
123
|
"picomatch": ">=2.3.2",
|
|
124
124
|
"path-to-regexp": ">=8.4.0",
|
|
125
|
-
"fast-uri": ">=4.1.
|
|
125
|
+
"fast-uri": ">=4.1.4",
|
|
126
126
|
"brace-expansion": ">=5.0.9",
|
|
127
127
|
"ip-address": ">=10.3.1",
|
|
128
128
|
"ws": "$ws",
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
"flatted": ">=3.4.0",
|
|
136
136
|
"picomatch": ">=2.3.2",
|
|
137
137
|
"path-to-regexp": ">=8.4.0",
|
|
138
|
-
"fast-uri": ">=4.1.
|
|
138
|
+
"fast-uri": ">=4.1.4",
|
|
139
139
|
"brace-expansion": ">=5.0.9",
|
|
140
140
|
"ip-address": ">=10.3.1",
|
|
141
141
|
"ws": "$ws",
|