5525 Commits
Author SHA1 Message Date
fa5d470ae4 MUL-7706 feat(wecom): every notice reads the language of whoever will read it (#8831)
* feat(wecom): every notice reads the language of whoever will read it

The adapter's user-visible strings were Chinese literals scattered across
replier.go, inbox_message.go and outbound_media.go, so an English-reading member
got Chinese notices whatever their profile said. copyPack already existed for
the bubble's five stream strings; this moves the rest into it and resolves the
locale from the destination rather than the deployment.

Who decides, per surface:
  - a 1:1 reply, the binding prompt, the inbox card: the reader's own profile
  - a group notice: the room, which reads the deployment's language
  - an attachment-failure notice: resolved on the request path and carried on
    attachmentTarget, because the failure happens on a detached goroutine with
    no context left to read a profile with

TestOnlyStringsGoHoldsUserVisibleCopy walks the package's AST and fails on any
Han literal outside strings.go. Two files are listed as pending with an exact
count: wecom_channel.go's unsupported-kind receipt and media_ingest.go's two
media notices, each moved by its own follow-up. The count is asserted both ways,
so the allowance cannot outlive the follow-up that consumes it.

* feat(wecom): every notice reads the language of whoever will read it

The adapter's user-visible strings were Chinese literals scattered across
replier.go, inbox_message.go and outbound_media.go, so an English-reading member
got Chinese notices whatever their profile said. copyPack already existed for
the bubble's stream strings; this moves the rest into it and resolves the locale
from the destination.

Who decides, per surface:
  - a 1:1 reply, the binding prompt, the inbox card: the reader's own profile
  - a group notice: the room, which reads the deployment's language
  - an attachment-failure notice: resolved on the request path and carried on
    attachmentTarget, because the failure runs on a detached goroutine with no
    context left to read a profile with

The wiring is the part that has to hold: router.go passes Languages, and
NewOutboundReplier warns when it is nil, because a missing lookup has no other
symptom — nothing errors, nothing is empty, every notice simply comes out in one
language. TestWecomReplierGetsItsLanguageLookupOnTheRealBootPath asserts it off
NewRouter itself, with the anti-vacuity check read as a delta between two
routers rather than a count, since chat:done has listeners of its own.

TestOnlyStringsGoHoldsUserVisibleCopy walks the package AST and fails on any Han
literal outside strings.go, naming the two files whose copy has not moved yet
with an exact count, asserted both ways.

* MUL-7706 docs(wecom): drop the duplicated stream-field docs, refresh the lint header

The restored StreamNoReply block carried its opening paragraph twice, and the
lint file's header still described two rules and a greeting after the second
rule was dropped.

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

* MUL-7706 test(wecom): compare two routers in the replier wiring guard

A bare chat:done count is non-zero whenever Slack or DingTalk is configured, so
the guard passed with the WeCom block never entered. Build one router without
the WeCom key and one with it, as wecom_bubble_wiring_test.go does, and require
the difference.

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

* fix(wecom): a relayed reply's attachment notice reads the reader's language too

deliverRelayed builds its own attachmentTarget and never set Locale, so
copyFor("") fell back to the deployment pack. On a multi-replica deployment
that is the common path, not the exception: chat:done lands wherever the run
finished, and only the lease holder can write to the socket — so an English
reader whose file fails to upload got the Chinese notice, while the same
failure on the direct path was already answered in English.

Nothing else differs when this is wrong. The file still fails, the notice still
goes out, and no existing test covered the relayed case.

Also two wording fixes against the product glossary: /issue creates an issue,
not a task, so IssueUsage now reads like Slack's; and the task_failed inbox
label is 'Run failed', matching what the web inbox shows for the same
notification.

* MUL-7706 test(wecom): keep each attachment-notice test under its own doc comment

The relayed test was inserted between the direct test's doc comment and its
function, so the direct test lost its comment to the relayed one. Put the
direct test back under its comment, follow it with the relayed test, and move
the util import out of the standard-library group.

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

* MUL-7706 test(wecom): the invoke-denied notice reads its one reader's language

A 1:1 refusal joins the per-outcome locale table; a group trigger's refusal,
sent to the sender's own 1:1, must read the sender's profile rather than the
room's deployment default.

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 23:00:52 +08:00
f76cde8e60 MUL-7766: feat(editor): fit Mermaid previews to show the whole diagram, with inline zoom (#8898)
The inline Mermaid preview now opens fitted: the whole diagram within the
column's width and at most 448px tall (the frame's collapse threshold less
padding), never upscaled past natural size. Zoom out / percent / zoom in /
fit buttons step from there; a zoomed diagram scrolls inside the same box
and can be drag-panned both ways. Tap still opens the full viewer.

- New useInlineZoom hook: measures the column, keeps a fitted preview
  fitted on resize, snaps through 100%, and zooms about the visible center.
- The SVG fills its iframe, so zoom resizes the iframe and redraws vectors
  instead of scaling a bitmap.
- Framed blocks get a floating pill bottom-right; the standalone editor
  preview puts the controls in its existing toolbar.
- The layout cache records the fitted height so the lazy shell reserves the
  space the preview actually takes.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 22:55:42 +08:00
54987006b5 fix(views): keep the PR row diff whole and spin the running-checks icon (MUL-7767) (#8900)
* fix(views): keep the PR row diff whole instead of cutting it mid-number

When the verdict pill needed the room, the diff after `repo#number` was
clipped by `overflow-hidden`, so `−486` showed as a bare `−` and `+312`
could read as `+31`. Everything after the dot is now one unit that wraps
onto a clipped second line when it doesn't fit, and the repo name is only
shown when the linked PRs span repos, so the diff fits at the narrowest
sidebar width in the common case. The tooltip keeps owner/repo#number.

MUL-7767

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

* fix(views): spin the running-checks icon in the PR verdict pill

The loader icon on a PR whose checks are running stood still. It now
spins (motion-safe), and stops when the snapshot is stale, since a stale
snapshot can't vouch that the checks are still running.

MUL-7767

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 22:55:16 +08:00
decd602a57 fix(views): stack wakeup condition hints under their labels (MUL-7769) (#8899)
The condition menu put each label and its hint side by side in a
22rem popup. English and French strings are too long for that row, so
labels and hints both wrapped and misaligned. Stack the hint under the
label, top-align the icon with the first line, and narrow the menu to
w-72 so every locale lays out the same way.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 22:47:02 +08:00
92649de29c fix(wakeup): scope test scheduler passes to their own workspaces (#8901)
CI runs every backend package concurrently against one database, and
IssueWakeupService.Tick dispatches every ready wakeup in it. The handler
child_done tests' runWakeupTick therefore dispatched rules owned by
concurrently running service tests, consuming their pending receipts
mid-assertion: TestIssueWakeupPendingEventsAreBoundedAndLegacyInputsDrain
failed on main with "unbounded pending receipts: 1" (reproduced locally
2/40 with the handler tests running alongside, 0/20 alone).

Give ListReadyWakeups an optional workspace filter and add
TickWorkspaces for tests; production Tick passes no filter and is
unchanged. Scope the handler helper, the busy-rule test and the expiry
candidate check to the workspaces they own.

Co-authored-by: J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 22:43:43 +08:00
13094caf7a MUL-7758: lay an issue's runs out in time (#8890)
* fix(issues): render run trigger snapshots as plain text (MUL-7758)

A run's trigger snapshot is the comment's raw Markdown cut at ~200 runes,
and the execution log printed it verbatim: an escaped ampersand read
"&amp;", and a comment that opens with a screenshot read
"![CleanShot 2026-…" because the cut lands inside the image URL.

Decode the entities in one pass, turn whole or cut images into a localized
"[Image]" label, and keep links' text, so every surface that lists runs
shows what the person actually wrote.

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

* feat(issues): lay an issue's runs out in time (MUL-7758)

The usage breakdown was a nine-column table of raw token counters headed by
a token total that is 99% cache re-reads and a "cache savings" figure larger
than the spend itself, with no way to tell when a run happened or to open
it. The execution log showed tokens where the question is cost, and kept
every past run behind a "Show past runs" toggle.

Execution log (sidebar):
- the header reads "21 runs · $166" and opens the new Runs dialog; narrow
  panels drop the count, never the cost's digits
- a spend strip draws one bar per past run, oldest to newest, height =
  cost, with a baseline dot for runs that recorded no usage
- agent time and elapsed time sit under it, active runs stay on top, the
  latest three past runs are listed with their cost, and "Open timeline"
  hands the rest to the dialog

Runs dialog (replaces the usage breakdown):
- spent / agent time / elapsed / runs, with failed and cancelled counts
- a cumulative cost curve over per-agent run lanes on one time axis, the
  run that moved the total most labelled, hover summaries on every bar
- runs grouped by day, newest first: who asked (quoted), which agent ran
  it, how long, a cost bar split by what was billed with the exact split on
  hover, and transcript / retry actions
- runs without usage are listed with "—" instead of being left to a
  footnote, so the run count no longer disagrees with itself

The timeline arithmetic lives in issue-run-timeline.ts with its own tests;
retry moves to a shared RetryRunButton so both surfaces retry the same way.

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

* feat(issues): open a run's transcript from its row (MUL-7758)

A Runs row cut its trigger to one line and opened the transcript only from
a small icon at the row's end, so a long ask could not be read anywhere
and the obvious click did nothing.

The whole row now opens that run's transcript; the trigger text is the
row's real button for keyboard and screen readers, and retry stays its own
action. The transcript dialog gains a "Trigger" line under its header that
shows the ask whole and wrapped, for every surface that opens it.

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

* revert(issues): open Runs rows' transcripts from the hover action again (MUL-7758)

Row-wide clicks were a response to the transcript icon looking permanent;
it only appears on hover, which reviewers found enough. Restore the
per-row icon and retry actions. The trigger keeps its full-text native
tooltip, and the transcript dialog keeps its Trigger line, so a truncated
ask is still readable in full.

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

* feat(issues): make the Runs chart answer the pointer anywhere (MUL-7758)

Only the lane bars carried hover summaries, and most are a few pixels
wide, so the chart read as static: hovering the curve did nothing.

The plot column now snaps the pointer to the nearest run and shows a
crosshair at its completion, the curve's reading there, and a card with
the ask, agent, time, duration, the run's cost and the total so far; the
other lane bars dim. Each run carries `costSoFar`, and
`nearestRunIndex` does the snapping, both with helper tests.

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

* fix(issues): reach nested runs and open cost splits without hover (MUL-7758)

Addresses the two review findings on the Runs dialog:

- Overlapping runs: the chart snapped by time alone and broke distance ties
  by start order, so a run fully inside a longer one could never be picked,
  even with the pointer on its bar. The innermost bar now wins a tie, and a
  pointer over a lane only considers that agent's runs.
- Cost split: the input / output / cache breakdown lived in a tooltip on a
  non-focusable span, out of reach for keyboard and touch. The cost cell is
  now a button that opens a popover on hover, click, tap or Enter.

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

* fix(issues): use radius tokens for the Runs chart marks (MUL-7758)

CI's radius check rejects fixed arbitrary radii outside its allowlist. The
lane bars, legend swatches and spend-strip bars used rounded-[2px]; they
now use rounded-xs (3px), the same step the transcript's run timeline
marks already sit on, rather than widening the allowlist.

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 22:41:52 +08:00
Xichang(Seacen) Zhao 4e2afed720 MUL-7710 fix(channel): refuse an inbound turn from a member who may not run the agent (#8838)
* fix(channel): refuse an inbound turn from a member who may not run the agent

The web chat checks the agent's invoke permission before it opens a session: a
private agent runs only for its owner, with no admin bypass. The shared channel
Router checked the sender's binding and workspace membership and nothing else,
so any member who had bound a chat identity could run another member's private
agent through its bot — on the owner's runtime, with the owner's credentials. A
1:1 message, a group mention, /issue and /new all got through, on every channel
the engine serves.

The Router now asks service.CanMemberInvokeAgent right after identity
resolution and before anything is stored, so a refused turn reaches no Chat, no
/issue and no later run's context. It judges the SENDER, never the installer who
owns a group's route — judging the installer is what would let this through.

MemberMayInvokeAgent is that existing function keyed by agent id, since the
Router holds the installation's agent id rather than the loaded row. An agent
that no longer exists admits nobody; a lookup that FAILED is an error rather
than a denial, so the dedup claim is released and the platform's redelivery is
still the message's chance.

A refusal is audited as invocation_not_allowed. WeCom answers a 1:1 in place; a
group trigger is answered in the sender's own 1:1 and the room hears nothing,
since a line there would tell everyone present which member was refused and
that the agent is someone's private one. Other channels stay silent, as they do
for a non-member.

* fix(channel): a failed permission lookup is not a denial

The policy swallowed two of its three query errors. CanMemberInvokeAgent
returns a plain bool, so ListAgentInvocationTargets failing came back as false,
and any error from GetMemberByUserAndWorkspace — not only pgx.ErrNoRows — read
as 'not a workspace member'. Only GetAgent's failure reached the caller.

For the scheduled triggers that shipped with it, failing closed is right: an
autopilot or an issue wakeup can wait for the next tick. For a channel turn it
is not. The Router reads false as a verdict, marks the message processed so the
platform's redelivery is discarded, and WeCom tells the sender they may not run
an agent they are in fact allowed to run — all from one transient error.

So the policy is now memberMayInvokeAgent, returning (bool, error):

  - (false, nil) stays a real verdict — no user id, not the owner of a private
    agent, not a target of a public_to one, and a member row that is simply not
    there, which is what pgx.ErrNoRows means here.
  - (false, err) is 'we do not know', and every other query failure produces it.

CanMemberInvokeAgent is now a thin wrapper that fails closed, so autopilot and
issue wakeup behave exactly as before. TaskService.MemberMayInvokeAgent returns
the error, and the Router releases its dedup claim on it.

TestRouter_RealServiceLookupFailure_ReleasesInsteadOfDenying drives the REAL
TaskService against a real database with one query failed at the SQL boundary,
because the router tests stub this at the interface and can only prove what the
Router does with an error it is handed — not whether the policy ever produces
one. It asserts Release rather than Mark, no invocation_not_allowed audit, and
no denial notice, for each of the two queries.

Also moves postPrivate's doc comment back to postPrivate: sendInvokeDenied's
was inserted above it, leaving one function documented by the other's text.
2026-09-27 22:38:51 +08:00
Bohan Jiangandmultica-agent 138134d341 fix(agent): pass Hermes custom args before the acp subcommand (MUL-7748) (#8882)
* fix(agent): pass Hermes custom args before the acp subcommand

Hermes only accepts its global flags (--provider, --yolo, -m, ...) ahead
of a subcommand, so the daemon's `hermes acp <custom args>` exited with
an argparse usage error before the ACP handshake and every run of a
Hermes agent with custom_args failed. Assemble `<prefix> <custom args>
acp` instead, keep the profile resolver and overlay stripping on the
same argv, and drop a trailing custom arg left without its value so it
cannot capture `acp` and hang the task in interactive chat.

Fixes #8878 (MUL-7748)

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

* fix(agent): keep acp's own flags after the Hermes subcommand

Hermes' acp subparser declares --accept-hooks, --version, --check,
--setup, --setup-browser and --yes/-y, and argparse only accepts them
after the subcommand: moving them ahead of `acp` turned an agent with
custom_args ["--yes"], which launched before, into a usage error.
Lay custom args out around `acp` instead — global flags (with their
values) before it, acp's own flags after it — and map profile
stripping back through that layout so custom args keep their
configured order.

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

* fix(agent): resolve Hermes flag abbreviations like argparse

Hermes keeps argparse's allow_abbrev, so custom args must be classified
the way its parsers resolve them, not by string equality. Behind `acp`
a `--y` is `--yes`; moved in front, the root parser reads it as
`--yolo` and turns off dangerous-command approval, while `--ye` went
from valid to a usage error. Keep every token the acp subparser would
take as its own option (exact, abbreviated or ambiguous) behind the
subcommand, and treat abbreviated value flags (`--prov`) as value flags
when pairing and when guarding against a bare flag capturing `acp`.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 22:21:50 +08:00
3d2592a9a2 MUL-7754: feat: local search index for web and desktop (#8891)
* feat(server): add a change log and sync endpoints for local search (MUL-7754)

Web and Desktop will keep a per-workspace copy of issue, comment, and
project text for instant search. Triggers on issue/comment/project record
the xid of each entity's latest write in search_index_change, covering
every write path including hard and cascaded deletes. Clients bootstrap
with GET /api/search-index/manifest + /snapshot pages and catch up with
POST /api/search-index/changes, whose cursor is a pg_snapshot: a change is
returned once it is visible in the target snapshot but not in the one the
client holds, so late commits are never skipped and long transactions do
not stall catch-up.

Workspace teardown skips the triggers and clears the table, and an hourly
scheduler job prunes rows older than 30 days; a cursor older than the
prune mark gets 410 and rebuilds.

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

* feat(core): add a synced local search index for web and desktop (MUL-7754)

Each (user, workspace) gets an IndexedDB copy of its issue, comment, and
project text, kept in a worker (SharedWorker when available, otherwise a
dedicated worker per tab) and searched in memory. Matching, ranking, and
snippets port the server's issue/project search, so local rows have the
server's shape and order.

The worker bootstraps from /api/search-index/manifest + snapshot pages,
catches up through /changes after realtime events, reconnects, focus, and
every five minutes, and resumes an interrupted bootstrap. It has no
credentials of its own: tabs run its requests through their API client.
Local results are served only while the copy is complete and recently
confirmed; otherwise searchIssues/searchProjects fall back to the server.
Workspaces over 500 MB of text, lost access, and expired cursors are
handled by declining, wiping, and rebuilding respectively. Session cleanup
deletes every local index.

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

* feat(views): search through the local index first (MUL-7754)

The command palette, @mention suggestions, the issue picker, and duplicate
suggestions now call the core local-first search. The palette drops its
300 ms debounce while the local index is serving, since no request leaves
the tab.

An E2E spec checks that palette results match server ranking without any
server search request, follow issues created and deleted through the API,
and that logging out deletes the local copy. .env.example documents the
FF_LOCAL_SEARCH_INDEX kill switch.

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

* fix(core): close local search gaps found in review (MUL-7754)

- Access loss: deleting, leaving, or being removed from a workspace now
  destroys its local copies right away (worker and tab both delete the
  database, since a full-page relocate may end the worker first). The sync
  hook also prunes every copy outside the user's current workspace list,
  which covers access lost while offline or on another device; copies a
  tab is showing are kept in case the list is stale.
- Memory budget: the engine counts UTF-8 bytes the way the manifest does,
  and the budget is checked on every load, snapshot page, and catch-up
  batch, not only at bootstrap. An over-budget copy is dropped and search
  falls back to the server until the workspace is measured again.
- Frozen tabs: a port the sweep dropped is restored with its workspace the
  next time the tab sends anything, a page restored from the bfcache
  attaches again, and a null local answer clears the tab's "serving" flag
  so the palette goes back to debounced server search.

E2E now also covers being removed from a shared workspace.

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 22:02:13 +08:00
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
4054d57802 MUL-7755: feat(issues): configurable side peek in every issue view (#8886)
* feat(issues): add a peek variant to IssueDetail

A single-column layout for the board's side peek: the core properties
render as a row of pills under the title (the create dialog's PillButton
with the sidebar's pickers), the properties sidebar and its toggle are
dropped, and the host supplies the header's leading and trailing
controls. The peek keeps its own scroll key and always opens at the top.

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

* feat(issues): open board cards in a side peek on Shift+Click (MUL-7755)

Shift+Click (or Space on a focused card) opens the issue in a floating
panel over the board, so it can be triaged without leaving the board.
Shift+Click another card switches the panel; on the same card, or Esc,
it closes. J / K step through the card's column, the peeked card keeps
a brand ring, and the board scrolls it clear of the panel. The page
header and toolbar stay usable, and the panel stops above the chat
launcher.

On web, Shift+Click on a board card no longer opens a browser window;
Cmd/Ctrl(+Shift)+Click still open tabs. Board and swimlane only.

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

* feat(issues): animate the side peek and peek the hovered card on Space

The panel now enters and exits through motion/react with the shared UI
easing: a 200ms fade and 16px slide in, a shorter 150ms fade and 8px slide
out, fade-only under reduced motion. The key is stable, so switching
issues (J / K, Shift+Click another card) swaps content without replaying
it, and the exiting panel stops taking clicks and keys at once. The
sideways board scroll that keeps the peeked card in view also respects
reduced motion.

Space now peeks the card under the pointer as well as a focused card,
unless a control has focus and owns Space itself.

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

* feat(issues): side peek in every issue view, with H / L and arrow keys

Shift+Click (and Space on the hovered issue) now opens the side peek from
list rows, table rows, gantt rows and swimlane cards as well as the board.
The host wraps every view and its loading states, so an open peek survives
a view switch; each view publishes its own order:

- board: its columns; swimlane: one column per status, running down
  through the expanded lanes; list, table, gantt: one column in display
  order, skipping collapsed groups.

H / L step to the nearest non-empty column at the same row (clamped), J / K
within it. The arrow keys mirror them unless the reader last clicked into
the panel, where they scroll it as usual; tabs, radios and sliders keep
their own arrows.

Peeked rows get a brand tint and leading bar; table cells carry it because
pinned cells paint over the row. DataTable gains a generic getRowProps for
that. The swimlane scroller reserves room for the panel like the board's.

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

* fix(issues): keep the peek closable in every state, and follow moved cards

Review of #8886:

- The peek's close button lives in IssueDetail's trailing controls, which
  the loading and not-found early returns dropped, leaving only Esc. Both
  states now render the host's trailing controls, and the panel's error
  boundary gets a fallback with its own close button.
- Keep-in-view keyed on the peeked card's neighbours, so a card alone in
  its column that moved to an empty column never scrolled back into view.
  It now acts whenever the element standing for the issue is a new one
  (another issue, a column move, a view switch) and still ignores reorders
  that leave it in place.

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

* feat(issues): make default card opening configurable (MUL-7755)

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

* feat(settings): move the issue-opening preference into the new Preferences page

Main folded the Preferences tabs into one page (MUL-7732), deleting the
issue tab the preference had been added to. It now lives there as its own
"Opening issues" section, in the page's own terms:

- device scope badge, like Appearance, since the choice is stored per device
- the either/or SegmentedToggle Theme uses, instead of a two-item dropdown
- no success toast: the new page announces only failures
- a settings-search entry, also matched by its option names

Clicks now mean "the other one" with Shift: a plain click opens the
preferred target, Shift+Click the other. In side-preview mode that makes
the full page one click away again (before, Shift also peeked, leaving only
the panel's button or a new tab); a link would otherwise hand Shift to the
browser, so the hook navigates in place itself. The row's hint says so.

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:07:49 +08:00
32a396fd52 fix(views): stop dialog scroll lock from re-rendering Mermaid diagrams (MUL-7760) (#8889)
useThemeVersion bumped on any class/style/data-theme mutation on <html>/<body>.
Base UI's scroll lock writes `overflow` into that style on every dialog open and
close, so each close re-rendered every Mermaid diagram on the page: a 140-200ms
long task (dev build, three diagrams) starting ~30ms into the 100ms exit
animation, which stalled the dialog's unmount and made the page flash.

The hook now keeps a theme signature (theme-selecting attributes plus the
resolved tokens, color-scheme and font) and only bumps when it changes. Tokens
written straight into the root style still count as a theme change.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 19:22:12 +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
e427ed7dfb fix(views): keep the Save as view name while the host re-renders (MUL-7756) (#8885)
SaveViewDialog re-seeded its draft (name, filters, display) whenever the
`scope` / `editView` prop identity changed. The My Issues header builds
`scope` inline, so any background refetch re-rendered it and wiped the
half-typed name. Seed once per open instead, so every host is safe.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 18:13:58 +08:00
e8a16d334f MUL-7753: one verdict per PR in the issue sidebar (#8884)
* feat(core): derive one verdict per pull request (MUL-7753)

Fold CI status and mergeability into a single prioritized verdict for the
issue sidebar, keeping a conflict alongside a failure so neither is lost.
Add helpers to drop the issue's own key from a PR title and to shorten
diff counts for the narrow sidebar.

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

* feat(views): lead each sidebar PR row with one verdict (MUL-7753)

Each PR now reads title, then repo#number with the diff size and a verdict
pill aligned right, so several PRs scan as a column. Failed PRs list what
failed underneath, one line per check. Owner, author, the full title and
exact counts move to the row tooltip; the section header shows a count.
Drop the Open / Unstable / Has hooks labels and the failed-names sentence.

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:02:50 +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
Bohan Jiangandmultica-agent 82847e2751 fix(chat): keep tool rows rendering when MCP input fields are objects (MUL-7708) (#8883)
* fix(chat): keep tool rows rendering when MCP input fields are objects (#8835)

The chat step fold summarized a tool call with its own helper that cast
the input to Record<string, string>. treg's MCP call tool passes query as
an object of URL parameters, so the summary returned that object, React
threw on an object child, and expanding the settled fold crashed the chat.

Reuse traceToolArgSummary, which the run transcript already uses and which
only accepts string values, and drop the duplicated helper.

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

* fix(chat): localize the multi-file patch summary in tool rows

Pass the transcript's patch_summary_more phrasing to traceToolArgSummary
so non-English chats no longer fall back to "a.go +1 more".

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 15:58:10 +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
importcpp 04cdd4857d fix(notifications): ignore malformed member mention IDs (#8876) 2026-09-27 13:32:11 +08:00
importcpp bd7308742c fix(plugins): avoid hook tool name collisions (#8856) 2026-09-27 13:17:01 +08:00
3125bd2ea2 feat(comments): show every attached image at full size (MUL-7736) (#8870)
Several images under a comment (or a chat message) were packed into
justified rows between 100 and 240px tall, so a screenshot shrank to a
thumbnail and reading it took a click into the viewer; tall screenshots
were also cropped to fit the row.

Each image now shows at its full size, one under another, the way a lone
image already did: a wide screenshot fills the column, and every image is
capped at 36rem tall so a phone screenshot keeps its shape instead of
running the length of the page. Files other than images stay cards in a
grid below.

JustifiedImageRow and its layout helper have no other caller and are
removed, along with the .image-tile styles.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 04:44:59 +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
bb40537c59 fix(issues): keep the run spinner turning after a steer moves it (MUL-7729) (#8871)
Sending a steer reply moves the running agent's block below the new
message. That keyed move detaches and reinserts the row, and Electron
reports both changes in one IntersectionObserver callback: first the
detached row (not intersecting), then its new place (intersecting).

useRunAnimationVisibility read only the first entry, so it paused the
spinner as if the row had left the viewport. Nothing changed afterwards,
so the spinner stayed frozen at its start angle for the rest of the run.
Read the last entry, which is the current state.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 04:25:10 +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
4f2fdc0b15 MUL-7733: more room around dynamic blocks, actions on hover (#8867)
* fix(comments): more room around dynamic blocks, actions on hover (MUL-7733)

A ```html or ```mermaid block sat 12px from the prose around it, barely
more than a paragraph break (10px), so a bordered card with a title bar
read as crowded against the text. It now gets 20px above and below.

The margin moves from the block to the lazy shell that wraps it. A
child's bottom margin cannot collapse through a box with a min-height,
so on the block it sat inside the reservation; on the shell it collapses
with the paragraphs on both sides and the prose first/last-child reset
still applies.

The title bar keeps the icon, title and kind chip, but Preview | Source,
fullscreen and copy now fade in while the block is hovered or holds
focus. They use opacity, so the bar does not reflow and the controls
stay in the tab order. They stay up in the source view (Preview is the
way back) and while the copy confirmation shows, and always show where
there is no hover.

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

* fix(mermaid): keep a wide diagram's left edge reachable (MUL-7733)

The diagram scroller centered its iframe with justify-content: center.
When the diagram is wider than the column, that pushes its left part
past the scroll origin, where it is cut off and cannot be scrolled to:
a four-step LR flowchart in a comment lost its first node. The iframe
is now centered with auto margins, which never go negative, so a small
diagram stays centered and a wide one starts at its left edge and
scrolls to the right.

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:11:19 +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
Xichang(Seacen) Zhao 7551ff1ddc fix(wecom): answer an unreadable message once per message, not once per delivery (#8836)
WeCom redelivers a callback it did not get an ack for, and this receipt is sent
from dispatchFrame — before c.handler, so the Router's own Claim never runs for
it and nothing downstream deduplicates it. Unclaimed, every redelivery of one
unreadable photo puts another apology in the chat, and each copy spends one of
that conversation's active pushes.

The claim is the same two-phase token the Router uses, on the same table and the
same (installation, message) key, so a message answered here can never also be
answered there. NewDeduper is exported for exactly that reason: router.go and
NewResolverSet now build it the same way instead of each reaching for the
private type.

Three outcomes, chosen so the user's worst case is today's behaviour: already
claimed says nothing; sent marks it; a FAILED send releases, because holding the
claim over a receipt that never arrived turns one failed send into permanent
silence.

The wiring is the part that has to hold. RegisterWecom warns when Dedup is nil,
since a missing one has no other symptom, and two tests cover the two ways it
can go missing: TestRegisterWecomCarriesTheDeduperIntoTheChannel drives
newWecomFactory, and TestWecomChannelGetsItsDeduperOnTheRealBootPath reads
NewRouter itself with the anti-vacuity check taken as a delta between two
routers.
2026-09-27 02:05:04 +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
Vladimir Babin f1560daff3 fix(search): fold/unfold all comments on identifier URLs (#8842)
The command palette keyed the collapse stores by the raw URL segment (e.g. MUL-42), while CommentCard reads them by the issue UUID, so Fold/Unfold All Comments did nothing on identifier URLs. Use the resolved issue id instead.

Closes #8834
2026-09-27 00:17:34 +08:00
400791262f build(views): upgrade Tiptap to 3.31.3, fixing select-all + delete (MUL-7725) (#8860)
* build(views): upgrade Tiptap to 3.31.3 (MUL-7725)

Move all @tiptap/* packages from 3.27.1 to 3.31.3. Beyond the
select-all + delete fix (MUL-7725, @tiptap/core 3.29.0), this picks up:

- security fixes: prototype pollution through mergeAttributes (3.30.4),
  a Markdown attribute parsing DoS (3.30.5), and prosemirror-view
  >= 1.42.3 for a paste XSS (3.31.2)
- deleteSelection over multi-range selections such as table cells,
  input rules crashing next to mention atoms, and the blockquote
  Backspace crash at a leading image
- Markdown parsing fixes for lists followed by headings, rules, code
  fences, or bullets, and for `||` inside table code spans

Stop at 3.31.x rather than 3.29.x: StarterKit only pins its bundled
@tiptap/* dependencies to exact versions from 3.30.0, so a 3.29 pin
still resolves most of them to the newest release.

@tiptap/markdown still parses an empty list item into a childless
listItem, so repairEmptyListItems stays. Only the pre-repair selection
changed: the caret now lands on the following block rather than in an
AllSelection. Update the test precondition and comments to match.

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

* test(editor): pin the caret after select-all + delete (MUL-7725)

Cmd+A then Backspace/Delete used to leave the emptied document under an
AllSelection, painted as a lone selected space with no caret. Tiptap
3.29 fixed deleteSelection upstream; guard it through our production
keymaps for both the content and the single-line title schemas.

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

* fix(editor): park the caret when a composer clears after send (MUL-7725)

Composers clear through ContentEditorRef.clearContent after a send.
Tiptap's clearContent maps the selection across the replace, and an
AllSelection maps onto itself, so a send right after Cmd+A left the
emptied composer "all selected". Surfaces that refocus after sending
(thread replies, chat, quick create) then painted that selection over
the empty line as a lone selected space: the MUL-7725 symptom through
a path the upstream deleteSelection fix does not cover.

Set the selection to the start after clearing.

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 00:14:45 +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
Bohan Jiangandmultica-agent 12f8f3f311 feat(agent): list Claude Opus 5.5 and GPT-6 Sol/Luna in the fallback catalogs (#8840)
Since the fallback catalogs only feed the model picker (#8811), listing
current models there is cheap. Add the models the installed CLIs now
advertise but the static lists were missing:

- Claude: claude-opus-5-5 (Claude Code 2.1.280 list_models default)
- Codex: gpt-6-sol (low..ultra) and gpt-6-luna (low..max), both default
  medium, from codex-cli 0.155.1 `debug models`; live discovery labels
  them "GPT-6 Sol" / "GPT-6 Luna" like GPT-6 Astra

MUL-7691

Co-authored-by: multica-agent <github@multica.ai>
2026-09-25 14:43:00 +08:00
Xichang(Seacen) ZhaoandBohan-J 005462ec22 MUL-7705 docs(channels): correct comments main's own later changes made false (#8833)
Three comments described behaviour that later changes on main replaced.
Comments only; no behaviour change.

- slack/typing_indicator.go: EventTaskFailed carries the chat session id on
  the envelope like EventTaskCancelled (both go through service.taskEvent),
  not only in the payload map. chat:done's payload is a ChatDonePayload
  struct, so the helper's map read only ever matches the two task events.
- wecom/types.go: outbound no longer needs a single replica when a
  sharded/dual realtime relay routes off-lease replies to the lease holder
  (#7429); legacy relay mode or no Redis still does. Also documents the
  per-chat quota gate (#8345): a slight overage is delayed, a burst past the
  caller's wait budget is refused before the write.
- cmd/server/router.go: the boot-time SINGLE-REPLICA CONSTRAINT note now
  states the relay-dependent topology.

Co-authored-by: Bohan-J <bhjiang@outlook.com>
2026-09-25 14:36:02 +08:00
Bohan Jiangandmultica-agent 1aa815a738 fix(daemon): stop validating saved model settings against fallback catalogs (#8811)
When model discovery fails, providers substitute a static catalog so the
picker stays usable. The daemon then validated saved thinking levels and
service tiers against that stand-in, silently dropping values the static
list did not know about — which forced hand-maintained model and per-model
effort tables to chase every new release.

Only a verified catalog (discovered, non-empty) may now reject, rewrite, or
drop a saved model, thinking level, or service tier. Fallback and empty
catalogs pass the saved values through to the CLI for every provider. The
checks that describe the binary rather than its models stay: a Claude CLI
without --effort (or without the saved value in its --effort list) still
drops the level, and Codex's explicit standard tier still follows the CLI
version.

- Catalog.Verified / Catalog.CLIThinkingLevels; missingFromFallbackCatalog removed
- claudeModelEffortAllow removed: the fallback picker offers the --help superset
- ModelKnownIncompatibleWithProvider no longer consults static lists (prefix-only, same result)

MUL-7691

Co-authored-by: multica-agent <github@multica.ai>
2026-09-25 14:33:58 +08:00
hutiefang76 570f8f82a1 feat(projects): show descriptions in repository pickers (MUL-7575) (#8668)
Closes #8662
2026-09-25 14:31:16 +08:00
dbfc014bdd MUL-7610: fix(autopilot): avoid loading task history during webhook recovery (#8710)
* fix(autopilot): avoid loading task history during webhook recovery

* refactor(autopilot): move webhook recovery task check into agent.sql

Rename HasTasksByIssue to HasTaskForIssue and place it beside the other
per-issue task existence queries. Group test imports.

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-25 14:30:50 +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
e2f4a22030 feat: steer a running agent from the normal composer, per recipient (MUL-7631) (#8760)
* feat(server): steer running turns from ordinary comments (MUL-7631)

A comment can now name recipients whose running turn should receive it
(`steer_agent_ids` on POST /issues/:id/comments). Each named recipient
with a running, steer-capable turn gets the comment bound to that turn
instead of a follow-up run; any other recipient keeps its normal
queued / coalesced / deferred trigger, so losing a race never drops input.

- One comment can steer several turns: receipts are keyed by
  (comment_id, task_id) and returned as `supplements` on comments and
  timeline entries. The single supplement_* fields mirror the first
  receipt for older clients.
- Completion reconciliation skips a steered comment only for the agent
  it steered, so the same comment still reaches recipients that were
  addressed without steering.
- Delivery claims match the (comment, run) pair.

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

* feat(issues): choose per recipient how a message reaches a running agent (MUL-7631)

Steering moves from a separate "Add message" box on the run row into the
normal reply and comment composers. Recipients keep the existing trigger
rules; each recipient's current run on the issue decides what the message
can do:

- running (and steer-capable): add to the current run (default in that
  run's own thread), start after this run, stop and start over, or skip
- queued: include when it starts, or skip
- idle: start on send, or skip

Receipts follow the message ("Waiting for Lambda to read it" → "Read by
Lambda · after step N"), a failed delivery offers retry or sending it as
a new run, and the run's step list marks where it read the message.
A draft whose recipient finished meanwhile stays in place and says it
will start a new run; a message with files is handled after the run.

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

* fix(server): steer only the chosen turn, and make a retried steer idempotent (MUL-7631)

- Steering names the exact running turns the author chose
  (`steer_task_ids`). A chosen turn that ended before the send arrives is
  never swapped for a later turn of the same agent; that recipient keeps
  its normal trigger.
- A steering send carries `client_request_id`. A retry after a lost
  response returns the original comment (200) instead of creating a second
  comment and a second delivery; a concurrent twin binding the same request
  is treated as already steered.

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

* fix(issues): guard steering sends against stale previews and broad resends (MUL-7631)

- "Stop and start over" never stops a run while the trigger preview is
  still catching up with an edited @mention: the stop is dropped unless the
  preview answers the submitted text.
- Steering sends the chosen turns' task ids and one request id per draft
  text, reused on retries of the same text.
- "Send as a new run" on a failed receipt reaches only that receipt's
  agent: every other current recipient is suppressed.

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

* fix(issues): keep the reply box placeholder neutral (MUL-7631)

The empty reply box cannot know who a reply goes to: the author may
@ someone else or only talk to a teammate. The recipient chip already
says what Send will do once there is text.

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

* test(issues): give steering composer tests room for the preview debounce (MUL-7631)

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

* feat(issues): choose the default reply to a running agent (MUL-7631)

Preferences > Comments gains "When replying to a running agent":
"Add to current run" (default) or "Start after this run". It only
changes what a reply does without a per-message choice; the recipient
chip still offers both, and replies that never steered by default
(another thread, an idle or queued agent) are unaffected. Saved on this
device next to the sticky comment bar.

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

* fix(server): save one comment per send and honor "don't start" on replay (MUL-7631)

Idempotency now lives on the comment, not the delivery receipt. A send's
client_request_id is stored on the comment behind a partial unique index
(migrations 550/551), so a retry, or a concurrent twin, returns the saved
comment (200) whether the send steered a running turn or fell back to a
normal trigger. Before, a twin could save a second comment, and a send whose
turn had ended kept no receipt to find.

The agents an author chose not to start are stored on the comment too, and
completion replay skips them. "Send as a new run" for one missed recipient,
and "Won't start this time", no longer wake the other agent once its current
run completes. An edit re-records the choice.

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

* fix(issues): repeat the original steering request on retry (MUL-7631)

Until a send is settled, retrying the same text sends its first steering
request unchanged, even after the chosen turn ended. The server then returns
the comment the first attempt saved instead of posting it again as new input.

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

* fix(migrations): fail fast on locks in 549 and 550 (MUL-7631)

Both take an ACCESS EXCLUSIVE lock on a table the product touches
continuously: comment for every read and write, task_supplement for every
daemon claim. Bound lock acquisition and execution, as 483 does for issue,
so a long transaction makes the migration fail and retry on the next run
instead of queueing all traffic behind it.

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

* fix(server): steer at most one turn per comment during rollout (MUL-7631)

Steering is live, and servers from before per-run receipts claim every
receipt of a comment at once. While they still serve daemons during a
rollout, a comment bound to two turns could have the second copy marked in
flight and never delivered. Until every server claims per (comment, run),
a comment steers the first chosen turn only; later recipients keep their
normal trigger. A follow-up lifts the limit after this ships.

Also covers a steer_task_ids entry that names another issue's turn.

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

* fix(server): finish a saved send on retry; keep an edit's choices with its text (MUL-7631)

A send's comment was saved before its agents were started or steered, and
deduplication treated the saved comment as the whole send. A dropped
connection cancelled the request between the two, and the retry only
returned the comment, so no agent ever received it.

- Attempts of one logical send now run one at a time under a transaction
  advisory lock, held on its own connection: a twin waits for the attempt in
  progress (up to 30s, then 409) instead of racing it, and a process that
  dies releases it.
- After the comment is saved, dispatch runs on a non-cancelable context, and
  comment.client_request_dispatched_at (migration 552) records that it
  finished. A retry that finds a saved but undispatched comment finishes the
  dispatch with the comment's recorded choices.

An edit's "don't start" choices were written by a separate statement after
the text, so interleaved edits could leave an older edit's choice. They are
now part of UpdateComment, under the same revision check as the text.

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

* fix(issues): offer one steered recipient per message during rollout (MUL-7631)

The server steers at most one turn per comment until #8800, but the
composer showed "Add to current run" for every running recipient and the
server quietly queued the rest. Now only one recipient steers: the one the
author picked, otherwise the first that would by default. The others show
"Start after this run", and picking "Add to current run" on one of them
moves the steer there.

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

* fix(server): claim a send before dispatching it; drop the request lock (MUL-7631)

The previous recovery marked a send dispatched only after reaching its
agents, so a failure after dispatch made a retry reach them all again. It
also held a pooled connection per send for an advisory lock while the send
needed more from the same pool, which could wait on itself until timeout.

A send's comment now holds a dispatch claim instead
(comment.client_request_dispatched_at, one conditional update): only the
attempt whose claim takes effect publishes the comment and reaches its
agents. A failed claim writes nothing else and returns 500 for the client
to retry; a retry that finds an unclaimed comment claims and dispatches
it; a claimed one is only returned. Concurrent attempts no longer wait on
each other, and post-save dispatch keeps a 30s bound of its own.

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

* feat(server): let one comment steer several running turns (MUL-7631)

Lifts the rollout limit from #8760. Every server now claims receipts per
(comment, run), so a comment can be bound to each running turn its author
chose, and each recipient receives it in its own run.

Merge only after #8760 is deployed everywhere.

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

* feat(issues): steer every running recipient a message addresses (MUL-7631)

The client side of lifting the rollout limit: each running recipient can
take the message in its current turn again, instead of only one per
message.

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

* refactor(issues): drop send-retry idempotency and two display extras (MUL-7631)

A steering send now behaves like any other comment when its response is
lost: a retry posts it again. The machinery that tried to make that retry
return the saved comment grew with every edge it met, and ordinary comments
never had it:

- Remove comment.client_request_id, its unique index, and the dispatch
  claim (migrations 551/552, and 550 reduced to suppressed_agent_ids).
- Remove the replay/resume path in CreateComment, the post-save
  non-cancelable dispatch context, and the client's sticky request id.
  Steering sends only `steer_task_ids`; each binding uses the comment id
  as its receipt request id.

Two display extras go too, since the UI already shows the same thing:

- The "run ended while you were writing" notice: the recipient chip
  already switches from "Add to current run" to its new action.
- "after step N" on a read receipt: the run trace already places the
  message at the step where it was read.

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

* fix(issues): space agent names in Chinese copy; neutral recipients header (MUL-7631)

- The steer badge joined names with Intl.ListFormat, which gives
  "Lambda和Orion" in Chinese. Latin names are now spaced from the Chinese
  joiner ("Lambda 和 Orion"), per the zh copy conventions; the enumeration
  comma stays unspaced.
- The multi-recipient popover was still titled "Will start when sent" and
  its tooltip said "choose who runs", though each row now picks how that
  agent handles the message. Title it "Recipients" and reword the tooltip.

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

* fix(issues): translate the recipient source labels in Chinese (MUL-7631)

"assignee" and "@mention" were left in English in zh-Hans; the recipient
menu header shows them. Use 负责人 (per the zh conventions) and @提及.

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-25 04:42:26 +08:00
1c908ea52c feat(issues): state a failed or cancelled run once in the thread (MUL-7692) (#8813)
* feat(issues): state a failed or cancelled run once in the thread (MUL-7692)

A run that ended in failure rendered as two stacked components: the
platform's system comment (the raw daemon error, shown as if the agent had
said it) with its own "Retry run" button, then the run line with "Failed",
the duration, and a red reason. One outcome was stated three ways, and a
failed row with failure_reason=cancelled read as both failed and cancelled.

- The run's own failure notice no longer renders as a comment. The run
  block takes its slot (keeping the notice's deep-link id and highlight),
  in a thread and at the top level. A top-level notice that people replied
  to keeps its thread header and drops only the raw body and its button.
- The run line states the outcome once: icon + reason (or who cancelled),
  then View activity, duration, and a single Retry run in the slot the
  running row's Stop occupies. The raw error moves to a tooltip on the
  reason.
- Cancellation reasons (cancelled, manual, user_cancelled) read as
  cancelled with the neutral Ban icon; red is reserved for real failures.
- Failures that need a configuration change (auth, quota, missing config
  or CLI, unavailable model, private runtime) keep the raw error in view
  under the row.

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

* fix(issues): keep a retry below the attempt it retries (MUL-7692)

A working run in a thread was spliced directly after the input it
answers. A retry answers the same input as the failed attempt, so while
it ran it sat above that attempt's failure row, out of order.

Place a working run after its input, after earlier runs on that input,
and after any row that happened before the run was enqueued. Later
comments still go below it, as MUL-7632 requires.

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 18:29:01 +08:00
cf2b84708d fix(release): replace assets on same-tag reruns (MUL-7683) (#8812)
* fix(release): replace assets on same-tag reruns

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

* fix(release): allow late desktop asset replacement

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-24 18:25:01 +08:00
07059303f4 MUL-7682 feat(issues): give every reply a tick on the thread rail (#8809)
The quick-jump rail only reached thread roots, so a long agent thread
could not be entered at the reply that mattered. Each reply now gets a
short tick under its thread's long tick, and the hover outline lists every
reply (avatar, first line, time, resolution mark) under its thread.

Jumping to a reply first undoes whatever hides it: the reader's own
collapse, a root resolution that folds the thread into a bar, or a reply
resolution that folds the other replies. The thread is mounted through
Virtuoso, the reply is aligned 16px below the top of the viewport (clear
of a pinned resolve-collapse bar), and it flashes like other jumps.

Past 80 comments the rail keeps one tick per thread, while the outline
still lists every reply. The rail layer moves to z-30 so the outline
paints over the sticky collapse bars that these jumps now pin.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-24 17:38:24 +08:00
mrlonely 79b9327837 fix(issue): bind update attachments safely (#8588) (#8766) 2026-09-24 17:37:32 +08:00
Bohan Jiangandmultica-agent ff8b285497 fix(runtimes): price Claude Opus 5.5 at its own tier (#8808)
Opus 5.5 had no pricing row. The dashboard resolver found no match for
`claude-opus-5-5` and showed its spend as $0, while the backend's
unanchored `claude-opus-5` rule matched it and billed it at Opus 5's
5/25 tier.

Add Opus 5.5 at Anthropic's published rates ($4 input / $20 output /
$5 5m cache write / $0.20 cache read, 0.05x input) to both tables, and
end the backend Opus 5 rule at `claudeVersionEnd`, the same way the
Fable 5 / 5.1 pair is split, so neither Opus rule can take the other's
ids.

Co-authored-by: multica-agent <github@multica.ai>
v0.5.3
2026-09-24 17:24:14 +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
Kevin Kwok ec70b224d7 MUL-7675: feat(grok): support live-run steering via Grok Build ACP (#8796)
* feat(grok): support steering active Grok Build runs

* test(grok): cover steering before first agent output

* fix(grok): bound interject acknowledgement time
2026-09-24 16:43:10 +08:00