3854 Commits

Author SHA1 Message Date
Multica Eve
9340454558 docs(changelog): add v0.3.34 entry for the 2026-07-01 release (MUL-3918) (#4794)
* docs(changelog): add v0.3.34 entry for the 2026-07-01 release (MUL-3918)

Co-authored-by: multica-agent <github@multica.ai>

* docs(changelog): tighten v0.3.34 bullets to one sentence each (MUL-3918)

Co-authored-by: multica-agent <github@multica.ai>

* docs(changelog): drop self-host anonymous source-channel feature bullet from v0.3.34 (MUL-3918)

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
v0.3.34
2026-07-01 18:02:35 +08:00
Naiyuan Qing
7b3cad664b revert(self-host): remove source channel reporting (#4799)
* Revert "test(onboarding): cover official source reporting controls (#4782)"

This reverts commit fc88c7720f.

* Revert "fix(self-host): restore official source report endpoint (#4781)"

This reverts commit ad1afdd48d.

* Revert "feat(self-host): collect anonymous source channels mul-3878 (#4741)"

This reverts commit 26142d74aa.
2026-07-01 17:53:16 +08:00
Bohan Jiang
159d9bebb1 feat(slack): route /issue slash command through quick-create (MUL-3908) (#4793)
The Slack `/issue` slash command used to directly create a raw issue: the
typed line became the title verbatim and a `todo` issue was assigned to the
agent to work on immediately. That files a rough, unstructured issue and starts
the agent on it before it is well-formed.

Switch the slash command to the quick-create pipeline instead
(TaskService.EnqueueQuickCreateTask, the same path as the web "quick create"
modal): the invoker's natural-language description is handed to the
installation's agent as a prompt, and the agent authors a well-formed issue
(proper title + structured description) in the background, attributed to the
bound member. Because creation is now asynchronous, the ephemeral reply is an
acknowledgement ("On it…") rather than a created-confirmation with a number;
the agent's completion surfaces to the invoker as a Multica inbox notification
through the shared quick-create completion path.

Installation routing and identity/membership checks are unchanged, so the same
workspace boundary and account-binding rules apply. Scope is the slash command
only — the message-based `@bot /issue` still runs through the shared
cross-platform engine (which also serves Lark) and keeps its direct-create
behavior.

- slash_command.go: swap IssueService.Create for EnqueueQuickCreateTask via a
  narrow quickCreateEnqueuer interface; prompt is the full text (no title/body
  split); drop the now-unused splitIssueText / issueCreatedText / GetWorkspace.
- router.go: wire h.TaskService instead of h.IssueService.
- tests: cover enqueue + ack, multiline prompt pass-through, empty prompt,
  unbound, non-member, inactive, team mismatch, and enqueue-failure.
- docs (4 locales): describe the quick-create behavior.

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-01 17:32:42 +08:00
Bohan Jiang
247e8ed5ab feat(slack): reuse account link across apps in one Slack workspace (#4786)
A Slack user who linked their Multica identity to one bot was re-prompted
to link again when messaging a second bot (a different Slack app) in the
SAME Slack workspace, because bindings are keyed per-installation
(installation_id, channel_user_id).

On an unbound inbound, the identity resolver now looks up an existing
binding for the same (Multica workspace, Slack team, Slack user) via the
new FindReusableChannelUserBinding query and, if that user is still a
workspace member, materializes a binding for the new installation instead
of returning ErrSenderUnbound — so the second bot resolves the user
silently. Reuse is fenced to one Multica workspace AND one Slack team, so
it never crosses either boundary; legacy installs with no recorded team
never reuse.

Adds resolver unit tests for every decision path and updates the Slack
integration docs (en/zh/ja/ko).

MUL-3911

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-01 16:58:23 +08:00
Naiyuan Qing
21d82b2ae5 fix(editor): upgrade tiptap inline code handling (#4790) 2026-07-01 16:55:43 +08:00
Naiyuan Qing
a27f828278 fix(issues): make comment highlight background-only (#4789) 2026-07-01 16:06:47 +08:00
Bohan Jiang
bbd900635e fix(slack): allow slack_chat issue origin so /issue can create issues (MUL-3908) (#4785)
Follow-up to #4780 (the /issue slash command), which merged the code + docs. The DB migration was pushed to that branch after it had already been squash-merged, so it never landed on main — the /issue command still fails at issue creation.

The issue.origin_type CHECK constraint (migration 111) only allowed autopilot / quick_create / lark_chat. Slack stamps origin_type='slack_chat' on every /issue create, so the INSERT trips SQLSTATE 23514 and IssueService.Create fails ("Something went wrong creating the issue"). This also silently broke the pre-existing message-based Slack /issue. Extends the constraint to include 'slack_chat', mirroring 111 (lark_chat). Numbered 131 because 129/130 were taken on main since #4780 branched.

Verified against a real Postgres: full chain up to 131 applies cleanly and an issue insert with origin_type='slack_chat' passes the CHECK after 131 (fails with the pre-131 constraint).

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-01 15:20:22 +08:00
bhirstmedia
170750242b BHI-12314: add Claude Sonnet 5 catalog and pricing support (MUL-3910) (#4783)
Co-authored-by: Ember <ember@Embers-iMac.localdomain>
2026-07-01 14:59:00 +08:00
Bohan Jiang
240ec4efd0 feat(slack): native /issue slash command over Socket Mode (MUL-3908) (#4780)
* feat(slack): native /issue slash command over Socket Mode (MUL-3908)

A message beginning with `/issue` is intercepted by the Slack client as a slash command and never delivered to the app, so the message-prefix /issue never worked on Slack (no event, no 👀, no issue).

Register /issue as a real slash command in the app manifest and handle EventTypeSlashCommand over the existing per-installation Socket Mode connection. It is a one-shot issue creation (no chat session / agent run) that reuses the shared IssueService and the same installation-routing + identity/membership checks as the message path, replying privately via the command's response_url (ephemeral) since a slash command has no message to react to.

Docs: register the command in the manifest and describe the slash-command behavior across all four locales.
Co-authored-by: multica-agent <github@multica.ai>

* docs(slack): add commands scope to /issue manifest; fix chat-run wording (MUL-3908)

Review follow-up: the manifest examples registered features.slash_commands but omitted the commands bot scope, so updating + reinstalling could still fail to grant the /issue command. Add - commands to oauth_config.scopes.bot in all four locales and document it in the permissions table.

Also correct the misleading "no agent run" wording in the slash-command header and router comment: a todo issue assigned to the agent still triggers it via maybeEnqueueOnAssign (issue-assignment), like the message /issue — the slash command only skips the chat session / chat run.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-01 14:47:44 +08:00
Naiyuan Qing
fc88c7720f test(onboarding): cover official source reporting controls (#4782) 2026-07-01 14:35:57 +08:00
Naiyuan Qing
ad1afdd48d fix(self-host): restore official source report endpoint (#4781) 2026-07-01 14:11:34 +08:00
Naiyuan Qing
26142d74aa feat(self-host): collect anonymous source channels mul-3878 (#4741)
* feat(self-host): collect anonymous source channels

* feat(self-host): include other source text

* feat(self-host): report source channel domains

* feat(self-host): add source domain reporting control

* fix(self-host): simplify source reporting copy

* fix(self-host): simplify source channel reporting gate

* fix(self-host): limit source reporting triggers

* chore(self-host): point source reporting at staging
2026-07-01 13:31:25 +08:00
Bohan Jiang
dbf11f0958 fix(attachments): relax frame-ancestors on local /uploads static route (MUL-3821) (#4777)
Self-hosted local-disk deployments serve document previews straight from
the public /uploads/* static route. That route inherited the global
`frame-ancestors 'none'` CSP from the middleware, so iframe-based previews
(PDF/HTML) were blocked by the browser — only the /api/attachments/*
download endpoint had been exempted (#4635 / #4679).

Serve /uploads/* through a new Handler.ServeLocalUpload that applies the
same preview security headers as the download endpoint
(setAttachmentPreviewSecurityHeaders), so the relaxed, config-aware
`frame-ancestors 'self' <configured origins>` policy applies to both
same-origin and split frontend/backend origin setups. Inline <img>
rendering is unaffected (frame-ancestors does not gate images); cloud
storage (S3/CloudFront) never hits this route.

Adds regression tests covering the relaxed CSP on /uploads and the
non-local-storage 404 guard.

Refs #4477

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-01 13:28:44 +08:00
Bohan Jiang
09bb3483f9 docs: document Trae CLI runtime and align provider tool counts (MUL-3860) (#4778)
Follow-up to #4724, which added the Trae CLI (traecli) ACP backend but
left the surrounding docs behind.

- install-agent-runtime: add a Trae CLI section (install, ACP transport,
  enterprise login, inline runtime brief, MULTICA_TRAECLI_MODEL)
- providers: fix the MCP paragraph — Trae also receives ACP mcpServers
- daemon-runtimes: add Qoder + Trae CLI to the built-in detection list
- README: add Trae CLI to the architecture diagram and runtime row
- bump stale English tool counts (12/13 -> 14) across cross-references;
  the '12' lists were already missing Qoder before this change

Scope: English docs only. The ja/zh localizations are separately behind
(they predate Qoder too) and need their own translation-sync pass.

Co-authored-by: J <agent-j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-01 13:27:00 +08:00
Multica Eve
f0c3b0911c MUL-3828: fix Cursor and Kiro runtime completion transcripts (#4738)
* fix: preserve cursor and kiro completion transcripts

Co-authored-by: multica-agent <github@multica.ai>

* fix(kiro): tolerate env-prefix and sh -c wrapper in comment-add detection

Harden isKiroIssueCommentAddCommand so the completion-preservation guard
also recognizes 'VAR=x multica issue comment add ...' and
'sh -c "multica issue comment add ..."' invocations. Addresses review nit
on MUL-3828 PR #4738.

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: J <bhjiang@outlook.com>
2026-07-01 13:23:12 +08:00
Xisheng Parker Zhao
4b9ea4aa68 feat(agent): add ByteDance TRAE CLI (traecli) as an ACP backend (#4724)
Adds the official ByteDance TRAE CLI (the `traecli` binary documented at
https://docs.trae.cn/cli — the product paired with the Trae IDE, not the
open-source bytedance/trae-agent) as a built-in agent backend. traecli is
ACP-native, so it is driven over the standard ACP JSON-RPC transport via
`traecli acp serve --yolo`, reusing the shared hermesClient exactly like the
Kiro and Qoder backends.

Validated end-to-end against the real traecli v0.120.42 with a logged-in
account: initialize advertises loadSession:true + mcpCapabilities{http,sse};
session/new returns result.sessionId + models.availableModels (18 models
discovered); session/prompt streams session/update notifications with
sessionUpdate=agent_message_chunk (hermesClient already normalizes this Zed-ACP
wire shape); a real board task ran 14 tool calls and completed in ~47s.

Implementation:
- server/pkg/agent/traecli.go: ACP backend; session/load resume
  (loadSession:true), session/set_model, MCP via ACP mcpServers, --yolo
  bypass-permissions for headless runs, blocked-arg filtering (acp, serve,
  --yolo, --print, --output-format, --permission-mode)
- agent.go: New() + launch header "traecli acp serve"
- models.go: discoverTraecliModels via the shared discoverACPModels
- daemon/config.go: auto-detect the `traecli` binary
  (MULTICA_TRAECLI_PATH / MULTICA_TRAECLI_MODEL)
- daemon.go: inline the runtime brief (traecli reads .trae/rules/, not
  AGENTS.md) and surface the runtime as "Trae" (providerDisplayName)
- execenv: AGENTS.md + .traecli/skills wiring; ~/.traecli/skills local root
- packages/core mcp-support: traecli consumes mcp_config
- frontend: official Trae provider logo
- docs: providers.mdx matrix + section, CLI_AND_DAEMON.md, README

Tests: fake-ACP unit tests matching the real wire format (streaming,
blocked-arg filtering, session/set_model failure, session/load resume) plus a
gated real-binary smoke test (TestTraecliRealACPSmoke) that skips when traecli
is absent or not logged in. Built-in provider only (mirrors qoder): not in
SupportedTypes / RUNTIME_PROFILE_PROTOCOL_FAMILIES, so no migration is needed.

Resolves #4376.
2026-07-01 13:19:06 +08:00
Bohan Jiang
444c2f29a5 fix(slack): stop the chat agent narrating its history reads (MUL-3871) (#4776)
Every Slack reply was prefixed with process narration like '我先读取 Slack 频道概览,
再打开相关线程…' before the actual answer — the model announcing the history reads
the channel-awareness prompt tells it to do. That narration is internal
context-gathering, not part of the answer.

Add an instruction to the channel-awareness block: do the reads silently and
reply with the answer only, no preamble about what it is about to read or just
read.

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-01 13:02:45 +08:00
Bohan Jiang
3a6d3522c8 feat(slack): two-command channel reads — chat history (overview) + chat thread [id] (MUL-3871) (#4762)
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>
2026-07-01 12:46:47 +08:00
Bohan Jiang
a0735f70c8 fix(selfhost): pass MULTICA_SLACK_SECRET_KEY through to backend container (MUL-3897) (#4768)
The backend service environment block in docker-compose.selfhost.yml
forwarded MULTICA_LARK_SECRET_KEY but omitted MULTICA_SLACK_SECRET_KEY,
so the variable set in .env never reached the container and Slack
integration stayed disabled ("slack integration disabled
(MULTICA_SLACK_SECRET_KEY not set)"). Add the missing passthrough.

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-01 12:46:03 +08:00
Bohan Jiang
87f9d0fdd3 refactor(autopilots): open access management as a popover from the edit modal (MUL-3893) (#4765)
The standalone 'Manage access' button on the autopilot detail header was
redundant — anyone who cannot open Edit also cannot manage access. The
first attempt folded it into the edit dialog's sidebar, which read as
cluttered. This instead surfaces it as a compact 'Manage access' button in
the edit modal header that opens a popover with the grant/revoke list.

- Extract the access UI into a reusable AutopilotAccessManager (no Dialog)
- Render it inside a header Popover in edit mode, gated on canManageAccess
- Drop the detail-page button, ManageAccessDialog, and the now-dead
  detail.manage_access i18n key (access.* keys are reused by the popover)

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-30 23:11:29 +08:00
Bohan Jiang
1c010d25c0 Revert "refactor(autopilots): fold access management into the edit dialog (MU…" (#4763)
This reverts commit 48f49d8abc.
2026-06-30 20:16:32 +08:00
Bohan Jiang
48f49d8abc refactor(autopilots): fold access management into the edit dialog (MUL-3893) (#4761)
Remove the standalone 'Manage access' button from the autopilot detail
header and surface the grant/revoke list as an 'Access' section inside
the Edit dialog's configuration sidebar. Anyone who cannot open Edit
already cannot manage access, so the separate affordance was redundant.

- Extract the dialog body into a reusable AutopilotAccessManager
- Render it in edit mode only, gated on canManageAccess
- Drop ManageAccessDialog and its now-dead i18n keys

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-30 20:15:47 +08:00
Jiayuan Zhang
c4209ec7c0 fix(issues): count active issues, not agents, in working chip (#4750)
The Issues board header 'x working' chip derived its count from the set
of distinct running agent_ids, so two agents on the same issue read as
'2 working'. Count distinct issue_ids instead so the number reflects how
many issues agents are working on — matching the filter the chip toggles,
which already narrows the list to those issues. The avatar stack still
shows the distinct agents behind that work.

Adds workspace-agent-working-chip.test.tsx covering the multi-agent /
single-issue case, multi-issue counting, scopedIssueIds filtering, and
the empty state.

Fixes MUL-3875

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-30 18:58:19 +08:00
Bohan Jiang
e57288ba60 feat(usage): log per-run prompt-cache hit ratio (MUL-3887) (#4759)
Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-30 18:49:41 +08:00
Multica Eve
f88544da63 docs(changelog): add v0.3.33 entry for the 2026-06-30 release (MUL-3889) (#4756)
* docs(changelog): add v0.3.33 entry for the 2026-06-30 release (MUL-3889)

Adds the v0.3.33 release entry to the /changelog page in all four
landing locales (en, zh-Hans, ja, ko), covering the 16 user-visible
changes that landed on main since v0.3.32 (2026-06-29 release).

The entry groups changes into Features / Improvements / Bug Fixes,
using product-language phrasing per the team convention (no
"modified function X" style notes). The Chinese version follows the
team's localization convention: `agent` → `智能体`, `Squad` → `小队`,
while `Issue` stays as-is as the canonical product term.

Release highlights:
- feat(autopilot): View/Write permission layer + Manage Access (MUL-3807)
- feat(slack): unified chat history backfill (MUL-3871) and typing
  reaction on inbound messages (MUL-3874)
- feat(skills): import skills from a .skill/.zip archive (MUL-3865)
- feat(cli)!: drop short UUID prefix resolution for `multica issue`
  (MUL-3838)
- feat(views): Agents page mobile friendly (MUL-3873)
- improvement: rewrite of the comment routing cascade
  (MUL-3794 + MUL-3879 follow-up)
- improvement: docs swap removed Gemini for CodeBuddy (MUL-3861) and
  remove 117 dead _one i18n keys (MUL-3877)
- improvement: self-host preflight allows newer Docker Compose
- fix(daemon): reconcile in-flight task and workspace state on WS
  reconnect (community contribution, closes #4665)
- fix(agent): recover Antigravity reply from transcript when stdout
  is empty (MUL-3726)
- fix(server): skip CLIENT SETNAME for managed Redis compatibility
  (MUL-3848, community contribution)
- fix(views): count tasks, not agents, in activity hover header
  (MUL-3872)

Verified via the existing `apps/web typecheck`, vitest landing
suite (changelog-page-client.test.ts among them), and eslint on the
i18n directory; all green.

Co-authored-by: multica-agent <github@multica.ai>

* docs(changelog): tighten v0.3.33 entry wording (MUL-3889)

Per feedback that the first draft read too verbose for the public
changelog, trim every bullet of the v0.3.33 entry to one short
sentence and drop the supporting clauses that were rehashing
implementation detail (contributor handles, issue numbers, "30s
ticker" specifics, byline of what the rewrite incidentally fixed).

The net effect is a tighter list that matches the cadence of the
v0.3.32 / v0.3.31 entries already on the page.

Applied identically across en.ts / zh.ts / ja.ts / ko.ts.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
v0.3.33
2026-06-30 18:27:46 +08:00
Bohan Jiang
a961d63611 feat(slack): make the chat agent explicitly channel-aware (MUL-3871) (#4755)
Before this, the chat prompt only carried a generic, always-on hint ('if this
came from a chat channel...'), and the task carried no channel signal — so the
agent never definitively knew it was inside Slack. For an ambiguous ask like
'what did you just talk about', it could read Multica instead of the Slack
conversation.

- Thread a chat_channel_type ('slack') signal: the server sets it on the chat
  task response when the session has a Slack binding
  (GetChannelChatSessionBindingBySession); the daemon Task carries it.
- buildChatPrompt now emits an EXPLICIT block only when channel-backed: 'You are
  operating inside a Slack conversation … this conversation and its history live
  in Slack, NOT in Multica … read it with multica chat history, do NOT look in
  Multica.' Web-only chat sessions get no such block (their history is the
  Multica chat_session the agent already resumes).

Tests: slack-backed prompt asserts the explicit Slack/“NOT in Multica”/command
copy; web-only prompt asserts the block is absent.

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-30 17:24:46 +08:00
Bohan Jiang
50a48cef1e feat(slack): unified multica chat history pull for channel backfill (MUL-3871) (#4747)
* 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>
2026-06-30 16:48:13 +08:00
MeloMei
ff286dcfac MUL-3848: fix(server): skip CLIENT SETNAME for managed Redis compatibility
Closes #4627
2026-06-30 15:51:45 +08:00
LinYushen
630feff1af MUL-3879: restore agent-authored squad-leader fallback in comment cascade (#4748)
After MUL-3794 rewrote the comment routing cascade, computeCommentAgentTriggers
returned early for every non-member author, so worker-agent result comments on a
squad-assigned issue no longer woke the assigned squad leader, breaking the
leader->worker->leader coordination loop.

Restore a narrow agent-authored fallback: when the issue is squad-assigned and
the author is not a member, route to routeAssignedSquadLeaderFallback. Member/
thread routing and explicit @agent/@squad mention routing are untouched, and the
lastTaskWasLeader self-trigger suppression is preserved (it lives inside
routeAssignedSquadLeaderFallback). Explicit mentions are handled before this
branch, so a mentioned target is never double-enqueued alongside the leader.

Co-authored-by: multica-agent <github@multica.ai>
2026-06-30 14:56:49 +08:00
Bohan Jiang
9ee2bd4c34 fix(agent): recover Antigravity reply from transcript when agy stdout is empty (#4744)
agy 1.0.14 print mode can complete a turn (tools executed, final reply produced) while writing zero bytes to stdout, so the daemon recorded a blank but "completed" run and the user saw no answer (MUL-3726, #4595).

When an otherwise-completed turn returns empty stdout, recover the assistant text agy durably wrote to its per-conversation transcript, bounded to the current turn (reset on each USER_INPUT, status=DONE only) so a resumed conversation never re-emits prior turns' answers. App data dir is read from the daemon-owned --log-file rather than guessing $HOME. All paths fail soft to "" so genuine no-text completions and other statuses are unchanged.

Verified against real agy 1.0.14 output plus unit + end-to-end + resume-boundary tests.
2026-06-30 14:47:16 +08:00
Bohan Jiang
b90816264e feat(skills): import skills from a .skill/.zip archive (#4735)
Import a skill from a local .skill/.zip archive: POST /api/skills/import now accepts a multipart upload (file + on_conflict) alongside the JSON URL body, and the CLI gains `multica skill import --file <path>`. Reuses the existing create + on_conflict contract, per-file/bundle/count caps, reserved-SKILL.md rule, and a zip-slip guard.

Closes #4730
MUL-3865
2026-06-30 14:46:46 +08:00
Bohan Jiang
424b02e79a chore(views): remove dead i18n _one keys in other-only locales (#4746)
ja/ko/zh-Hans resolve only the CLDR `other` plural category, so every
`_one` key in those locales is dead weight that i18next never renders.
Remove 117 such orphan keys across 25 namespaces. Each already has its
`_other` sibling, so this is behavior-preserving.

Also add a parity-test guard that fails if a locale whose CLDR plural
rules lack a `one` category ships any `_one` key, so these can't silently
accumulate again (gated on Intl.PluralRules, the same source i18next uses).

Follow-up to #4740 (MUL-3877).

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-30 14:44:35 +08:00
Bohan Jiang
3b45f7fdf6 docs(slack): drop reinstall callout from Slack integration docs (MUL-3874) (#4745)
Slack is not officially launched yet, so the 'already created your app
with an older manifest — add reactions:write and reinstall' guidance is
unnecessary; nobody is running a pre-launch manifest in production. Remove
the warning callout from all four locales (en/zh/ja/ko).

The reactions:write scope in the manifest and the scope table stay, since
the typing indicator still depends on it.

Co-authored-by: J <agent-j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-30 14:29:56 +08:00
Anderson Shindy Oki
93a43a3b0a chore: Remove redundant i18n keys (#4740) 2026-06-30 14:22:19 +08:00
Bohan Jiang
e5995c423f feat(slack): typing reaction on inbound message (MUL-3874) (#4737)
* feat(slack): add typing reaction on inbound message (MUL-3874)

Mirror the Feishu typing indicator on Slack: react with 👀 on the user's
message when it is ingested, then remove the reaction when the agent's run
finishes (EventChatDone) or fails (EventTaskFailed).

- New slack.TypingIndicatorManager: Add on ingest, Clear on terminal run
  events; state keyed by chat_session_id, bot token re-resolved from the DB on
  clear (never held in memory), all failures logged and swallowed (best-effort).
- Wire via the channel-agnostic engine.TypingNotifier seam (slackTypingNotifier
  in the ResolverSet) — the Router already calls OnIngested off the ACK path.
- Clear subscribes to the event bus directly so a failed run also drops the
  reaction (the outbound replier only handles EventChatDone).
- Skip messages older than 2m so Socket Mode reconnect replays don't restamp.

Requires the installed Slack app to hold the reactions:write scope.

Co-authored-by: multica-agent <github@multica.ai>

* fix(slack): clear typing reaction when no task runs; document reactions:write (MUL-3874)

Addresses review feedback on the typing-indicator PR.

1. Stuck reaction on offline/archived agent. The debounced flush
   (flushChatRun) enqueues no task when the agent has no runtime or is
   archived (or on any enqueue/reload error), so no task lifecycle event is
   ever published and the bus-driven clear never fires — leaving the 👀 (and
   Feishu's Typing) reaction stuck on the user's message. Fix at the shared
   engine seam: add TypingNotifier.OnSettled(ctx, sessionID), which the Router
   calls from the flush on every no-task exit (before any offline/archived
   notice). Both the Slack and Feishu notifiers route it to manager.Clear, so
   the latent Feishu case is fixed too. Adds engine coverage (offline/archived
   clear, success does not) and a Slack OnSettled test.

2. Missing reactions:write scope in docs. reactions.add/remove silently fail
   without the scope, but the BYO app manifest/docs never listed it. Add
   reactions:write to the manifest + scope table and a reinstall note across
   all four locales (en/zh/ja/ko).

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: J <agent-j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-30 14:21:08 +08:00
xiawiie
93028d303b fix(daemon): reconcile in-flight task and workspace state on WS reconnect (#4718)
After a WebSocket disconnect, the daemon's view of running tasks and
workspace state can lag the server for up to 5s (per-task cancellation
poll) or 30s (workspace sync) because both loops park on coarse tickers
that do not observe the WS wakeup channel.

This change adds a small fan-out broadcaster (`reconcileBroadcaster`)
that the WS connect path fires once per (re)connect. `watchTaskCancellation`
and `workspaceSyncLoop` subscribe and re-check immediately on broadcast,
without disturbing the ticker cadence. The broadcaster is edge-triggered
with a one-slot replay so a broadcast that lands before a subscriber is
ready is not lost (closes the daemon-startup race), and back-to-back
broadcasts inside 1s are debounced so a flapping connection cannot fan
out into a request stampede.

Existing behaviour is preserved: shouldInterruptAgent still decides
whether to interrupt, the 5s/30s ticker still bounds the worst case,
and the WS heartbeat / HTTP heartbeat coordination is untouched.

Closes #4665
2026-06-30 13:50:18 +08:00
Bohan Jiang
506f2df7ad fix(views): count tasks, not agents, in activity hover header (MUL-3872) (#4734)
The agent-activity hover card renders one row per task and counts tasks.length, but it reused the agent-worded hover_header copy, so a single agent running multiple tasks made the card read '3 agents working' while the workspace chip read '2 working' (unique agents).

Add a dedicated hover_header_tasks key (en/zh-Hans/ja/ko) and point the hover card at it so the header now reads '3 tasks working'. The per-issue chip keeps hover_header since it genuinely passes the unique-agent count.

Co-authored-by: J <agent-j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-30 13:42:09 +08:00
Anderson Shindy Oki
3c61f729d4 MUL-3873: feat: Add agents page mobile friendly
Closes MUL-3873
2026-06-30 13:30:50 +08:00
Bohan Jiang
81291e334e docs: swap removed Gemini runtime for CodeBuddy in built-in lists (#4733)
Gemini CLI runtime was removed in MUL-3617 (#4503), but the canonical
"12 built-in providers" list still advertised [Gemini](/providers#gemini)
(a now-dangling anchor) and omitted CodeBuddy, which the daemon actually
auto-detects (config.go probes `codebuddy`). Swap Gemini -> CodeBuddy
across all 16 occurrences (index / how-multica-works / cloud-quickstart /
daemon-runtimes x en/zh/ja/ko); the count stays at twelve.

MUL-3861

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-30 12:38:30 +08:00
Janek Lasocki-Biczysko
59cb534e87 fix(selfhost): allow newer Docker Compose versions
Allow newer Docker Compose CLI plugin versions in self-host preflight while continuing to reject legacy Docker Compose v1.
2026-06-30 12:31:56 +08:00
Multica Eve
f892e03e41 feat(cli)!: drop short UUID prefix resolution for multica issue (MUL-3838) (#4732)
BREAKING CHANGE: `multica issue <command> <ref>` no longer accepts short
UUID prefixes (e.g. `1881abcd`). Pass the issue key shown by
`multica issue list` (`MUL-123`) or the full UUID instead. Other
resources without a human-readable key (autopilots, projects, labels,
task runs, workspaces) continue to accept short UUID prefixes.

The previous resolver paged the entire workspace issue list client-side
to disambiguate a short prefix, which timed out on workspaces with
~1000 issues (14–35s; reported in GH #4701). Since the issue key
(`MUL-123`) already covers every human use case for an issue reference
and the full UUID covers every machine case, supporting a third
identifier form has no real product value and forces every issue
command to carry ambiguous / min-length / hex-validation semantics
through the CLI.

Rather than pushing the prefix resolver down into the server (with a
new DB query and an expression / generated index), this change removes
the path entirely. The user-facing migration is trivial: the
`identifier` column shown by `multica issue list` is already routable.

Changes:

- `resolveIssueRef` now accepts only the issue key (`MUL-123`) or the
  full UUID. A short hex prefix returns a tailored error pointing to
  the supported forms; non-hex gibberish returns a generic guidance
  error. Neither path makes an HTTP call.
- The unused `fetchIssueCandidates` paginator is removed.
- Tests cover: full UUID succeeds via a single GET, identifier-first
  resolution does not list, and short prefix / dashed short prefix /
  bare numeric / non-hex inputs all fail fast with no HTTP traffic.

Product rationale and the first-principles discussion are recorded on
Multica issue MUL-3838.

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-30 12:31:15 +08:00
Bohan Jiang
d970b68ce7 feat(autopilot): View/Write permission layer + member access delegation (MUL-3807) (#4695)
* feat(autopilot): add View/Write permission layer

Autopilot write and execute operations were gated only by workspace
membership, so any member could edit, delete, trigger, or rotate the
webhook of any autopilot, and GetAutopilot returned webhook tokens to
every member (a token alone can trigger the autopilot).

- Add canWriteAutopilot / requireAutopilotWrite: update, delete, trigger,
  replay-delivery, and all trigger/secret management now require the
  autopilot creator or a workspace owner/admin.
- Redact webhook_token/path/url in GetAutopilot for callers without write
  access; trigger metadata otherwise stays visible (View default = all
  members). Creating an autopilot stays open to any member.
- ANDs with the existing private-assignee-agent dispatch gate.

MUL-3807

Co-authored-by: multica-agent <github@multica.ai>

* feat(autopilot): delegate write access via collaborators + manage-access UI

Adds an explicit grant primitive so an autopilot's creator/admin can
authorize specific workspace members to manage it, with a frontend entry
point — beyond the implicit creator/owner-admin set from the prior commit.

Backend:
- New autopilot_collaborator table (migration 128, members-only, app-layer
  cleanup, no FK) + sqlc queries.
- memberCanWriteAutopilot now also honors explicit collaborators; the write
  gate, webhook-secret redaction, and a new per-caller can_write flag (on
  list + detail) all flow through it.
- POST/DELETE /api/autopilots/{id}/collaborators (writer-gated); GetAutopilot
  embeds the collaborators list. Delete cleans up grants in its transaction.
- Tests: grant->write->revoke flow, non-writer can't grant, non-member rejected.

Frontend (web + desktop via packages/views):
- ManageAccessDialog: member picker to grant/revoke, current list with remove.
- 'Manage access' entry in the autopilot detail header; edit/run/add-trigger/
  delete and the list-row kebab + per-trigger rotate/delete now gate on
  can_write (absent => allowed, server stays the gate).
- can_write wired through types/schema/api client/mutations; en + zh-Hans copy.

MUL-3807

Co-authored-by: multica-agent <github@multica.ai>

* fix(autopilot): add manage-access i18n keys to ja/ko locales

The locale parity test requires every non-EN bundle to cover every EN
key. The prior commit added detail.manage_access + the access.* block to
en and zh-Hans only, failing parity for ja and ko. Add the translated
keys to both.

Co-authored-by: multica-agent <github@multica.ai>

* fix(autopilot): restrict access-list management to creator/admin only

Final-review fix: AddAutopilotCollaborator/RemoveAutopilotCollaborator
used requireAutopilotWrite, which counts granted collaborators as
writers — so a collaborator could in turn grant/revoke others, a
privilege escalation contradicting the 'collaborators cannot re-grant'
design.

- New requireAutopilotAccessManagement guard uses the narrower
  autopilotWriteByOwnership predicate (creator or workspace owner/admin
  only); swapped into both collaborator endpoints. Collaborators keep
  their edit/trigger/secret write-execute rights.
- GetAutopilot now also stamps can_manage_access (narrower than
  can_write); the detail page gates the 'Manage access' button on it so
  collaborators no longer see an entry that would 403.
- Tests: collaborator grant-others -> 403, revoke-peer -> 403, while
  retaining edit; can_manage_access true for owner, false for collaborator.

MUL-3807

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-30 12:29:11 +08:00
Multica Eve
5d79696fb5 MUL-3794: rewrite comment routing cascade 2026-06-30 12:24:57 +08:00
Multica Eve
de7f3cb9e3 docs(changelog): add v0.3.32 entry for the 2026-06-29 release (MUL-3840) (#4706)
* docs(changelog): add v0.3.32 entry for the 2026-06-29 release (MUL-3840)

Lands the daily release notes for v0.3.32 in all four landing locales (en / zh-Hans / ko / ja). Groups today's PRs by Feature / Improvement / Bug Fix with product-oriented wording, keeping technical commit jargon out of the user-facing changelog.

Co-authored-by: multica-agent <github@multica.ai>

* docs(changelog): drop two fixes from v0.3.32 per release-confirmation feedback

Removes the 'deleted agents hidden from usage leaderboard' (MUL-3771, #4637) and 'Antigravity daemon-mode guidance' fix lines from all four locales, leaving five customer-facing fixes for the 2026-06-29 release. The Issue body on MUL-3840 is kept in sync separately.

Co-authored-by: multica-agent <github@multica.ai>

* docs(changelog): drop reverted self-host onboarding beacon from v0.3.32

The anonymous self-host onboarding source beacon (MUL-3708, #4691) was reverted in #4712 because of issues with the collection path. Remove the corresponding feature bullet from all four locales so the v0.3.32 changelog only advertises what actually ships.

Co-authored-by: multica-agent <github@multica.ai>

* docs(changelog): hold Slack BYO app feature back from v0.3.32 user changelog

Per release confirmation, the Slack bring-your-own-app feature is shipping behind a disabled frontend entry for v0.3.32 — code lands, but it is not publicly available yet. Drop the Slack feature bullet from all four locales and rewrite the entry title around what is actually exposed to users (Remove parent Issue + daemon reconnect + attachment preview improvements).

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
v0.3.32
2026-06-29 19:18:13 +08:00
Naiyuan Qing
b336f07617 Revert "feat(analytics): anonymous self-host onboarding source beacon (MUL-37…" (#4712)
This reverts commit 63eb6f73ad.
2026-06-29 19:01:14 +08:00
Jiayuan Zhang
10b33b14f5 fix(dashboard): reconcile deleted-agent spend in usage leaderboard (MUL-3776) (#4661)
PR #4637 (MUL-3771) dropped hard-deleted agents from the per-agent
leaderboard so they'd stop rendering as a bare UUID, but the top-line
Cost/Tokens KPIs still count their spend (those totals aggregate
task_usage_hourly without joining `agent`). The breakdown therefore no
longer reconciled with the totals (#4640).

Instead of dropping unknown-agent rows, fold them into a single
aggregated "Deleted agents" row: sum(visible rows) == KPI total again,
with no UUID exposed. Archived agents still appear as themselves (the
agent list is fetched with include_archived). The bucket carries
tokens + cost only; Time/Tasks render as "—" since the run-time rollups
inner-join `agent` and never attribute time to deleted agents.

- bucketUnknownAgentRows replaces filterKnownAgentRows in dashboard/utils
- Leaderboard renders the sentinel bucket row with a neutral placeholder
  and a "{{count}} agents · {{deleted}} deleted" caption
- i18n: deleted_agents + caption_with_deleted (en/zh-Hans/ja/ko)
- tests cover bucket reconciliation, archived-stays, null-loading passthrough

Co-authored-by: multica-agent <github@multica.ai>
2026-06-29 17:21:35 +08:00
Bohan Jiang
9f1766cdb3 docs(slack): binding link uses the web app URL, not MULTICA_PUBLIC_URL (MUL-3666) (#4705)
Match the code fix (#4703): the "link your account" link is built from the web
app URL (MULTICA_APP_URL ?? FRONTEND_ORIGIN), which a normal deployment already
sets — not MULTICA_PUBLIC_URL (the backend/API URL). Updates en + zh + ja + ko.

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-29 17:18:18 +08:00
Bohan Jiang
2b940046d7 fix(slack): build the binding link from the web app URL, matching Lark (MUL-3666) (#4703)
The Slack "link your account" prompt built its redeem link from
MULTICA_PUBLIC_URL, but /slack/bind is a web-app page — the link must use the
web app URL, not the backend/API URL. MULTICA_PUBLIC_URL is intentionally the
backend/API public URL (webhooks, daemon server_url, attachments); the Lark
replier already uses appURLFromEnv() (MULTICA_APP_URL ?? FRONTEND_ORIGIN).
Slack was never migrated, so on deployments that set FRONTEND_ORIGIN but not
MULTICA_PUBLIC_URL (e.g. dev) the binding prompt silently failed
("public url not configured") and @-mentions got no response.

Rename slack.OutboundReplierConfig.PublicURL -> AppURL and feed it
appURLFromEnv() in router.go, mirroring Lark. Backend/API-URL uses of
MULTICA_PUBLIC_URL (webhooks, attachments, daemon server_url) are unchanged.

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-29 17:13:41 +08:00
Multica Eve
f59cb2f494 MUL-3834: harden daemon websocket reconnect (#4699)
* MUL-3834 harden daemon websocket reconnect

Co-authored-by: multica-agent <github@multica.ai>

* MUL-3834 stabilize daemon websocket liveness tests

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-29 16:46:57 +08:00
Bohan Jiang
d2bc85e01a refactor(slack): declutter the Slack connect UI (#4700)
* refactor(slack): declutter the Slack connect UI

Trim the Slack bring-your-own-app UI to match the leaner Lark card and
stop burying the setup behind prose nobody reads:

- Drop the "Required bot scopes: …" block from the connect dialog.
- Shorten the Slack integration card description to mirror the Lark
  card; the token/admin details stay in the setup docs.
- Remove the dialog intro paragraph and the per-field token hints;
  replace the small "Read the setup guide" link with a larger,
  more prominent step-by-step guide link.

Removes the now-unused i18n keys (byo_dialog_intro, byo_bot_token_hint,
byo_app_token_hint, byo_scopes_hint) across en/zh-Hans/ja/ko.

* docs(slack): drop the users:read warning callout

The bot manifest already lists users:read as a required scope (with the
bots.info rationale in the scopes table), so the standalone warning
callout was redundant. Removed across en/zh/ja/ko.
2026-06-29 16:31:42 +08:00