claude-threads 1.27.0 → 1.29.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 +32 -2
- package/README.md +3 -0
- package/dist/index.js +2046 -1015
- package/dist/mcp/mcp-server.js +589 -329
- package/docs/CONFIGURATION.md +109 -0
- package/package.json +1 -1
package/docs/CONFIGURATION.md
CHANGED
|
@@ -131,6 +131,8 @@ stickyMessage:
|
|
|
131
131
|
| `stickyMessage` | No | Channel sticky visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no sticky, no bumping) |
|
|
132
132
|
| `directChannelMode` | No | Direct channel mode: the whole channel is one session, and the bot replies with top-level channel posts instead of thread replies. `true` for defaults, or an options object (`respondTo`). See [Direct Channel Mode](#direct-channel-mode). |
|
|
133
133
|
| `approvals` | No | Who may answer tool-permission prompts and other reaction gates: `owner` (session participants) or `all_users` (everyone on `allowedUsers`). Unset keeps the historical default per mode — `all_users` for thread sessions, `owner` for direct channel mode. See [Approvals](#approvals). |
|
|
134
|
+
| `ackReaction` | No | Read receipt: react to every accepted message (session start, follow-up, resume) the instant it is accepted, before Claude produces output. `true` uses 👀 (`eyes`), a string names a custom emoji. Persistent, unlike the typing indicator — useful in busy channels and for messages queued behind an in-flight session start. The receipt means *accepted*, not *delivered*: a later failure (capacity limit, Claude not coming up) is still reported by its own post. `!commands` are not acked — they have their own immediate feedback, and neither are messages accepted through the message-approval flow (an authorized user approving a non-participant's message) — there the approval reaction is already the visible signal. Note: in direct channel mode this is one reaction API call per accepted message. Default off. |
|
|
135
|
+
| `auditLog` | No | Append-only audit trail of what the bot executed for this platform — tool calls (incl. subagents), session lifecycle, security-relevant commands, plan approvals. One JSONL stream per platform under `~/.claude-threads/audit/` (override: `CLAUDE_THREADS_AUDIT_DIR`), files `0600`. The bot never deletes it — rotation/retention is the operator's job (logrotate, SIEM ingestion). See [Audit log](#audit-log). Default off. |
|
|
134
136
|
| `directMessages` | No | Mattermost only: DM auto-discovery. A direct message from a user on `allowedUsers` spawns a derived direct-channel-mode instance for that DM conversation — no per-DM entry needed. See [DM auto-discovery](#dm-auto-discovery). |
|
|
135
137
|
|
|
136
138
|
### Slack
|
|
@@ -152,6 +154,8 @@ stickyMessage:
|
|
|
152
154
|
| `stickyMessage` | No | Channel sticky visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no sticky, no bumping) |
|
|
153
155
|
| `directChannelMode` | No | Direct channel mode: the whole channel is one session, and the bot replies with top-level channel posts instead of thread replies. `true` for defaults, or an options object (`respondTo`). See [Direct Channel Mode](#direct-channel-mode). |
|
|
154
156
|
| `approvals` | No | Who may answer tool-permission prompts and other reaction gates: `owner` (session participants) or `all_users` (everyone on `allowedUsers`). Unset keeps the historical default per mode — `all_users` for thread sessions, `owner` for direct channel mode. See [Approvals](#approvals). |
|
|
157
|
+
| `ackReaction` | No | Read receipt: react to every accepted message (session start, follow-up, resume) the instant it is accepted, before Claude produces output. `true` uses 👀 (`eyes`), a string names a custom emoji. Persistent, unlike the typing indicator — useful in busy channels and for messages queued behind an in-flight session start. The receipt means *accepted*, not *delivered*: a later failure (capacity limit, Claude not coming up) is still reported by its own post. `!commands` are not acked — they have their own immediate feedback, and neither are messages accepted through the message-approval flow (an authorized user approving a non-participant's message) — there the approval reaction is already the visible signal. Note: in direct channel mode this is one reaction API call per accepted message. Default off. |
|
|
158
|
+
| `auditLog` | No | Append-only audit trail of what the bot executed for this platform — tool calls (incl. subagents), session lifecycle, security-relevant commands, plan approvals. One JSONL stream per platform under `~/.claude-threads/audit/` (override: `CLAUDE_THREADS_AUDIT_DIR`), files `0600`. The bot never deletes it — rotation/retention is the operator's job (logrotate, SIEM ingestion). See [Audit log](#audit-log). Default off. |
|
|
155
159
|
|
|
156
160
|
### Direct Channel Mode
|
|
157
161
|
|
|
@@ -190,6 +194,22 @@ Under effective `owner` mode the scoping is enforced consistently across every p
|
|
|
190
194
|
|
|
191
195
|
The approval set is fixed when the Claude CLI is spawned; a later `!invite` extends message access immediately but reaches the approval set on the next CLI respawn (e.g. via `!cd` or `!permissions`).
|
|
192
196
|
|
|
197
|
+
### Audit log
|
|
198
|
+
|
|
199
|
+
`auditLog: true` writes an append-only JSONL stream per platform to `~/.claude-threads/audit/<platformId>.jsonl` (override the directory with `CLAUDE_THREADS_AUDIT_DIR`). One line per event:
|
|
200
|
+
|
|
201
|
+
- `tool_use` — every tool call Claude **issued** (including `server_tool_use` blocks), with the audit-relevant detail (Bash command line, file path, search pattern); subagent sidechain calls are included and marked `subagent: true`. Note the semantics: the audit records the *request* at the moment Claude emits it — an interactive permission denial can still stop the execution, and the denied attempt is exactly what an auditor wants to see.
|
|
202
|
+
- `session_start` / `session_resume` / `session_end` — lifecycle with the triggering user.
|
|
203
|
+
- `command` — security-relevant `!commands` with actor: `!cd`, `!invite`, `!kick`, `!permissions`, `!stop` (active and paused sessions), `!kill`, `!memory forget`, `!routines` management, routine creation, `!worktree remove`, `!plugin install`/`uninstall`.
|
|
204
|
+
- `plan_approval` — plan approved/denied, by whom, via reaction or `!approve`.
|
|
205
|
+
|
|
206
|
+
Notes for operators:
|
|
207
|
+
|
|
208
|
+
- **The bot never deletes audit files.** An audit trail that expires itself is not one — rotation and retention are yours (logrotate, or let your SIEM's file collector ingest and rotate). The writer holds the file descriptor open across writes, so use **`copytruncate`** (or restart the bot after rotating): a rename-based rotate never errors the cached fd, and the bot would keep appending to the rotated file until restart.
|
|
209
|
+
- **Entries contain command lines verbatim** (that is the point); files are `0600` in a `0700` directory — enforced on every start, including pre-existing files, and the writer refuses symlinked audit paths. Treat the directory with the same care as the thread logs.
|
|
210
|
+
- **Actor attribution is best effort**: the username whose (authorized) message triggered the current turn — resumes are attributed to the resuming user — falling back to the session starter. In fast multi-user threads a tool call can be attributed to the previous sender.
|
|
211
|
+
- **Tool-permission decisions (allow/deny of individual tool calls) are not recorded** — they are resolved inside the MCP permission server subprocess, which the bot process does not observe. Plan approvals and the audited commands cover the decisions that flow through the bot itself.
|
|
212
|
+
|
|
193
213
|
### Direct messages (DM)
|
|
194
214
|
|
|
195
215
|
**Mattermost only.** A Mattermost DM is just a private channel with its own id, so a bot DM conversation is direct channel mode pointed at that id — no separate feature needed. (This recipe does NOT work on Slack: Socket Mode distributes event envelopes across an app's active connections, so a second platform entry sharing the same app credentials can consume and discard events meant for the other entry. Slack DM support needs a single-connection, channel-aware implementation.)
|
|
@@ -389,6 +409,95 @@ confirmation says so.
|
|
|
389
409
|
bot-process-credentials caveat as memory distillation applies in OAuth
|
|
390
410
|
account pools.
|
|
391
411
|
|
|
412
|
+
### Watches (`watches`, default: enabled)
|
|
413
|
+
|
|
414
|
+
Event triggers, Claude Tag-style proactiveness: a watch fires when a
|
|
415
|
+
**matching message appears in the channel** — the bot starts a session **in
|
|
416
|
+
the triggering message's own thread** (as the watch's creator) and works the
|
|
417
|
+
task right where the event happened. Other thread participants reach the
|
|
418
|
+
session through the normal message-approval flow.
|
|
419
|
+
|
|
420
|
+
```yaml
|
|
421
|
+
platforms:
|
|
422
|
+
- id: mattermost-main
|
|
423
|
+
type: mattermost
|
|
424
|
+
# ... credentials ...
|
|
425
|
+
watches: true # default; `false` disables evaluation + commands
|
|
426
|
+
|
|
427
|
+
limits:
|
|
428
|
+
maxWatches: 10 # per-platform cap (default 10)
|
|
429
|
+
watchCooldownMinutes: 5 # min minutes between fires of one watch (default 5)
|
|
430
|
+
watchDailyCap: 20 # max fires per watch per day (default 20)
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**Creating** (natural language, confirmed before saving):
|
|
434
|
+
|
|
435
|
+
```
|
|
436
|
+
!watch when someone reports a production incident, triage it and post a checklist
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
A haiku pass splits the request into a matching **condition**, a **task**,
|
|
440
|
+
and a set of lowercase **prefilter keywords** (with synonyms and — for
|
|
441
|
+
non-English requests — terms in both languages). The confirmation card shows
|
|
442
|
+
all three, and **nothing is saved until someone reacts 👍**.
|
|
443
|
+
|
|
444
|
+
**Matching is two-stage** to keep chatty channels free:
|
|
445
|
+
|
|
446
|
+
1. Every channel message the bot would otherwise ignore is screened against
|
|
447
|
+
the keywords locally (zero cost). No keyword hit → nothing happens.
|
|
448
|
+
2. A keyword hit gets **one haiku call** that semantically confirms the
|
|
449
|
+
message against the condition ("a link to last year's incident postmortem"
|
|
450
|
+
does not fire an incident watch). A keyword hit alone never fires;
|
|
451
|
+
a failed or ambiguous confirmation never fires (fail-closed).
|
|
452
|
+
|
|
453
|
+
**Managing:**
|
|
454
|
+
|
|
455
|
+
- `!watches` — numbered list with condition, creator, and last-fire status
|
|
456
|
+
- `!watches pause|resume|delete <n>` — owner-gated
|
|
457
|
+
- (No manual `run` — watches are event-driven; use `!routines run` for
|
|
458
|
+
on-demand work.)
|
|
459
|
+
|
|
460
|
+
**Semantics & guardrails:**
|
|
461
|
+
|
|
462
|
+
- Fires run **as their creator** and are re-authorized on every fire — a
|
|
463
|
+
creator who loses platform authorization disables the watch (with a
|
|
464
|
+
channel notice).
|
|
465
|
+
- Per-watch cooldown (default 5 min) and daily cap (default 20 fires/day);
|
|
466
|
+
at most one watch fires per message; fires count against `MAX_SESSIONS`.
|
|
467
|
+
- 3 consecutive failed fires auto-disable the watch with a channel notice;
|
|
468
|
+
`!watches resume <n>` re-arms it.
|
|
469
|
+
- Messages inside active or paused session threads never trigger watches
|
|
470
|
+
(loop prevention), and the bot's own posts are filtered before evaluation.
|
|
471
|
+
- Platform note: on **Mattermost**, messages from *other* bots (CI alerts,
|
|
472
|
+
webhook integrations) can trigger watches — useful for "watch the CI bot".
|
|
473
|
+
On **Slack**, the client filters all bot events, so only human messages
|
|
474
|
+
trigger.
|
|
475
|
+
- The fired session auto-includes the triggering thread's recent messages as
|
|
476
|
+
context (it is the event being responded to) — no interactive context
|
|
477
|
+
prompt to stall on.
|
|
478
|
+
- **Each fire starts a full Claude session on your subscription** — the
|
|
479
|
+
confirmation and `!watches` listing both say so.
|
|
480
|
+
- Watches are scoped per platform instance (same privacy boundary as memory)
|
|
481
|
+
and stored at `~/.config/claude-threads/watches.yaml` (0600; override with
|
|
482
|
+
`CLAUDE_THREADS_WATCHES_PATH`).
|
|
483
|
+
- The parse and each match confirmation use one haiku `claude -p` call — the
|
|
484
|
+
same bot-process-credentials caveat as memory distillation applies in
|
|
485
|
+
OAuth account pools.
|
|
486
|
+
- Watches are not available in **direct channel mode** — a DCM channel routes
|
|
487
|
+
every message to the one channel session, so there is no "otherwise
|
|
488
|
+
ignored" traffic to evaluate; `!watch` refuses with an explanation.
|
|
489
|
+
|
|
490
|
+
**Security note — who can trigger a fire:** the *creator* must be authorized,
|
|
491
|
+
but the *triggering message* can come from **any channel member** (that is the
|
|
492
|
+
point: incident reporters and CI bots are usually not on `allowedUsers`). The
|
|
493
|
+
channel membership is the trust boundary. The triggering content is framed as
|
|
494
|
+
data — the confirm prompt classifies it without following instructions inside
|
|
495
|
+
it, and the fired session's prompt marks the thread as context, not
|
|
496
|
+
instructions — but framing is a mitigation, not authorization. Treat a watch
|
|
497
|
+
in a channel with untrusted members accordingly, and be especially deliberate
|
|
498
|
+
about combining watches with `skipPermissions: true`, which lets the fired
|
|
499
|
+
session act without human tool approval.
|
|
500
|
+
|
|
392
501
|
## Claude Accounts (optional, multi-account mode)
|
|
393
502
|
|
|
394
503
|
By default every session spawns `claude` with the bot's own `process.env`, so they all share one subscription's token budget. Add a `claudeAccounts` block to spread load across multiple accounts. Omit the block entirely to stay in single-account mode (unchanged behavior).
|
package/package.json
CHANGED