Replaces the single scoped `multica chat history --scope` read with two clean
noun-commands so the agent can navigate a channel with many threads (e.g. read
the specific thread a user referred to):
- `multica chat history` — the channel OVERVIEW: recent top-level messages, each
thread tagged with thread_id + reply_count + latest_reply (it does NOT expand
thread contents). Backed by GET /api/chat/history + slack.History.ChannelOverview
(conversations.history).
- `multica chat thread [id]` — read one thread: no id = the thread you're in,
an id = a specific thread IN THE SAME channel. Backed by GET /api/chat/thread +
slack.History.Thread (conversations.replies; DM falls back to history).
The channel stays server-pinned to the session; a thread id is only a
within-channel locator, so the security boundary (no cross-channel reads) is
unchanged. `--scope` is removed.
The prompt now teaches both commands and, via a new chat_in_thread signal
(derived from the binding: last_thread_id != last_message_id), tells the agent
which to start with — `chat history` for a top-level @mention, `chat thread` for
an in-thread one.
Tests: slack ChannelOverview/Thread (current/by-id/DM-fallback/no-binding/clamp),
handler both endpoints + auth, prompt top-level vs in-thread guidance.
Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
* feat(slack): add unified `multica chat history` pull for channel backfill (MUL-3871)
Agents @mentioned in a Slack thread/channel only saw the triggering message,
never the prior conversation (GitHub #4717). Instead of force-assembling a
recent-context block on every inbound (the Feishu approach), expose a single
channel-agnostic pull command the agent runs on demand.
- channel: normalized HistoryMessage/HistoryPage/HistoryOptions vocab so the
agent sees one shape regardless of platform.
- slack.History: resolves session -> binding -> installation -> bot token and
reads conversations.replies (real thread) or conversations.history (DM /
top-level channel, capturing sibling messages). thread_ts is recorded on the
binding config at session creation to pick the right call.
- handler GET /api/chat/history: authorized purely by the task-scoped token
(stamped X-Task-ID -> the task's own chat session), so an agent can only read
the conversation it is currently running for.
- multica chat history CLI command (no args; same for every channel).
- buildChatPrompt nudge so the agent discovers the command.
Feishu is intentionally untouched. Adding a platform = implement the reader.
Co-authored-by: multica-agent <github@multica.ai>
* fix(slack): require task-token actor source on chat history endpoint
Niko's review caught a privilege-boundary hole: the endpoint trusted
X-Task-ID, but it is mounted under the general Auth group where a normal
JWT / mul_ PAT request does NOT strip a client-forged X-Task-ID — only the
mat_ task-token branch stamps it. A workspace member who knew a chat task id
could forge the header and read that task's Slack channel/DM/thread history.
Gate on the server-set X-Actor-Source == "task_token" (the Auth middleware
deletes any client-supplied value and re-stamps it only on the mat_ branch),
then trust X-Task-ID. Adds a regression test: a forged X-Task-ID without the
task-token actor source is rejected with 403 and never reaches the reader.
Co-authored-by: multica-agent <github@multica.ai>
* fix(slack): thread-first history for follow-ups, channel for first turn (MUL-3871)
A Slack conversation has two nested histories: the surrounding channel and the
agent's own thread (the bot's first reply opens a thread on the @mention). The
first version picked replies-vs-history from a thread_ts fixed at session
creation, so a session started by a top-level @mention always read CHANNEL
history — even on follow-ups inside the bot's thread, which should read THREAD
history first.
- Add a HistoryScope (auto|thread|channel). The handler resolves auto:
first turn (no prior bot reply) -> channel; follow-up -> thread. The agent can
override with --scope channel|thread, and the response reports the scope read.
- The thread root is derived from the binding (last_thread_id / composite-key
suffix), available for every engaged group session, instead of the
creation-time thread_ts (now removed from the binding config).
- A DM degrades a thread request to channel history (DMs have no threads).
- Prompt guidance + CLI help updated to explain the policy.
Tests: scope selection (thread/channel/DM-fallback/no-root), root derivation,
and handler auto-resolution (first->channel, follow-up->thread, explicit
override).
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>