1622 Commits
Author SHA1 Message Date
5ed09342af MUL-7680: issue wakeup v2 — conditions, runaway protection, check-ins and visibility (#8807)
* feat(wakeups): add rule deadlines and expose the child-done system rule (MUL-7680)

Wakeups can now end: an absolute deadline (expires_at) or a relative wait
(expires_in_seconds) that restarts when the rule is re-enabled. When the
deadline passes first, the scheduler ends the rule; event rules with
on_timeout=wake run the target once with a wakeup.timeout fact. A timed-out
rule keeps disabled_at NULL so its timeout run stays claimable.

The implicit "wake the parent's assignee when a stage of sub-issues
finishes" behavior is now described by GET /api/issues/{id}/system-wakeups
and can be turned off or given a supplementary instruction per issue via
PUT /api/issues/{id}/system-wakeups/child_done. Rule reads fail open to the
previous behavior.

List responses add the creator (member or agent) and the new expiry fields.
The CLI gains --expires-in, --expires-at and --on-timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(issues): let members create wakeups and show the child-done system rule (MUL-7680)

The issue sidebar's Wakeups section is always available on open issues and
gains a New wakeup popover. Members pick a condition (a time, a recurring
check with an end date, someone's reply, an agent's run ending, or raw
events), the agent to wake, the instruction, how often it fires, how long
to wait and what happens on timeout. It posts the same configuration agents
create with the CLI.

The parent's child-done wake appears as a System rule with its stage,
remaining sub-issues and target, a per-issue toggle and a supplementary
instruction. Rows show when a rule ends, timed-out rules read as such, and
details name who created the rule. Rule titles wrap to two lines instead of
truncating.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* docs(wakeups): document deadlines, member-created rules and the system rule (MUL-7680)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(wakeups): platform conditions, runaway protection and check-ins (MUL-7680)

Conditions: a wakeup can now wait for a fact the platform checks itself
(a status, assignee, label or property value; sub-issues or one stage
finishing; a linked PR's checks finishing or merging; another issue's
status). Related events only prompt an early evaluation; the rule wakes
its agent when the predicate becomes true, and a repeating rule fires
again only after it turned false or its facts changed. Finished PR checks
from before registration are ignored.

Runaway protection: repeating event rules stop after max_fires runs
(default 20), a rule pauses when its causal chain passes through it a
third time without a person in between, or when it starts 12 runs in an
hour. The reason is stored and shown.

Also: silent check-ins for every/cron checks (the run then posts no
fallback comment), wake now, delete, a per-rule run history, paused-rule
lists, timeline entries for created/triggered/timed-out/paused/check-in,
and source, paused scope, 7-day runs and child-done system rows in the
workspace list. The CLI gains --until-* conditions, --max-fires and the
trigger/delete/checkin/runs commands; the brief and wakeup prompt state
the check-in exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(issues): record child-done transitions in the status write (MUL-7680)

The child-done system rule ran after the status write committed and gave
up on any error, so a crash, deploy or transient failure lost the
parent's wake. A trigger now records each child's move into a closed
status in the writing transaction, for every writer. The request that
wrote it processes the rows right away and a scheduler sweep retries
anything left over; claims make the two paths process a transition
once.

The rule also follows a workspace default (settings key
system_wakeup_child_done) until an issue sets its own, the per-issue
update accepts partial bodies, and each trigger adds a timeline entry
that names its system comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(views): wakeup conditions, history, timeline and workspace list (MUL-7680)

Issue sidebar: members can create the platform-checked conditions (a field
reaching a value, sub-issues or a stage finishing, a linked PR's CI or merge,
another issue's status) and cap how often a repeating wait fires. Rule
details show the fire count, why the platform paused a rule, the latest runs
with silent-check notes, and offer wake now and delete.

The issue header says what the issue is waiting for ("Emacs 在等 Jiayuan 回复
+2") and opens the Wakeups section. Board and list cards use the same short
sentence, or flag a paused rule. Wakeup runs read as "由唤醒触发 · <condition>"
in the execution log, transcript and on the comments they post.

The timeline shows created, triggered, timed-out, paused and check-in entries
with the rule they came from; consecutive check-ins merge, and the child-done
entry stands in for its system comment, which it can reveal.

Automation → Issue wakeups adds a source column and filter, 7-day runs, a
paused scope with a banner, the child-done system rule on each waiting parent,
and "New wakeup" with an issue picker. Settings → Issue statuses sets the
system rule's workspace default. Copy in all five languages.

The server records the watched issue's identifier in other-issue conditions
and the rule's creator in timeline snapshots, and returns board summaries with
their condition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* docs(wakeups): document conditions, runaway protection and check-ins (MUL-7680)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(wakeups): list system rules without a revision and localize statuses (MUL-7680)

System rule rows returned revision 0, which clients reject as a rule
revision; they now return null, and a list row with an invalid revision no
longer fails the whole page. Built-in statuses in conditions and the status
picker read in the viewer's language. Adds a browser test for a condition
created from the form, the header summary, the scheduler waking the agent
and the workspace list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(views): say why a rule paused without repeating "paused" (MUL-7680)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(i18n): use the product's task terms in wakeup copy (MUL-7680)

ja, ko and fr wakeup strings said イシュー / 이슈 / ticket; the product term
for an issue in those languages is タスク / 태스크 / tâche (and sub-tasks
accordingly), per the conventions page. French strings are rewritten for
the feminine noun.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* test(views): pin the shortcut platform in the wakeup form test (MUL-7680)

The send shortcut is primary+Enter, which is Ctrl on Linux CI runners, so
pressing Cmd+Enter only submitted on macOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(views): simplify the workspace wakeup list

Put the filters on one row: scope as a segmented control on the left,
source/trigger/agent and a search-as-you-type box on the right. "New
wakeup" moves to the page header, which drops the separate Search button.

Rows are single-line: the identifier sits before the title, frequency and
timezone move into the trigger tooltip, and selection, prompt edit and
the last run's transcript show on row hover or focus. The issue column
takes the remaining width and truncates, so the table fits the page.

Also fix creating from the list: the issue picker closes itself right
after reporting the selection, and that close cancelled the flow before
the form could open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* refactor(server): run the child_done rule as a system issue wakeup

The parent-assignee wake on closed sub-issue stages was its own code path:
a system comment, a mention-style run, an override table and endpoints.
It is now an ordinary issue_wakeup row with system_rule set, sharing the
condition, receipts, runaway protection, timeline entries and run model of
people's rules.

- One children_done evaluator: a stage wakes the assignee when it closes
  while a later stage waits; the wrap-up waits for every sub-issue,
  unstaged ones included. People's sub-issue conditions read the same set.
- issue_child_event records closing, reopening, re-parenting and restaging
  for every writer; the request processes it right after commit and the
  sweep retries. User sub-issue rules become immediate too.
- The target is resolved when it fires: an agent run, a squad leader run,
  an inbox notification for a member, or only the timeline entry.
- A parked (backlog) parent catches up when it leaves backlog; a waiting
  run of the same agent is joined instead of queuing a second one; the
  hourly limit pauses a runaway rule. No system comment is posted.
- The run gets the issue's instruction, else the workspace's, else the
  built-in one, plus each stage's counts and the next stage.
- Workspace defaults move to GET/PUT /api/system-wakeups (owners and
  admins) and apply to every rule nobody customized; turning a rule on
  records what already holds so old facts do not fire.
- Existing open parents get their rule from a one-time backfill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(views): workspace wakeup settings and sub-issue notifications

- Settings → Wakeups holds the sub-issue rule's workspace default and
  default instruction; the toggle leaves Issue statuses.
- The issue's system rule shows who it reaches (a member is notified),
  a platform pause, and the instruction it will use.
- Timeline entries say whether the assignee was woken, notified or joined
  a waiting run. The folding of the old system comment is gone.
- Inbox renders the new children_done notification on web, desktop and
  mobile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(server): stop wakeups from repeating their agent's runs

Runs are serialized per issue and agent, so a wakeup never ran beside
another run of its agent, but it queued behind one and repeated it. Each
rule now checks two things before it starts a run:

- Acknowledged: every input came from the agent itself. Its own comments
  and issue changes never wake it, and a condition its own unfinished run
  on the issue satisfied does not wake it when the platform or the agent
  set the rule up. This fixes #8849: a coordinator closing its own stage is
  not woken again while that run is going. A person's condition rule still
  runs afterwards, since the running agent lacks its instruction.
- Merged: when a run of the agent is already waiting to start on the issue
  (assignment, comment, another rule), the rule's instruction and facts
  join that run (context.wakeup_joined, sent to the daemon as
  wakeup_joined and rendered for every prompt kind) instead of queuing
  another. Turning the rule off or replacing it withdraws what it joined.

Sub-issue changes now record the run that made them, and hints and
condition.met inputs carry those runs so a satisfied condition knows
whether the agent caused it. A rule that names the agent itself as the
actor keeps waking it on its own changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(views): show merged and acknowledged wakeups, hint assignee comment rules

- Timeline entries say when a wakeup joined the agent's waiting run or was
  the agent's own doing, for people's rules and the sub-issue rule.
- The create form tells a member that a reply or comment rule for the
  issue's agent assignee joins the run a comment already starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(server): hand waiting wakeups to a run when it is claimed

A rule that fired while its agent had a run waiting used to write its
instruction into that run right away and consume its inputs. That let a
member's rule run under another member's identity, kept deleted rules'
instructions in the run, lost the inputs when the run was cancelled or
claimed by an older daemon, and skipped the fire cap and loop check.

Now the rule only waits for a run that runs as the same person its own
run would, keeping its inputs. When a daemon advertising
joined-wakeups-v1 claims that run, JoinWaitingWakeups rechecks each rule
(on, same agent and person, creator still allowed, not the agent's own
input, no loop), consumes its inputs, counts the fire toward max_fires,
adds its chain and records the merge. A re-claim drops entries of rules
turned off or changed since. Anything else leaves the rule its inputs to
start its own run.

A child_done rule created by the backfill or by processing a change now
treats sub-issues with unprocessed close events as still open, so the
backfill no longer swallows a close waiting for the sweep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(server): consume joined wakeup inputs only once the run starts

A claim used to consume the inputs of the rules that joined the run. If
that claim did not go through, the run went back to the queue carrying
inputs the rules no longer had: turning the sub-issue rule off on the
issue or for the workspace (which only changes enabled) did not remove
them from the next claim, and cancelling the run lost a once rule that
had already ended.

A claim now reserves the inputs for the run (receipt task_id, still
unprocessed). The rule's next dispatch settles them: a run that started
consumes them as a merged firing (once ends, max_fires counts and can
pause, timeline entry); a run that ended without starting gives them
back; one that has not started keeps them. Turning a rule off or
changing it discards its pending inputs, reserved ones included, and a
later claim of the same run rechecks every entry and drops the ones
without reserved inputs or no longer allowed to reach the run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 21:08:54 +08:00
0a51a6cc43 MUL-7759: copy image from the desktop attachment viewer (#8888)
* fix(desktop): add Copy Image to the right-click menu (MUL-7759)

Electron ships no default context menu, so right-clicking an image in the
attachment viewer (or anywhere in the app) offered nothing. Add a Copy
Image item for loaded <img> targets that copies the decoded bitmap via
webContents.copyImageAt — works for any format and origin without a refetch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(views): copy-image button in the desktop attachment viewer (MUL-7759)

Adds a copyImage clipboard helper (fetch, re-encode non-PNG through a
canvas, write via the async Clipboard API) and a Copy image button next to
Download for image previews. Shown only in the desktop shell: web script
can't read the storage CDN's bytes (no CORS), and browsers already offer
Copy image in their own context menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(views): gentler copy-image failure copy per conventions (MUL-7759)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 18:58:50 +08:00
104bf7be35 MUL-7732: regroup settings by scope and consolidate scattered pages (#8874)
* feat(mcp): report how many agents use each workspace MCP server

The workspace MCP library listing now carries agent_count: the number of
live (non-archived) agents each entry is assigned to. Settings shows it so
an admin can tell whether replacing or removing a server affects anyone.

The field is optional in the client schema, so an older server that omits
it simply shows no count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(settings): regroup settings by scope and consolidate scattered pages

Implements the MUL-7732 settings redesign:

- Navigation is grouped by who a setting affects: Personal, the current
  workspace (with Issues & workflow and Connections & extensions
  sub-groups), and This device on desktop. Pages and sections carry a
  scope badge (account, this device, workspace), and members see a lock on
  pages only owners and admins can change plus a shared read-only notice.
- Settings search in the nav indexes page and row titles and
  descriptions; opening a result scrolls to and highlights the row.
- Preferences is one page instead of three tabs, with a searchable time
  zone picker, a segmented theme control and a field grid for the
  create-issue dialogs.
- Code merges Repositories, GitHub and self-hosted Git. Repositories are
  read-only rows edited through dialogs; self-hosted Git connects in a
  dialog that ends on the one-time webhook secret.
- The PR merge rule moves to Statuses & transitions as an automatic
  transition; Code only shows its current value. Built-in statuses show
  edit and archive as unavailable instead of refusing after a click.
- Integrations becomes Messaging (IM channels only) with a breadcrumb on
  detail pages; Composio moves to Personal as Connected apps.
- Members gets tabs, search, invite and share-link dialogs and a seat
  summary. MCP servers show assignment counts and explain how MCP,
  connected apps and plugins differ. Workspace General shows members plain
  values and changes the issue prefix through an explicit dialog.
- Skill labels are managed from the Skills page; Settings > Labels keeps
  the issue catalog.
- Successful saves no longer toast; failures still do.

Old ?tab= values (github, repositories, integrations, issue, chat, labs,
and the Composio OAuth callback) resolve to the new pages, since server
redirects still use them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* docs: point settings paths at the regrouped settings pages

GitHub, repositories and self-hosted Git now live under Settings > Code,
IM bots under Settings > Messaging, and the PR merge rule under
Settings > Statuses & transitions. The GitHub guide also drops the old
master switch step in favor of pausing from the GitHub row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(settings): edit the PR merge rule on the Code page

The "After PRs merge, move the issue to" rule sits with the other pull
request rules on Code again instead of a separate Automatic transitions
section. It applies to every code host, so pausing GitHub features does
not lock it. The statuses page goes back to Issue Statuses, and its merge
badge links to the rule on Code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(settings): manage skill labels under Settings → Labels again

The Skills page entry was buried at the bottom of the filter menu's Labels
submenu. Settings → Labels switches between the issue and skill catalogs
again, using the members page's segmented switch (now SettingsViewTabs),
and the Skills page keeps its shortcut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(settings): keep GitHub resume reachable after a disconnect

Pausing GitHub features is a workspace setting, but the pause and resume
item lived in a menu that only rendered with an installation. A workspace
paused before disconnecting, or with github_enabled=false and no
installation, lost the way back, and every PR switch stayed disabled. On
deployments without a GitHub App it could not even reconnect, though
Co-authored-by works without one.

The menu now shows whenever GitHub is connected or paused, and only
Disconnect requires an installation. The paused note shows in either state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(settings): let the URL choose the open members list

The members page read `section` only to initialize its list tab, so a
search result for invitations or share links opened while already on the
page changed the URL but not the list, and back/forward did nothing. With a
router the open list is now derived from the URL, and switching lists
replaces `section`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(settings): tell repositories on different ports apart

The manual repository dialog reused the GitHub import's identity, which
drops the port, so https://git.example.com:9443/acme/api.git read as a
duplicate of the same path on :8443 and could not be saved. Non-default
ports now stay in the identity; default ports, including 22 for ssh://,
still compare equal to the scp-like form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* test(comments): stop the recipient-menu tests flaking under CI load

The steering tests opened a recipient chip menu while the composer could
still re-render its chips, from the previous send settling or the preview
catching up. That closed the menu, and the menu steps also used the 1s
default wait inside the 5s default test timeout. Under parallel load
locally, about 1 in 20 runs failed the same way as CI.

Menu choices now go through a helper that reopens the menu until the item
shows. The remaining waits get the 5s the first steps already had, and each
test gets 15s. 140 of 140 loaded runs pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 18:15:01 +08:00
344ffaf1be MUL-7737: feat(views): address bar for HTML attachment previews (#8873)
* MUL-7737: feat(views): address bar for HTML attachment previews

HTML attachments render in a sandboxed srcdoc iframe, so the document's URL
is about:srcdoc and location.search is always empty. A self-contained mock
that picks its screen with `?s=overview` could only show its default screen.

A one-line bridge script placed in front of the author's markup gives the
document a real query and fragment through history.replaceState (Chromium
and WebKit allow an opaque-origin document to rewrite them), resolves
relative pushState/replaceState URLs against the document, reports address
changes to the parent, and turns `?…` link clicks into navigate requests.

The viewer and the full-page preview get a reload button and an address
field after the file name. A new query reloads the document; a
fragment-only change navigates in place. "Open in new tab" carries the
address as `loc`, and the full page keeps it in the desktop tab's view
state across tab switches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* MUL-7737: feat(views): put the HTML address in the viewer's top bar

The address bar took a row of its own above the document. The file name
in the top bar is the fixed part of the address anyway, so it now is the
address: the query and fragment follow the file name and are edited in
place. A document at its bare address reads as just the file name, so
files that never use an address look as before; hovering or focusing
shows the field and a hint of where to type. Reload joins the HTML view
controls, disabled with them in the source view.

The address state moves from the stage to PreviewPanel, which owns both
the bar and the stage, and resets per file since the viewer pages to the
next file without remounting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 16:26:00 +08:00
815fe37b1f MUL-7685: fix(agents): paginate task history and aggregate duration (#8804)
* fix(agents): paginate task history and aggregate duration

* test(agents): cover pagination boundaries and query lifecycle

* fix(migrations): register agent history index retry cleanup

* fix(agents): show totals only for complete task history

* fix(agents): average duration over the full rolling window

The server bounds duration totals by completed_at > now() - 30 days,
which can span 31 calendar days. deriveAgentActivity summed duration only
after trimming buckets to the chart's 30 local-day slots, so runs on the
oldest partial day were dropped from the average. Sum duration for every
returned bucket before slotting.

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

* fix(migrations): renumber agent history index to 552

main now carries 551_pr_merge_status, and migration numbers from 129
onward must be unique (TestMigrationNumericPrefixesAreUnique).

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

---------

Co-authored-by: Bohan-J <bhjiang@outlook.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 15:58:00 +08:00
9c6622787d MUL-7735: docs(readme): fix stale facts, sync the Chinese README, retake the hero screenshot (#8869)
* MUL-7735: docs(readme): fix stale facts and bring the Chinese README in line

Facts that had drifted from the code:
- Runtimes table was missing ZeroClaw (25 rows against the 26 CLIs in
  scripts/agent-cli-command-names.txt).
- Architecture diagram routed Desktop and iOS through Next.js; only Web
  does. Desktop and iOS talk to the Go backend directly.
- A single execution is a Run, not a Task (conventions glossary; the
  docs page is already titled Runs / 运行).
- Mobile runs natively on iPad too; link the mobile-app docs page.
- New Feishu/Lark connections are mainland-China Feishu only.
- "Any Git host" overclaimed; it is GitHub, GitLab, Gitea, and Forgejo.

Chinese README: channel list now matches English (adds WeCom and
Telegram and the community-maintained note), docs links point at
/docs/zh/ with Chinese heading anchors, adds the missing CLI-skill row
and Star History badges, and uses the UI's terms (聊天, 自托管).

Structure: the CLI count now appears once, in the Runtimes section,
instead of four places per file; Get started and the five-minute
walkthrough are merged; the self-host block states the anonymous
telemetry default and the DO_NOT_TRACK opt-out; Stay in the loop gains
steering a running agent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* MUL-7735: docs(assets): retake the workspace overview screenshot

The hero image predated the sidebar regrouping (Work / AI Team) and the
Usage -> Analytics rename. Retaken from a seeded local environment at the
same 1600x900 size and WebP q80 (85 KB -> 82 KB). The README and docs
index pages share this file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 04:27:06 +08:00
6dc6a0b9a2 MUL-7650: attachment viewer — CSV tables, JSON/YAML tree, numbered code, HTML viewports, every kind in a new tab (#8868)
* feat(server): accept JSON Lines in the attachment text preview proxy

The viewer now shows .jsonl / .ndjson files as a tree, so the text proxy's
whitelist takes them too (extension and application/x-ndjson). The whitelist
test gains the CSV / TSV / log / JSON Lines cases and says it is mirrored by
the client-side table in packages/views/editor/utils/preview.test.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(views): table, tree, numbered-line and viewport views in the attachment viewer

MUL-7650, step 3 of MUL-7642. Fills in how the full-window viewer shows the
file types agents produce most.

- CSV / TSV (`table` kind): parsed to a table on the shared DataTable —
  pinned header, virtualized rows, resizable columns, a frozen row-number
  column. Clicking a header sorts ascending / descending / file order;
  numbers sort numerically, empty cells stay last, all-number columns align
  right. `;`-separated exports are sniffed. DataTable headers now carry
  aria-sort when the table sorts through TanStack's own state.
- JSON / JSON Lines / YAML (`structured` kind): a collapsible tree with
  inline previews of collapsed records and paged long lists; Tree / Raw in
  the top bar. A file that does not parse falls back to the source and says
  why. YAML alias expansion stays capped by the parser.
- Code, text and logs: numbered lines (the number is a pseudo-element, so
  copying skips it) with a sticky gutter, and a wrap toggle. Logs and plain
  text wrap by default, code does not; the reader's choice carries across
  the sequence.
- HTML: Fit / Desktop 1440 / Tablet 768 / Phone 390. A device width lays the
  document out at that width and scales it down when the stage is narrower,
  with the size and scale shown under it; switching never remounts the
  iframe. A source toggle shows the HTML with line numbers.
- Open in new tab works for every previewable kind: /{slug}/attachments/{id}
  /preview loads the record by id and renders the viewer's own top bar and
  stage (AttachmentPreviewStandalone), keeping the desktop scroll restore for
  HTML.
- Whitelist: .jsonl / .ndjson join the text types on both sides, and the
  client test mirrors the server's case table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 04:13:06 +08:00
7dac88a6a8 MUL-7649: issue deliverables and dynamic blocks — sidebar section, overview grid, viewer info panel, html/mermaid block frame (#8779)
* feat(attachments): issue deliverables model and sequence block ids (MUL-7649)

collectDeliverableFiles groups every comment upload into deliverables,
merging same-name, same-type re-uploads into versions (newest version
first). Description attachments are inputs and never enter the model.

Sequence items now carry the id of the block they came from, so a viewer
can say which comment (or the description) a file was posted in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(viewer): host details, overview and info panel slots (MUL-7649)

PreviewSequenceProvider takes an optional describeItem (title accessory,
info panel, locate action) and onOpenOverview. The viewer renders only the
slots it is handed: a locate button, a grid button (G) and an info toggle
(I) whose panel sits beside the stage. Letter keys stand down in fields,
and any key pressed inside an open menu stays with the menu.

A zoom canvas still at fit now follows a viewport resize (the info panel
opening), while a zoom the reader chose is only clamped, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(issues): deliverables section, overview grid and info panel (MUL-7649)

The sidebar's "Deliverables" section shows what the issue delivered as a
whole: its pull requests and the files its comments uploaded, re-uploads
merged into one entry marked v2. It lists the three newest images and four
newest files, plus "View all N deliverables".

The former Pull requests section becomes the code group, keeping its
MUL-7429 actions (link a PR by URL, the auto-complete menu) beside the
group label. While the workspace shows PRs, the section stays up even with
nothing delivered, since that is where a PR gets linked by hand.

The overview (from "view all" or G in the viewer) shows code first, then
files grouped by posting comment, filterable by kind, each group one click
from its comment. Its count always equals the sidebar's.

In the viewer, deliverables get a version switcher, and every file gets an
info panel (source excerpt, sibling files, uploader, time, size) and
"Show in comments", which unfolds a resolved thread if needed and reuses
the timeline's jump-and-flash highlight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(comments): standalone attachments as cards and an image row (MUL-7649)

The files under a comment (and a chat message) no longer stack as one
full-width row each. Other files become cards in a grid: type glyph, name,
"TYPE · size", download / remove on hover, and the whole card opens the
file. Several images sit in one row at a shared height; a lone image keeps
its full size. HTML keeps its embedded preview (MUL-2330).

On the issue page a re-uploaded file's card carries its version (v1, v2)
from the deliverables model, through a small context.

Standalone attachments are grouped images, then HTML, then files, and the
preview sequence walks them in that same order, so paging follows the
screen. The file-type glyph moves to editor/utils/file-icon so comment
cards and the deliverables surfaces share one mapping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* refactor(issues): pull requests keep their own section (MUL-7649)

Since MUL-7429 the PR block is issue workflow — linking a PR by hand, the
auto-complete switch, "won't auto-complete: missing Closes" — rather than
output, so it goes back to main's own Pull requests section, unchanged,
directly above Deliverables.

Deliverables is now the delivered files only: hidden until a comment
delivers one, and the sidebar number, "View all N" and the overview's
"All N" all count files. The overview drops its code group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(comments): lay several images out as justified rows (MUL-7649)

Fixed-height tiles wrapped as soon as the column narrowed — three
screenshots became two plus an orphan, with a ragged right edge. Rows are
now justified from each image's real aspect ratio: every row shares one
height and a full row runs edge to edge. A row takes as many images as fit
above a minimum height, never grows past a maximum, and a last row that
does not fill keeps the height of the row above it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(rich-content): dynamic block frame for html and mermaid fences (MUL-7649)

A fenced ```html or ```mermaid block is part of the message body, so it now
renders in one shared frame instead of two unrelated widgets:

- Always-visible title bar: kind icon, the fence's title="..." (or the kind
  name), a kind chip, Preview | Source, fullscreen and copy. Previously the
  controls only appeared on hover and floated over the content.
- The body takes its content's height (at least 120px) and collapses past
  480px behind a fade and "Show all". An HTML block used to be a fixed 480px.
- Loading keeps the height this block had earlier in the session; a script
  error or a Mermaid parse error is explained inside the frame with
  "View source" and "Copy error".
- HTML gets the app's theme tokens (--foreground, --chart-1 ...); HTML that
  uses one also gets the app's color-scheme, so it follows dark mode. HTML
  that uses none keeps its own look.

The sandboxed document reports its height and first uncaught error through a
one-line postMessage bridge; the page checks the message source and clamps
every value, and stops a document whose height follows the frame. rehype-raw
drops the fence meta, so the title is read in the closed-fence parse.

The Mermaid sandbox now also declares the app's color-scheme and font: in
dark mode it was painted on an opaque white canvas, and its labels were drawn
in a serif font that did not match the layout Mermaid measured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(attachments): HTML files show as cards, not inline previews (MUL-7649)

Reverses the MUL-2330 pin. An uploaded HTML file is a deliverable to open,
not part of the text: like every other non-image file it shows as a card
(a row inline in the body, a grid card under it) that opens in the viewer,
and its contents are no longer fetched to embed a 480px iframe. HTML meant
to be read in place is written as a ```html block, which renders as a
dynamic block.

- Attachment drops its html branch; HtmlAttachmentPreview is deleted and
  HtmlPreviewBody keeps only the inline source the viewer uses.
- orderStandaloneAttachments groups images, then files (HTML included), so
  AttachmentList and the viewer sequence still walk the same order.
- The MUL-2330 regression pins now assert the card and that no text fetch
  happens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* docs: charts go in html/mermaid blocks, files show as cards (MUL-7649)

Agents that uploaded an HTML chart expecting it to render in the comment now
get a file card instead. Tell them where each kind of content goes:

- multica-platform skill (issues reference, routed from the table): a fenced
  ```html / ```mermaid block renders in place with a title bar and content
  height; an attached file, HTML included, is a card that opens the viewer.
  Covers title="...", the sandbox, the theme variables and their opt-in
  color scheme, and sizing to content rather than the viewport.
- `multica issue comment add --attachment` help says the same in one line.
- Comments docs (all five languages) describe both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(daemon): runtime brief says where charts and diagrams go (MUL-7649)

The chart/file rule lived only in the multica-platform skill's issues
reference, which an agent opens on demand, and the skill's description did
not mention comment formatting, so an agent that just wanted a chart in its
result had no reason to read it. Agents that uploaded report.html expecting
an inline chart now get a card.

- The brief's Output section carries one line, beside the file-delivery
  line, on the surfaces the web renders (issue comments and web/mobile
  chat): put charts and diagrams in the text as a fenced html or mermaid
  block, name it with title="...", and an attached file, HTML included,
  shows as a card. Theming and sizing stay in the reference.
- Channel chats, autopilot run results and quick-create stdout do not render
  these blocks and do not get the line; the delivery tests pin both sides.
- The skill description names "charts and files in comments" so the
  reference is picked for that task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(issues): locate a file in a collapsed thread (MUL-7649)

"Show in comments" for a file posted in a reply only unfolded resolved
threads and gave up after 30 frames, so a thread the reader had collapsed
stayed shut and the reply never mounted. A reply now goes through the
quick-jump rail's jumpToReply, which already undoes every kind of folding
and waits for the reply to land. A root comment gets the same treatment for
its own thread (the reader's collapse, or a resolved bar), then the jump.

Found in review by Emacs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(viewer): the info panel's "Show in comments" closes the viewer (MUL-7649)

The top bar's locate went through the viewer, which closes itself first;
the same button in the info panel called the host's callback directly, so
the page scrolled underneath a viewer that still covered it. Controls handed
to describeItem gain close(), and the issue page builds one close-then-locate
action for both entry points.

Found in review by Emacs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(issues): the deliverables overview is a real modal dialog (MUL-7649)

The overview was a portaled layer with role="dialog" and nothing else: focus
stayed on the opener, Tab walked into the page underneath, and closing left
focus wherever it was. It now renders through the shared Dialog, restyled to
cover the window, so the primitive moves focus in, keeps it inside, returns
it to the opener, and handles Escape. `G` back to the viewer's file stays.
The dialog is named by its title ("MUL-123 deliverables").

Found in review by Emacs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 03:23:21 +08:00
fc4a615700 MUL-7726: let the workspace choose the status merged PRs move an issue to (#8862)
* feat(pr): move issues to a workspace-chosen status when their PRs merge (MUL-7726)

The merge automation completed an issue only when a PR said "Closes MUL-1",
so the workspace switch mostly did nothing: most PRs are linked by title or
branch and never completed anything. The workspace now picks what a merge
does, in settings.pr_merge_status: "none", or the key of a started or done
status (custom ones included, Blocked excluded). Absent means Done. When
every linked PR is merged, the issue moves to that status. A closing keyword
only links.

- Decision: terminal, triage, "none", the per-issue switch and a new
  at_target state (the issue already has the target) come before the PR
  states, so the issue page only speaks when a merge would move the issue.
  The response adds target_status.
- MoveIssueFromPullRequests replaces CompleteIssueFromPullRequests and
  writes the target status. The target is resolved through the delivery's
  shared catalog read (Resolver.WritableCategory). An archived or unknown
  choice, or a failed read, fails closed to no write.
- Close intent is no longer synced or read. The column stays.
- Migration 551 pins "none" on existing workspaces whose history shows a
  merge never moved every issue: auto-complete switched off, or a linked PR
  whose merges were not all keyword merges. Workspaces with no links, or with
  only keyword merges, keep the Done default. Replays are harmless.
- CLI: `multica issue pr-automation <id> on|off` lets an agent keep one issue
  where it is. The multica-platform skill describes the new rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(views): choose the status merged PRs move an issue to on the GitHub page (MUL-7726)

- Settings → Integrations → GitHub → Features: "After PRs merge, move the
  issue to" picks Don't change, or a started or done status, custom ones
  included and Blocked excluded. It replaces the read-only auto-complete row.
  The self-hosted Git page shows the same setting. The statuses page drops
  its switch, and a badge on the target status now links to the setting.
- Issue sidebar: the line under the PR list names the target status ("Moves
  to In Review when #19 merges"). It says nothing when the workspace leaves
  status alone, when the issue already has the target, or on an older
  backend's no_close_intent. The per-issue switch reads "Keep status when PRs
  merge" and is hidden when it would change nothing. The menu opens the
  GitHub setting. The link hint only shows while auto-link is on.
- core: derivePRMergeStatus replaces derivePRAutoCompleteEnabled; the
  auto_complete schema adds target_status and the at_target state.
- Copy in 5 languages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* docs: describe the workspace-chosen status for merged PRs (MUL-7726)

GitHub integration, self-hosted Git, issues and environment variable pages
(4-5 languages) now say that the workspace picks what a merge does: Done by
default, another started or done status, or no change. They also say that a
closing keyword only links, and how to keep one issue's status. The
troubleshooting entries explain what a missing line under the PR list means.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* refactor(pr): keep the merge automation out of agents' instructions (MUL-7726)

Agents don't act on what a merge does to an issue, so they don't need a
command for it or a rule to learn. Drop `multica issue pr-automation`, and
cut the multica-platform skill down to how PRs get linked: no merge rule, no
auto_complete field guide, no "the server moves it to done" note. People
still keep one issue's status from its Pull requests menu, and the docs now
point there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(pr): keep the retired auto-complete switch working for old desktop clients (MUL-7726)

Review of #8862: an admin on a desktop client from before this change can
still flip "Complete issues when their PRs merge". The client saved
pr_auto_complete_enabled = false, and the save looked successful, but the
server only read pr_merge_status, so merges kept moving issues to Done.

- Settings writes: a flip of the retired switch becomes a choice (off →
  none, on → Done). An old client that echoes a chosen target without
  flipping the switch leaves the target alone. Every write mirrors the switch
  from the effective choice (false for none, absent otherwise), so old
  clients show the right state.
- Reads: without pr_merge_status, the retired switch decides (off → none,
  else Done), on the server and in derivePRMergeStatus. This also covers
  settings written by a pod that predates the change during rollout.
- Migration 551 sets the switch off on the workspaces it pins to none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 02:26:24 +08:00
692dd019ac MUL-7713: docs(troubleshooting): explain macOS TCC re-prompt after CLI upgrade (#8843)
* docs(troubleshooting): explain macOS TCC re-prompt after CLI upgrade

The Homebrew CLI is ad-hoc signed and installed under a versioned Cellar
path, so macOS drops the daemon's privacy grants on every upgrade and the
next run that touches a protected folder blocks on an unanswered prompt.
Document the symptom, the cause, and how to re-grant access (en/zh/ja/ko/fr).

Part of #8720

* docs(troubleshooting): scope macOS TCC section to launchd daemons

- Say the hang applies when the dialog names multica (launchd/LaunchAgent
  daemons), instead of stating it for every daemon.
- List all upgrade paths: multica update and the Runtimes page Update button.
- Restart the daemon the way it was started (launchctl kickstart for a
  LaunchAgent), and note auto-update is only on by default for Multica Cloud.
- fr: use "le CLI" / "daemon" like the rest of the page; zh: link text
  matches the Desktop page title.

Part of #8720

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

---------

Co-authored-by: Bohan-J <bhjiang@outlook.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 02:11:19 +08:00
importcpp 6328e81834 fix(mobile): avoid seeding incomplete project lists (#8854) 2026-09-27 02:04:27 +08:00
Danila Katalshov ba9f639242 docs(mobile): correct the app icon comment after #7453 (#8844)
The comment above `icon` still said the mobile icon is shared with the
desktop client. Since #7453 the mobile icon is its own full-bleed,
no-alpha artwork, and pointing it back at the padded desktop icon would
bring back the double-masked white ring from #6995.
2026-09-27 02:02:35 +08:00
57fabfc078 MUL-7724 feat(desktop): left-align window toolbar as Back / Forward / sidebar toggle (#8859)
Left-align the title-bar controls just past the macOS traffic lights and
order them Back, Forward, then the sidebar toggle, with arrow icons in
place of chevrons. Drop the standalone History button: long-pressing,
right-clicking, or pressing ArrowDown on Back / Forward already opens the
directional history menu.

With the History button gone and the controls left-aligned, the toolbar
clearance shrinks from 256px to 200px (88px traffic-light inset + three
28px buttons + two 8px gaps + 12px trailing pad), matching the sidebar's
minimum width so the controls always fit inside the expanded sidebar.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-26 23:06:03 +08:00
pseudoyu 64b06b3de8 fix(telegram): attach the file a reply quotes (#8828)
A reply that quoted a photo, video or file reached the agent without the
file: in a group with an @-mention the quote rendered as
"[empty or non-text message]", and in a private chat, or when replying to
the bot's own message, the quoted message was not rendered at all. Only
the trigger message's own file went through the media resolver.

Telegram delivers the quoted message in reply_to_message with its file
ids, so the adapter now picks that file the way it picks the sender's own,
renders it in the quoted block as its placeholder above the caption, and
carries both files in the raw envelope in body order. The resolver ingests
every file, keys each object by its position as DingTalk and WeCom do, and
tells the sender once when one could not be fetched. A quoted file is
selected context in every chat and whoever sent it, as long as the reply
addresses the bot; quoted text keeps the group-mention rule.

Recent-context entries render a file the same way, so a captioned photo in
the window no longer hides that a photo was there.
2026-09-25 14:29:30 +08:00
f05f8250bc docs(changelog): add v0.5.3 release entry (2026-09-24) (MUL-7683) (#8805)
Co-authored-by: Eve <eve@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-24 17:16:02 +08:00
Battleplus 5fc4d19131 fix(desktop): normalize bundled CLI version (#8695)
Signed-off-by: Battleplus <3559424769@qq.com>
2026-09-24 16:55:24 +08:00
Bohan Jiangandmultica-agent 51e95dca2d fix(issues): fit PR auto-complete copy on one line (MUL-7678) (#8797)
* fix(issues): shorten the no-close-keyword hint under the PR list (MUL-7678)

"No PR says “Closes MUL-123”, so merging won’t complete this issue" wrapped
to two lines in the default-width sidebar. Drop the trailing clause: the
line sits in the auto-complete slot under the PR list, and "Closes" is the
GitHub keyword developers already read as "completes on merge". The keyword
and identifier, the part a reader acts on, stay.

Same cut in all five locales. The docs already describe this line as "no
PR says `Closes MUL-123`", so they need no change.

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

* fix(issues): lead the no-close-keyword hint with the outcome (MUL-7678)

Review of #8797: the line sits in the auto-complete status slot next to
"Completes when #19 merges", and when it shows the PR is usually one click
from merging. Dropping "merging won't complete" left the fix but not the
warning. Put the outcome first and keep the keyword:

  Won't complete: no "Closes MUL-123"

Measured with Inter at 12px in Chromium: 229px (235px for a five-digit
number), one line in the 260px text column of the default 320px sidebar.
"Won't complete on merge: ..." measured 285px and wrapped. zh, ja and ko
use the same terse shape and also fit; fr takes two lines.

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

* fix(issues): fit the PR auto-complete menu toggle on one line (MUL-7678)

"Don't auto-complete this issue" measures 204px at 14px Inter, over the
198px text column of the w-60 menu, so it wrapped. Use "Turn off
auto-complete" / "Turn on auto-complete" (154px / 152px): same verbs as
the "Auto-complete is off for this issue · Turn on" status line, and the
menu already belongs to this issue's Pull requests block. fr wrapped too
and gets the same shape; zh, ja and ko already fit and stay.

The docs path to the toggle is updated to the new label.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-09-24 16:19:57 +08:00
Bohan Jiangandmultica-agent 0da0b46bbf MUL-7672: only a closing keyword completes an issue on PR merge (#8794)
* feat(pr): only a closing keyword completes an issue on merge (MUL-7672)

#8758 made every linked PR count toward auto-complete, so a title-only
"MUL-123: ..." PR moved its issue to Done on merge. Restore the earlier
contract: a PR completes its issue only when its title or body puts a
closing keyword (Closes/Fixes/Resolves) right before the identifier.

- Body closing keywords link again; a bare body mention still links nothing.
- close_intent is recorded on the link and follows the PR text until the
  merge/close event, then freezes; a link first made after merge carries none.
- The decision requires every linked PR merged and at least one with close
  intent; a new no_close_intent state explains why a merge won't complete.
- Everything else from #8758 stays: manual link/remove, per-issue and
  workspace switches, edge-triggered evaluation, the result line.
- Docs (4 languages), UI copy (5 languages) and the multica-platform skill
  describe the keyword rule again.

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

* fix(pr): sync close intent on every link of the PR (MUL-7672)

Review of #8794 found two ways a withdrawn closing keyword still completed
the issue:

- close_intent was refreshed only for identifiers the PR still claimed, so
  a manual link, or an automatic link whose merge event (carrying the final
  text) arrived before the edit event, kept a stale true.
- the refresh sat behind the auto-link toggle, so turning auto-link off
  froze whatever intent was recorded.

Replace the per-link update with one PR-wide sync: until the merge/close
event, every link of the PR (automatic or manual) gets close_intent exactly
when the current title/body closes its issue. It runs whenever GitHub
features are on, independent of auto-link, which now only decides which
links are created.

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

* fix(pr): decide close intent apart from auto-link ownership (MUL-7672)

Re-review of #8794: the PR-wide close intent sync reused the auto-link
verdict, and resolvePRLinkPolicy skips workspaces with auto-link off. With
an installation bound to several workspaces, turning auto-link off in one
of them therefore cleared a closing keyword only that workspace resolves,
and the merge no longer completed the issue.

The policy now reads every bound workspace with GitHub on, and records
the identifier's sole resolver next to the auto-linking owner. Links still
follow the owner (auto-link off workspaces are not competing claimants);
close intent is permitted for the owner or the sole resolver, so an
identifier that resolves in more than one workspace stays withheld.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-09-24 14:56:59 +08:00
Bohan Jiangandmultica-agent 9c69661f7f feat(web): licensing FAQ, privacy policy, About team section, source-available wording (MUL-7558) (#8643)
* docs(license): name Index Labs (Hong Kong) Limited as the producer (MUL-7558)

The LICENSE and NOTICE credited "Multica, Inc.", which is not a registered
entity. Replace it with the company that holds the rights, define "the
producer" (used throughout Part I but never defined), and point to where
commercial licenses and branding waivers are requested.

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

* feat(web): add licensing and privacy pages, describe Multica as source-available (MUL-7558)

- /licensing: plain-language licensing FAQ with the rule of thumb and common
  scenarios, in en/zh/ja/ko.
- /privacy: privacy policy based on what Multica Cloud actually collects;
  the Contact Sales privacy link now points here instead of /about.
- About: new "Who's behind Multica" section (team story + contact routes;
  team member cards left for later).
- Replace "open source" / "fully open source" with source-available
  wording across the landing copy, page metadata, JSON-LD, docs, READMEs,
  and the Helper agent prompt, since the license restricts hosted use.
- Contact Sales consent copy says "Multica" instead of "Multica, Inc.".
- Reserve the "licensing" workspace slug for the new top-level route.

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

* fix(web): drop the Slack users section from the licensing page (MUL-7558)

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

* fix(web): add the commercial-use FAQ in ja/ko and sharpen the source block headline (MUL-7558)

- ja and ko override faq.items wholesale, so the new "Can I use Multica
  commercially?" entry (the FAQ's only link to /licensing) was missing
  there. Add it, plus a test that keeps every locale's FAQ in step.
- The source block headline now states the value ("Every line, on your
  terms.") instead of repeating the license category.
- Privacy policy: list apps connected through Composio among the
  integrations that receive data.

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

* fix(web): align the privacy policy with what the product actually does (MUL-7558)

Privacy review found promises the code does not keep:

- Workspace deletion removes content from the service, but uploaded files
  are not yet erased from object storage and backups keep copies. Say so,
  and give an email route for erasing files.
- AI: coding agents send prompts, code, and tool results to their own model
  providers; only the Cloud assist features use a provider we pick. Scope
  the training promise to Multica.
- Crash reports: redaction filters recognizable emails and credentials in
  the error message only, not every field.
- Self-hosted: list the snapshot fields (including the random deployment
  ID), and note that AI, integrations, and analytics follow the operator's
  configuration.
- Sharing: cover workspace members, admins, and authorized agents; move
  legal and M&A disclosures out of the service provider list.
- Add legal bases, consent withdrawal, and the right to complain, plus
  retention for billing records and analytics.

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

* fix(web): build trust-page test dictionaries through createLandingDict (MUL-7558)

main now passes a docs href to each landing dictionary factory, so the
test's single-argument calls failed typecheck. Build the dictionaries the
way the app does, mark the DOM-free test as node, and bring the new French
mobile-app doc in line with the source-available wording.

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

* fix(web): stop overpromising on data handling and align trust copy with product terms (MUL-7558)

- The commercial-use FAQ now names both license triggers in every locale
  (offering Multica to outside users, and embedding it in a product you sell
  or distribute); ja and ko read as hosted-only before.
- Replace "your data never leaves your network" and "code never passes
  through Multica servers" with what actually happens: agents run on your
  machines, workspace content is stored by Multica, coding tools send
  prompts to their model providers, and self-hosting keeps workspace data on
  your servers. Links to the privacy policy.
- New and changed copy follows the terminology conventions: issue ->
  任务/タスク/태스크, agent run -> 运行/実行/실행 (Run in English),
  workspace -> 工作区, onboarding -> 上手引导.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-09-24 14:25:49 +08:00
pseudoyu 554937f36b docs(telegram): update French pages for photo, video and file support (#8790)
The French pages from #8757 were translated before #8727 landed, so both
still said the Telegram bot only accepts text. Translate the current English
passages, reusing the terms the French pages already use.
2026-09-24 13:08:11 +08:00
1a09c65b1a feat(llm): support MULTICA_LLM_DISABLE_THINKING env (MUL-7162) (#8657)
* feat(llm): support MULTICA_LLM_DISABLE_THINKING env

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

* docs(llm): scope reasoning_effort note to quick actions

The disable-thinking docs claimed GPT-5.6-family models already get reasoning_effort=none from the server and do not need the new switch. That is only true for quick actions (GenerateJSON); chat auto-titling goes through GenerateText, which sets no reasoning field. Narrow the wording in .env.example and all four language docs so the translations stay in sync.

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

* docs(llm): scope the disable-thinking note to accepting upstreams

The previous wording called the switch "the only way" to turn off auto-titling
reasoning, but on a standard OpenAI endpoint the switch adds chat_template_kwargs
to the title request and the upstream rejects it, so titles fail instead of
skipping thinking. Drop the "only way" claim and name the feature the way the
list below does (follow-up questions).

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

---------

Co-authored-by: zhudejun1 <zhudejun1@huya.com>
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: Multica Agent <agent@multica.local>
2026-09-24 13:07:25 +08:00
Bing Free d5a25d7438 MUL-7608 feat(mobile): add Simplified Chinese localization (#8702)
Add a standalone Simplified Chinese UI to the mobile app, with a
Settings → Language picker (Follow system / English / 简体中文) whose
manual choice persists across restarts. Mobile keeps its own i18next
resources rather than reusing web/desktop copy, following the
conventions.mdx glossary: issue statuses and categories stay lowercase
English identifiers, Squad is 小队.

- Failure reasons: REASONS now mirrors web's REASON_LABEL, and a drift
  test pins it to the en/zh-Hans failure_reason keys.
- Production env: .env.production stays committed with the public
  multica.ai defaults; the *:prod scripts layer a gitignored
  .env.production.local on top for self-hosting or a custom bundle ID.
- Duplicate-mark activity rows from #8741 are localized too.
2026-09-24 13:07:10 +08:00
Bohan Jiangandmultica-agent 4f5fbc9216 fix(vcs): explain untrusted TLS certificates and trust private CAs via Helm (MUL-7639) (#8761)
* fix(vcs): report untrusted provider TLS certificates on connect (MUL-7639)

ConnectVCS reported every non-token failure as "could not reach the
provider instance" and logged nothing, so a Gitea behind a private CA
looked like a network problem. Log the underlying validation error and
tell certificate failures (untrusted CA, host name mismatch, other
verification failures such as expiry) apart from unreachable instances.
Status codes are unchanged.

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

* feat(helm): trust extra CA certificates in the backend (MUL-7639)

backend.extraCACerts.configMap mounts an existing ConfigMap of PEM
certificates read-only and points SSL_CERT_DIR at the system directory
plus the mount, so the backend trusts an internal CA without dropping
public CAs or disabling TLS verification. Unset renders unchanged.

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

* docs(self-host): document trusting a private CA (MUL-7639)

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

* fix(helm): render when values predate extraCACerts (MUL-7639)

helm upgrade --reuse-values from an older chart carries no
backend.extraCACerts key, and reading .configMap on it failed the whole
render with a nil pointer even when no CA was wanted. Fall back to an
empty dict, and cover the missing-key, default and enabled renders in
the chart test.

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

* docs(self-host): restart Compose backend after replacing a CA (MUL-7639)

up -d keeps the running container when only a mounted CA file changed,
so the backend kept its old trust store. Use restart instead.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-09-24 13:04:01 +08:00
pseudoyuandClaude Fable 5.1 62efe26aa0 MUL-7620 feat(telegram): send and receive photos, videos and files (#8727)
* feat(telegram): send and receive photos, videos and files

Telegram was text-only in both directions: dispatch dropped every non-text
update with an "unsupported" notice, the outbound subscriber only knew
sendMessage/editMessageText, and the channel was never declared to
DeclareChannelFileDelivery, so agents were told they cannot attach files.

Inbound: photos, documents, video, video notes, animations, audio and voice
notes become chat attachments through the engine's MediaResolver seam
(getFile, file host download, object storage, intent-ledger row before the
PUT, as Slack does). The caption is the message text and a placeholder marker
leads the sender's own text; the engine swaps it for the attachment link once
the bind commits. Quoted and recent group context still go in front of it.
Stickers and other non-file kinds keep the unsupported notice.

Outbound: files the agent bound to its reply are sent into the chat as their
own messages once the text settles (sendPhoto for common images up to 10 MB,
sendVideo/sendAudio for mp4 and common audio, sendDocument for the rest up to
50 MB). A photo Telegram refuses to process is resent as a document.

Both halves hang off the same store != nil branch that declares file
delivery, so the agent is promised the hop only where it exists. Compared
with the WeCom reference this drops relay routing, the three-state delivery
classification, per-reply metrics and the pending/admitted counters: Telegram
outbound is stateless HTTP and the delivery lease already guarantees a single
sender per turn.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(telegram): address media review — deliver files once, admit before spawning, keep the notice without storage

Review on #8727 raised five points; each lands here with a test.

1. A file-only reply could be delivered twice. closeTurn returned true
   whether this call ended the turn, a deeper attempt owned it, or it was
   settled already, and the empty-reply branch delivered files on all three.
   closeTurn now reports closedNow / closedAlready / closeHeld, and files go
   out only on closedNow. Regression tests: a replayed chat:done on another
   replica sends the files once; a completion an automatic retry has
   superseded sends nothing and the retry's answer still lands.

2. Admission sat behind the goroutine and the lookup. The attachment lookup
   now runs on the terminal worker, so a reply with nothing bound costs one
   indexed read and no goroutine; a delivery is spawned only for files known
   to exist, under a slot claimed first (non-blocking, four slots). Every
   slot busy sheds the reply with the notice instead of parking it.

3. Rebased on main over MUL-7585: recent group context, /new isolation and
   the privacy rule are kept; unaddressed group media is buffered and never a
   turn, as before; the media placeholder leads the sender's own text with
   quoted and recent context still in front.

4. Without object storage the member got no signal. The polling loop now
   knows whether the deployment stores media (ChannelDeps.AcceptsMedia, set
   from the same store != nil branch as the resolver) and keeps the old
   unsupported notice when it does not. A fetch that fails — over the 20 MB
   bot download limit, or a failed download — tells the sender so, instead of
   leaving the agent a placeholder nothing stands behind.

5. The failure notice claimed more than the code knew: a lost response may
   mean Telegram accepted the file. It now says delivery could not be
   confirmed and that the file stays attached in Multica, which holds either
   way.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(telegram): offset the media placeholder past prepended context; report a failed attachment lookup

Second review round on #8727.

1. With quoted or recent group context ahead of the sender's text, the
   MediaRef carried only the placeholder, so the engine replaced occurrence
   0: a member who typed "[Image]" in the window received the sender's
   file and the sender's own marker stayed bare. inboundMedia now carries
   PlaceholderIndex — the enrichers only prepend, so it is the count of the
   marker ahead of the sender's segment, literals included (the DingTalk
   rule) — and the resolver sets it as InlineIndex. Regression tests cover
   the recent-context and the quoted-message paths.

2. A failed ListAttachmentsByChatMessage dropped the files silently while
   the text already on screen referred to them. The read has no side
   effects, so it is retried three times 250 ms apart; if it still fails
   the member gets a notice worded for what is known — whether the reply
   had files could not be checked — never the one that presumes a file
   existed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-24 12:41:47 +08:00
9df0cae78a MUL-7429: complete issues when every linked PR merges (#8758)
* feat(pr): complete issues when every linked PR merges (MUL-7429)

Title/branch identifiers link a PR; keywords and the body no longer matter.
Completion is evaluated only on a PR event for the issue (merge, link,
unlink), so settings changes and reopening never complete an issue.
Adds manual link/unlink with remembered exclusions and a per-issue
auto-complete switch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(pr): issue-page PR automation and single auto-complete setting (MUL-7429)

The issue's Pull requests section says what the merge rule will do next,
links a PR by URL, removes one per row, and turns auto-complete off for
that issue. The workspace switch lives under Issue statuses; the GitHub
page reports it. Agent skill and docs describe the title/branch rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(pr): accept PR links pasted without a scheme (MUL-7429)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* test(skills): pin the PR auto-complete contract in the platform skill (MUL-7429)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(pr): re-check linked PRs at the completion write; skip stale GitHub events (MUL-7429)

The status write now requires every linked PR to still be merged when it
runs, so a PR linked between the decision and the write keeps the issue
open. GitHub PR upserts ignore events older than the stored row, like the
self-hosted path, so a late delivery cannot roll a merged PR back to open
and turn a redelivered merge into a new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-24 02:58:19 +08:00
Bohan Jiangandmultica-agent 920c3ea6d9 feat(docs): add French documentation aligned with UI terminology (MUL-7637) (#8757)
* docs(i18n): translate documentation corpus to French (MUL-7637)

Add French (.fr.mdx) translations for all 45 docs pages plus meta.fr.json
navigation, mirroring the English source. Product and UI terms follow the
in-app French locale at packages/views/locales/fr/ (issue -> tâche,
run -> exécution, autopilot -> automatisation, Chat -> Discussion,
Settings -> Paramètres, etc.). Status and role identifiers stay lowercase
English per the conventions glossary, with the French UI label shown once
where they are defined.

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

* feat(docs): serve French docs locale (MUL-7637)

Register fr in the docs i18n config and add the French Fumadocs chrome,
language-switcher label, and home hero copy. Fumadocs maps fr to Orama's
built-in French tokenizer, so search needs no custom localeMap entry.

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

* feat(i18n): link French UI to French docs (MUL-7637)

Route French users from the landing page, runtime guides, the autopilot
webhook filter help, and the Slack/Lark/DingTalk/Telegram setup links to
/docs/fr. The four integration tabs and the runtime helper each carried a
copy of the same locale-prefix ternary; they now share docsLocalePrefix.

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

* fix(i18n): localize remaining docs entry points (MUL-7637)

French users still reached English docs from the Help menu, the Agents and
Skills "Learn more" links, and the landing footer. The views links now use
docsLocalePrefix; the landing dictionaries take the docs href from
docsHrefForLocale, so locales that reuse another dictionary (French reuses
English) still link to their own docs.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-09-23 18:30:31 +08:00
Multica Eveandmultica-agent 92d29fe76b docs(changelog): add v0.5.2 release entry (2026-09-23) (MUL-7630) (#8749)
* docs(changelog): add v0.6.0 release entry (2026-09-23) (MUL-7630)

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

* docs(changelog): switch release entry to v0.5.2 and tighten title (MUL-7630)

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

* docs(changelog): fold steering into a single entry (MUL-7630)

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-09-23 17:39:56 +08:00
Bohan Jiang 1539c54ec5 feat(issues): make duplicate marks traceable and one click from the original (MUL-7349) (#8741)
* feat(issues): make duplicate marks traceable and one click from the original (MUL-7349)

- Log duplicate_marked / duplicate_unmarked on the duplicate and
  duplicate_added / duplicate_removed on its original, with the other issue
  linked in the activity feed; the delete and GitHub merge paths now carry
  both ends of the mark so those rows are written too.
- Expose duplicate_of_issue_id on issue responses (only while cancelled) so
  list rows, board cards and table cells can show an arrow to the original
  that opens it directly.
- Banner links as a whole to the original and shows its status; the undo
  action becomes the secondary "Not a duplicate" with a toast.
- Sidebar duplicates show reporters and a count; the status picker reads
  "Change original" on an issue that already is a duplicate; the mark
  picker opens with same-title and recently viewed candidates.

* fix(issues): keep the duplicate pointer in step with CI shape tests (MUL-7349)

- Add duplicate_of_issue_id to the CLI --fields whitelist, which the
  list-endpoint shape test checks against a real response.
- Project the column in the search parity test's legacy query copy and
  scan it, so both queries read through the one scanner.
- Mount the row marker's query and navigation hooks only when the issue
  is a duplicate, so surfaces rendered without a QueryClient (the table's
  inline title) and thousands of ordinary rows subscribe to nothing.

* fix(issues): resolve a duplicate's original on the server and log one row per mark change (MUL-7349)

- Issue responses carry duplicate_of {id, identifier, title, status}, filled
  beside the status category only while the issue is cancelled and the
  original still exists; the bare pointer is no longer exposed. Rows read it
  directly, so the marker makes no request and is a real link where the row
  is not one.
- A mark change suppresses the generic status_changed row; the unmarked row
  records where the status went. Covered through the real UpdateIssue path,
  with the test server wiring activity listeners like main.go.
- The picker applies one selectability predicate to search results and
  suggestions, so an existing duplicate can no longer be chosen.
2026-09-23 16:55:50 +08:00
Bohan Jiangandmultica-agent 8355c7aeca fix(cli): name the TLS handshake timeout and stop masking it on login (MUL-7572, GH #8654) (#8744)
A Windows user whose network dropped the two-packet TLS ClientHello that
Go 1.24+ sends by default (post-quantum key share, ~1.5 KB) saw only
"Sign-in did not complete: the server could not issue an access token"
and, on the --token path, "make sure it is valid and not expired". Neither
mentioned the network, and the generic timeout copy pointed at
MULTICA_HTTP_TIMEOUT, which does not govern the handshake.

- classify net/http's "TLS handshake timeout" as its own kind, with copy
  that names the GODEBUG=tlsmlkem=0 check in both languages
- let `multica login` keep the transport copy for transport failures; its
  sign-in copy now only overrides HTTP refusals
- troubleshooting entry (en/zh/ja/ko)

Co-authored-by: multica-agent <github@multica.ai>
2026-09-23 16:45:36 +08:00
a2111888aa fix(issues): polish the mark-as-duplicate affordance (MUL-7349) (#8738)
* fix(issues): make the duplicate action read as an action, not a copy (MUL-7349)

The status picker footer borrowed lucide's Copy glyph, which every other
surface in the app uses for copy-to-clipboard (code blocks, API tokens,
webhook URLs), so the row read as "copy this issue". Blend — two
overlapping shapes — is unused elsewhere and says "these two are the
same thing". The banner and the Relations menu item follow so all three
duplicate surfaces share one glyph.

The footer label also rendered muted, which made the only action in the
popover look disabled next to the status rows. Everything else already
matched (14px/400/20px, same icon size and text offset), so only the
colour moves; the icon stays muted like the rows' trailing check.

The trailing ellipsis goes: the footer stands alone under the status
list, where nothing else is elided. The Relations menu item keeps its
own, matching its siblings there ("Set parent issue...", "Add
sub-issue...").

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

* fix(issues): use CircleEqual for the duplicate icon (MUL-7349)

Blend's two intersecting rings read first as "related" or "mixed", and
the overlap crowds at 14px. A circled equals sign says "these two are
the same thing" and stays legible at menu size. All three duplicate
surfaces switch together: the status picker footer, the Relations menu
item, and the banner above a duplicate's title.

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

---------

Co-authored-by: J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-23 15:53:37 +08:00
Bohan Jiangandmultica-agent 9b8da372cc MUL-7450 fix(lark): surface the event-delivery check a silent Bot needs (#8735)
* fix(lark): surface the event-delivery check a silent Bot needs

A Feishu app whose event subscription delivers to a request URL instead
of the long connection binds successfully, shows Connected everywhere,
and never receives a message. Multica has no webhook ingress, so those
events go nowhere near us. Nothing in the product said so: the drop path
in the connector writes no log line, an inbound audit row is only reached
once a message enters the Router, and the troubleshooting docs listed
only the causes this case is not.

Three pointers, no behaviour change:

- The connector logs the event type when the decoder declines a frame,
  so a socket that is up but only receiving events we do not handle is
  distinguishable from a healthy one. Heartbeats carry no event type and
  stay silent, so the log does not fill with noise.
- The connected badge on the agent's Integrations tab names the long
  connection and links to the guide. That is where someone looks when
  the Bot stays quiet; the install dialog closes itself on success.
- The integration guide's troubleshooting list gains the subscription
  mode and event subscription, in all four locales.

Refs #8496

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

* fix(lark): report a dropped event type once per connection

Review on #8735: one Info line per declined frame is unbounded. An app
that subscribes to reactions or membership churn delivers those
continuously, and there are thousands of Feishu installations.

Report each event type once per connection instead. The map is
read-loop-local, so it needs no lock and a reconnect re-reports. What
the line is worth stays the same: it names what IS arriving on a socket
whose drops leave no other trace. It says nothing about what is missing
— an app delivering to a request URL sends no frames at all, so it
produces no line, which is why install-time verification is the actual
fix for #8496 rather than this.

Also drops apps/web/next-env.d.ts from the branch: Next regenerated it
during a typecheck run and it has nothing to do with this change.

Refs #8496

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-09-23 15:25:45 +08:00
Francisco Guimarães (chico)andmultica-agent 5403083420 MUL-7607 fix(auth): restore invitation-based signup (#8706)
Co-authored-by: multica-agent <github@multica.ai>
2026-09-23 14:33:43 +08:00
Basuotian 6278fdb8bc feat(web): offer the Windows CLI installer alongside curl (MUL-7602)
Expose the supported PowerShell installer alongside the existing macOS/Linux command in the runtime dialog, onboarding, and landing download section.
2026-09-23 14:28:55 +08:00
pseudoyuandClaude Fable 5.1 d31fcbc9b7 MUL-7585 feat(telegram): inline recent group context on @mentions (#8673)
* feat(telegram): inline recent group context on @mentions

When a member @-mentions the bot in a group, the agent only ever saw that
single line. Lark solves this with a <recent_context> prefetch
(lark/inbound_enricher.go, MUL-3084); Telegram could not copy it because
the Bot API has no history endpoint — getUpdates is consume-once.

Each installation's polling loop now keeps an in-memory ring of the last
DefaultRecentContextSize (10) human group messages per chat and per forum
topic, and inlines that window as a <recent_context> block ahead of an
addressed group message, in the same recent → quoted → own composition
Lark uses. The trigger and an explicitly quoted parent are excluded from
the window; /new starts a chat without ambient context; p2p chats and
unaddressed messages are never enriched. Unaddressed chatter is still
never persisted or turned into a session (MUL-2671): it only surfaces as
read-context on a turn a member directed at the bot, and the ring dies
with the polling loop.

The docs now say what the window contains and that Group Privacy must be
off in @BotFather for the bot to receive the surrounding messages at all.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(telegram): state the recent-context retention and privacy contract accurately

Review follow-up for #8673. Setup step 4 now presents Group Privacy on as
the default, minimal-visibility choice and turning it off as the optional
step that enables recent group context, with its cost spelled out. The
Groups section no longer claims unaddressed chatter is "never stored": it
says the message is held in a short in-memory buffer and saved with any
addressed turn that pulls it in, that a reply to the bot triggers the
context just like an @-mention, what Telegram delivers under each privacy
setting, and that an admin bot receives everything regardless. Same fix in
zh/ja/ko. The two code comments that relied on privacy mode being on, or
claimed the message was never persisted, now describe the real contract.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 14:22:48 +08:00
importcpp 024e365ee0 fix(mobile): avoid duplicate WebSocket recovery and reject invalid frames (#8690) 2026-09-22 16:30:53 +08:00
f95c3b657a fix(desktop): preserve startup toolbar clearance (#8686)
Co-authored-by: Forge-Boy <forge-boy@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-22 15:20:16 +08:00
Multica Eve d4267bf42b docs(changelog): add v0.5.1 release entry (2026-09-21) (MUL-7557) (#8640)
Add the 2026-09-21 changelog entry in all four locales and set the web and desktop app versions to 0.5.1. Steering is left out — it is not shipping today.
2026-09-21 18:31:32 +08:00
Multica Eveandmultica-agent 8c9f865e3e MUL-7480: Revert comment steering feature (#8648)
* Revert "MUL-7480: Link delivered steers to final replies (#8623)"

This reverts commit 3de6275eb3.

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

* Revert "fix: preserve active task intent in steer frames (#8621)"

This reverts commit b866dacd1c.

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

* Revert "MUL-7480: Steer active agent turns from comments (#8605)"

This reverts commit 8ce795b007.

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

* chore(db): remove reverted comment steer schema

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

---------

Co-authored-by: Sol-Boy <github@multica.ai>
2026-09-21 18:29:50 +08:00
3de6275eb3 MUL-7480: Link delivered steers to final replies (#8623)
* feat: link steer receipts to final replies

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

* fix: keep steer final reply locators stable

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

---------

Co-authored-by: Forge-Boy <forge-boy@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-21 15:04:17 +08:00
c375f83c2e MUL-7418: add issue event and time wakeups (#8467)
* feat: add issue event and time wakeups (MUL-7418)

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

* feat(wakeups): capture issue collaboration event lifecycle

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

* fix(wakeups): preserve reply source when reopening threads

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

* feat(wakeups): summarize sidebar and issue activity cues

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

* fix(wakeups): restore disabled configurations with a two-way toggle

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

* feat(wakeups): manage workspace wakeups in Autopilot

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

* chore(wakeups): reserve migrations after current main

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

* test(wakeups): align inventory fixtures with shared contracts

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

* fix(wakeups): distinguish trigger conditions and execution results

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

* fix(wakeups): suppress registration feedback and drain bounded evidence

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

* fix(wakeups): align sidebar actions with titles

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

* fix(wakeups): bound database work and unify shared issue visibility

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

* feat(wakeups): filter mutation events by member or agent actor

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

* refactor(wakeups): merge structured evidence and narrow receipt locks

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

* feat(wakeups): let people edit wakeup instructions

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

* fix(wakeups): align inventory controls and compact prompt editing

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

* fix(mobile): handle deferred agent run status

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

---------

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-21 04:17:12 +08:00
8ce795b007 MUL-7480: Steer active agent turns from comments (#8605)
* feat: steer active agent turns from comments (MUL-7480)

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

* fix: make comment steering hint driven

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

* fix: harden active comment steering races

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

---------

Co-authored-by: Forge-Boy <forge-boy@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-20 19:00:53 +08:00
Bohan Jiangandmultica-agent 9018df3efb feat(projects): set a repository's checkout ref from the UI (MUL-7504) (#8592)
* feat(projects): set a repository's checkout ref from the UI (MUL-7504)

A project that tracks one delivery line could already pin the branch its
tasks start from — `github_repo.resource_ref.ref` has been stored, sent to
the daemon and honored at checkout since #4467. The UI only ever displayed
it. Anyone working through the web, desktop or mobile app had to drop to
`multica project resource update --ref` to set something the interface
already showed as a property of the resource, and until they did, every
task silently started from the remote default branch.

This adds the missing input, on the three paths a repository is configured:

- Create-project modal: a "Branch, tag, or commit" field per selected repo,
  shown once a URL is entered so the common default-branch case still reads
  as one field.
- Resource panel: the same field in the attach form, plus a dialog on each
  attached repo — the row previously offered only delete.
- Mobile: the field in the attach sheet, and the ref rendered in the list,
  which never showed it at all.

Three details the shape of the existing code forced:

- The ref renders on its own line rather than appended to the repo label. A
  custom name took that slot, so naming a repo hid the one signal that its
  tasks do not start from the default branch — and mobile is the only client
  that collects a name.
- Edits spread the stored ref before overwriting. The server replaces
  resource_ref wholesale, so a payload carrying only `ref` is rejected for a
  missing URL.
- Pasting a `.../tree/<branch>` URL now splits into the two fields. Mobile's
  URL check accepted that whole string and stored it as the clone URL, so
  the gap was already producing targets that cannot be cloned.

Server-side, `ref` was stored with no validation beyond a trim, so a typo
survived to checkout and surfaced as a daemon 500 naming its local repo
cache, minutes later and to whoever ran the task rather than whoever typed
it. validateGitRef applies the shape rules from `git check-ref-format` plus
a length cap. Existence on the remote is deliberately still not checked:
the server cannot see the repository, and an offline daemon or a private
repo must not block saving configuration.

Free text rather than a branch picker, by design. Nothing in the product
can list a repository's branches today, and a picker could not express a
tag or a commit SHA anyway. A "list remote branches" endpoint remains the
follow-up #8572 proposes; it should stay an input aid, never the only way.

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

* feat(projects): make the pinned branch the delivery target too (MUL-7504)

Pinning a repository's starting point only solved half the problem. Nothing
in Multica creates pull requests — `gh pr create` is the agent's own call,
and without `--base` it targets the repository's default branch. So a
project pinned to `release/2026-09` had its agents start in the right place
and then open pull requests into `main`, carrying every commit the release
line had that `main` lacked. Product review asked for the two halves to
move together, so the brief now states both.

The Repositories section names each repo's starting point and, when
anything is pinned, says to deliver back to it. The Project Context bullet
stops implying `--ref` is how you reach the configured revision — the
daemon already applied it, and `--ref` is the override.

The delivery rule is stated conditionally, because a pin is not necessarily
a branch. The field accepts anything git resolves, and neither the server
nor the daemon can tell a branch from a tag or a commit without asking the
remote — which the product deliberately does not do. A tag has nothing to
merge back into, so the agent is told to confirm rather than guess a base.

That same ambiguity shapes how the UI narrows to branches. The field now
says "Starting branch" and explains that tasks both start and deliver
there, but the promise cannot be enforced: `v1.2.3` is a legal branch name
and `main` is a legal tag. Only a full-length object id is unambiguous, and
only that is declined, pointing at `multica repo checkout --ref` where a
one-off revision belongs. The stored grammar is unchanged, so the CLI and
API still accept tags and commits — the daemon has always resolved all
three, and this is a UI-level product choice, not a data restriction.

Two fixes from the same review:

- An unpinned repository now reads "Default branch" instead of showing
  nothing. Clearing a branch used to make the line vanish, which looks
  identical to the setting never having existed — there was no way to
  confirm from the panel that a repo was deliberately on its default, or
  that the row had a branch setting at all.
- The edit dialog says the change applies to newly started work. The ref is
  read when a task is claimed, so work already underway keeps the branch it
  started on, and the copy now says so rather than leaving it to be
  discovered.

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

* fix(projects): close three gaps in the starting-branch flow (MUL-7504)

All three found in review of the two commits before this one.

**A resumed task could be told to deliver somewhere new.** The starting
point in the brief is the project's CURRENT setting, read when the run was
claimed. A task that started from `release/a`, left uncommitted work and
resumed after the project moved to `release/b` kept its old checkout — the
work is still branched off `release/a` — while the brief now named
`release/b` and told it to deliver there. Following that would push the old
line's work into the new one, and would break what the edit dialog
promises: work already underway keeps the branch it started on.

The brief cannot settle this alone, because whether a checkout gets reused
is only known once `repo checkout` runs. It defers to the checkout instead,
which already reports `Kept` and names the branch it is on.

**Create Project accepted a branch it had already rejected.** The submit
gate only checked the add-a-repo field, so a branch edited on an
already-selected repo reached the payload with nothing but an inline error
to show for it — and the server accepts commit ids by design, so nothing
downstream caught it. Both the button and handleSubmit now check every
selected repo; the button alone was never enough, since TitleEditor's
onSubmit calls handleSubmit directly.

**A second pasted browse URL was stored whole.** Splitting `/tree/<branch>`
was gated on the branch field being empty, so pasting another repository's
browse URL after one was already filled in saved the entire `/tree/...`
string as the clone URL — a target that cannot be cloned. Normalising the
URL is now unconditional in all three entry points. Whether to overwrite
the BRANCH is the separate question, and the pasted pair wins: the branch
field only appears once a URL is present, so a value sitting in it came
from the previous URL rather than from something typed ahead of time.

One defect the review surfaced indirectly: GithubRefField took an optional
`id` and the per-repo instance in the create-project modal passed none,
which detached the label from its input — leaving the field unnamed for a
screen reader, and unreachable by getByLabelText, which is why the gap had
no test. The id is now generated when omitted.

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

* fix(execenv): stop naming the checkout as the resumed delivery target (MUL-7504)

The resume warning added in the previous commit told the agent to deliver
kept work "to the branch it actually started from, which the checkout
names". The second clause is wrong. A kept checkout reports the branch the
worktree is ON — resolved by `git symbolic-ref`, so the task's own
`agent/<name>/<task>` branch. That is the HEAD of a pull request, never its
base, and nothing in WorktreeResult carries the ref it was cut from. An
agent following that sentence would pass its own branch as `--base` and
open a pull request whose base equals its head.

The warning now says what the reported branch actually is, and points at
the sources that do settle the question: the base of the work's existing
pull request, or the target the task states, and ask when neither does.

It also moves out of the `if pinned` branch. A project cleared back to its
default branch renders no starting point at all, yet a task resumed after
that change still holds a checkout cut from the old one — gating the
warning on a pin dropped it exactly where the mismatch is invisible. The
one sentence that only makes sense alongside a listed starting point
("do not retarget it to a starting point listed above") stays conditional,
so the cleared variant does not point at a line that is not there.

Regressions cover both resume shapes — pinned A to B, and A cleared to the
default branch — plus the specific wrong phrasing, so it cannot come back.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-09-20 17:10:08 +08:00
6b69b3710c Revert "MUL-7517: clarify squad leader identity framing (#8597)" (#8600)
This reverts commit a51fd83072.

Co-authored-by: Forge-Boy <forge-boy@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-20 14:57:49 +08:00
a51fd83072 MUL-7517: clarify squad leader identity framing (#8597)
* fix(squads): clarify leader identity framing (MUL-7517)

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

* fix(squads): address briefing review feedback (MUL-7517)

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

---------

Co-authored-by: Forge-Boy <forge-boy@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-20 14:48:17 +08:00
ZIceandmultica-agent 8feab0abe4 perf(web): reduce route barrel imports for #8387 (#8575)
Co-authored-by: multica-agent <github@multica.ai>
2026-09-20 14:00:41 +08:00
YYClaw 3fbffd5085 MUL-7521: fix(i18n): correct inbox agent activity notification hints (#8590)
The Inbox notification setting for "Agent activity" told members they would
be notified when an agent run completes or fails. The server has only ever
emitted an inbox item for a failed run; completion is deliberately left to
the issue's status change. Members who enabled the toggle expecting a
"finished" notification never received one.

Change the hint to "When an agent run fails" in the five shared UI locales
used by web and desktop, and in the mobile app's independent settings copy,
which additionally still used the retired "task" wording. Notification
behavior, preference keys, and labels are unchanged.
2026-09-20 13:39:22 +08:00
2df765a3c8 docs(changelog): add v0.5.0 release entry (2026-09-18) (MUL-7481) (#8543)
* docs(changelog): add v0.5.0 release entry (2026-09-18) (MUL-7481)

Adds the 0.5.0 changelog entry to all four landing locales and bumps the
web and desktop app versions.

Range v0.4.44 → 594ff89 (55 PRs). Minor bump because the release ships
full French product UI, which is worth announcing on its own.

Left out of the changelog, as agreed on MUL-7481: Triage (triage_v1 not
rolled out), the plugins_v1 / billing_workspace_subscriptions gate error
semantics, UI Lab, CI/test/docs, prompt and skill changes, the Issue
status category migration, and internal performance work.

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

* docs(changelog): retitle the v0.5.0 entry around agent behavior (MUL-7481)

The title listed the top of `features` and carried a small feature
(Autopilot schedule editing) while leaving out the day's agent work.

Drop the small feature, keep the French UI, and put the agent side in:
runs are both steadier (Grok/Pi/Copilot/Codex/Cursor/Hermes, durable
terminal reports) and leaner — a resumed agent no longer re-reads the
whole Issue and every comment (#8377, #8488). That last one now has its
own improvements line so the title stays backed by the entry.

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

---------

Co-authored-by: Sol-Boy <sol-boy@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: Bohan-J <bhjiang@outlook.com>
2026-09-18 18:02:36 +08:00
Bohan Jiang d7009df8cc Revert "MUL-7300 fix(issues): preserve Quick Create original input (#8519)" (#8546)
This reverts commit a6472044c3.
2026-09-18 17:46:06 +08:00
a6472044c3 MUL-7300 fix(issues): preserve Quick Create original input (#8519)
* fix(issues): preserve quick-create original input

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

* fix(issues): validate quick-create origins

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

---------

Co-authored-by: Sol-Boy <sol-boy@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-18 16:17:16 +08:00
78a5f69ae1 MUL-6813: fix: surface private runtime owner mismatch as explicit failure (#7805)
* fix: fail private runtime owner mismatches (PUCK-89)

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

* fix: keep runtime mismatch repair off idle claim path

* test: cover mixed runtime claim outcomes (PUCK-89)

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

* fix: settle runtime owner mismatches during claim

* fix: reject ownerless agents on private runtime claims

* chore: restore claim version sampling layout (PUCK-89)

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

* fix: re-authorize delivery at the finalize boundary, dedicated runtime_access_denied client copy, fixture convention (PUCK-89)

Blocker 1 — final delivery gate: FinalizeTaskClaim now runs a caller-
supplied authorize closure inside its transaction. The singular and
batch claim paths share finalizeClaimDelivery, which re-locks the
runtime row (FOR UPDATE), re-reads the agent, and re-verifies the
private-runtime owner fence against CURRENT ownership before the task
token commits. A concurrent re-registration that would change
owner_id blocks until the gate commits, closing the stale-snapshot
TOCTOU. Mismatched tasks settle through the existing
failClaimedTaskBeforeLaunch -> FailTask path; singular keeps
200 {"task":null}, batch skips the task and keeps returning valid
ones. Regressions cover both paths.

Blocker 2 — client copy: runtime_access_denied gets dedicated
actionable copy (make runtime public / rebind-copy agent) on the
issues blocked-trigger mapping, both chat send-failure toasts, the
chat failure-reason map, the mobile dispatch-reason helper, and the
autopilot run-now toast. Generic fallbacks unchanged; locale keys
added for en/ko/ja/zh-Hans.

Blocker 3 — fixture convention: runtime_access_denied_test.go now
uses testutil.Call; daemon_runtime_access_test.go seeds its chat task
via the dbfx.Task fixture. Test semantics unchanged.

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

* fix: bind final task token to locked runtime owner

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

* fix: keep delivered user context on current owner

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

* test: use testutil.Call in settlement-failure claim regression

Convert the last manual httptest.NewRecorder flow in
TestFinalizeClaimDelivery_SettlementFailureIsUnsettled to the repository's
required testutil.Call helper (hard convention from review) and drop the
now-unused net/http/httptest import. No production code changes.

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

* PUCK-132: settle queued private-runtime owner mismatches as runtime_access_denied

Align persisted task failure semantics with admission semantics: queued
private-runtime ownership mismatches now settle as runtime_access_denied,
letting clients reach the dedicated recovery copy instead of the generic
invalid_task_identity copy. Actual task/agent identity violations
(agent rebound, agent deleted, response identity mismatch,
error_agent_runtime_changed) keep invalid_task_identity.

- add taskfailure.ReasonRuntimeAccessDenied (permanent, non-retryable
  ownership authorization failure; agent process never launched)
- swap the reason in the two owner-mismatch settlement paths in
  handler/daemon.go (response-assembly recheck + delivery-gate
  authz-error default branch, including ownerless-runtime denial)
- regression A (queued issue task), B (queued chat task + assistant
  failure message via existing FailTask path), C (agent rebind keeps
  invalid_task_identity)

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

* PUCK-132: map persisted runtime access failures in clients

* fix(i18n): add missing fr translations for runtime_access_denied keys

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

---------

Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: worker-opencode <worker-opencode@multica.local>
Co-authored-by: puck-181 <puck-181@local>
2026-09-18 15:39:25 +08:00