Commit Graph

4044 Commits

Author SHA1 Message Date
Lambda
ec2c24611c Merge remote-tracking branch 'origin/main' into agent/lambda/d8ef233f
# Conflicts:
#	packages/views/issues/components/comment-input.tsx
2026-07-12 14:56:37 +08:00
Jiayuan Zhang
b64ebd60b5 feat: add customizable keyboard shortcuts (#5294) 2026-07-12 14:50:36 +08:00
Lambda
b26436f7a9 refactor(settings): move the sticky comment bar toggle to Settings > Preferences (MUL-4435)
Per review feedback: pinning the comment bar is a low-frequency choice, so
the toggle moves off the bar itself into Settings > Preferences as a Switch
row. The composer keeps reading the store for the sticky behavior and the
40vh editor cap; the pin button and its issues-locale tooltips are removed.

Co-authored-by: multica-agent <github@multica.ai>
2026-07-12 14:32:25 +08:00
Bohan Jiang
9d453fac1e fix(comments): clarify 409 for top-level comment from a comment-triggered task (MUL-4417) (#5292)
* fix(comments): clarify 409 when a comment-triggered task posts a top-level comment (MUL-4417)

A comment-triggered task that posted a parentless top-level comment on its
own issue got a 409 whose message named the required parent id but never
said top-level comments are disallowed. Agents misread it as the issue being
locked and deleted good replies trying to reset. Keep the guard (agents must
reply under their trigger comment), but make the error self-explanatory and
document the constraint in the CLI --parent help. Add handler-level tests
pinning the rejected top-level case and the allowed reply-under-trigger case.

Refs GH #5266.

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

* fix(comments): tighten 409 wording and assert the fix hint (MUL-4417)

Review nits on #5292: drop the inaccurate "while it is active" phrasing and
the redundancy from the 409 message so it matches the actual allow-set
(trigger or coalesced comment); collapse the incident narration to one line;
and assert the actionable parent_id (--parent) hint in the regression test so
the guidance can't be dropped silently.

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

---------

Co-authored-by: J (Multica agent) <agent-j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-12 14:23:30 +08:00
Lambda
53900830d1 feat(issues): add sticky setting for the issue comment bar (MUL-4435)
The bottom comment composer can now pin itself to the scroll viewport so
it stays reachable while reading a long timeline. A pin toggle on the bar
itself persists the preference (default: on) via a new localStorage-backed
useCommentComposerStore. While pinned, the editor area is height-capped so
long drafts scroll internally instead of covering the timeline.

Co-authored-by: multica-agent <github@multica.ai>
2026-07-12 14:08:44 +08:00
Jiayuan Zhang
3bd69bdaa2 MUL-4432: add DM button on agent detail page (#5289)
* feat(agents): add DM button on agent detail page

The header gains a DM action next to Assign work. It shares the MUL-3963
invocation gate with assignment: allowed users land on the Chat tab with a
fresh compose bound to the agent via a new one-shot ?agent=<id> deep link
(consumed once the permission-filtered agent list resolves, then stripped);
denied users get an explanatory toast instead of a hidden affordance.

Closes MUL-4432

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

* fix(chat): harden the ?agent= deep link against races and undetermined permissions

Review follow-up for the DM button (PR #5289):

- An explicit user action (thread select, archive, manual new chat) now
  supersedes a still-pending ?agent= intent, so an intent deferred by slow
  agent/member queries can no longer fire late and clobber that choice.
- The composingNew reset reads the live store value; under StrictMode's
  effect replay the render-captured snapshot re-closed the compose pane the
  intent had just opened (one-shot guard rightly refuses to re-fire).
- A settled miss (revoked access, archived agent, bad id) now toasts and
  strips the param instead of silently keeping a re-fireable intent; still-
  loading queries keep the intent pending. Query errors never settle.
- useAgentPermissions exposes isLoading: the detail page disables DM while
  membership resolves instead of toasting a false not_member deny at a
  legitimate member.

The chat store test mock is now reactive (useSyncExternalStore) — with a
plain mutable ref React bails out of committing post-effect re-renders and
the StrictMode regression cannot reproduce. Both new regression tests fail
against the pre-fix code.

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

---------

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-12 13:56:31 +08:00
Jiayuan Zhang
4ad060ec54 fix(settings): unify form control widths with SettingsRow size tiers (#5291)
Replace per-row controlClassName widths with a size enum (text /
select-wide / select / code / none) on SettingsRow. Text-entry fields
(name, slug, description, context, bio) now share the text tier so
their control edges align within a card; deliberately-short fields
(issue prefix) keep a clearly smaller tier.

MUL-4428

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-12 13:54:58 +08:00
Kyou
c7002e3b30 fix(daemon): detect Codex CLI under ChatGPT.app on macOS (#5250)
Detect the bundled Codex CLI under the relocated ChatGPT.app on macOS, while keeping the legacy Codex.app path so older installs still resolve.

Closes #5205
2026-07-12 13:50:54 +08:00
Rusty Raven
7356b56a10 fix(taskfailure): anchor HTTP status-code matches to digit boundaries (#5275)
Anchor the 401/402/403/429/529 HTTP status-code matches to digit
boundaries so embedded numbers (e.g. "402913 tokens", "15290ms") no
longer misclassify process/unknown failures as provider errors and skew
failure observability. Mirrors the existing 5xx anchoring (providerHTTP5xxRe).

Fixes #5271
MUL-4422
2026-07-12 13:41:53 +08:00
YYClaw
9f775db16e fix(cli): stop pflag from garbling login --token help output (MUL-4410) (#5253)
Fix garbled `multica login -h` output: the --token line printed a raw NUL and a hijacked value placeholder. Change the NoOptDefVal sentinel to a printable value and drop backticks from the usage string. Add a regression test that renders the flag help through pflag's real path and asserts no control bytes plus the standard --token string[="prompt"] form.

Co-authored-by: YYClaw <197375+yyclaw@users.noreply.github.com>
Co-authored-by: J <j@multica.ai>
2026-07-12 13:27:09 +08:00
Jiayuan Zhang
d8165bac4e feat(views): unify reply and comment send buttons to the circular chat style (#5288)
SubmitButton is now always circular — the shape prop is removed since
every composer uses the same silhouette. ReplyInput drops its inline
icon-xs Button for the shared SubmitButton, gaining the same size,
disabled state, and send tooltip as the comment composer and chat.

MUL-4433

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-12 13:18:02 +08:00
Jiayuan Zhang
05d9298582 fix(agents): let workspace members view runtime capabilities (MUL-4427) (#5281)
* fix(agents): let workspace members view runtime capabilities (MUL-4427)

The Agent capabilities redesign (#5277) reused the runtime local-skills
discovery endpoint on Agent detail surfaces, but the endpoint kept the
owner-only gate from the original import flow. Viewing an agent bound to
someone else's runtime returned 403, which the Skills / MCP tabs rendered
as 'try again when the runtime is online' even though the runtime was
online.

- Discovery (list + poll) now requires workspace membership only; the
  payload is the deliberately redacted inventory built for this display.
- Import (init + poll) stays owner-only: it copies skill file contents
  off the owner's machine.
- The failed notice no longer blames runtime connectivity, and a 403
  (new client against an older backend) gets an honest permission
  message.

* test(settings): stub Intl.supportedValuesOf in timezone picker tests

The preferences-tab timezone tests drove a ~600-option Base UI Select
through userEvent in jsdom; on slow CI runners the clear-preference case
exceeded even its extended 20s per-test timeout (PR #5281 frontend job).
Stub the IANA enumeration down to the curated COMMON_TIMEZONES fallback
— everything the tests pick lives there too — and drop the now-unneeded
20s overrides. File test time drops from ~35s to under 1s.

---------

Co-authored-by: Lambda <lambda@multica.ai>
2026-07-12 04:00:31 +08:00
Jiayuan Zhang
c377d7fb4f feat(labels): add scoped label management (#5279)
* feat(labels): add scoped label management

* fix(labels): address review feedback

* fix(migrations): use unique label migration prefix
2026-07-12 03:46:08 +08:00
Jiayuan Zhang
8a05af0342 fix(agents): pin capability/settings sub-nav while content scrolls (MUL-4426) (#5280)
The agent detail page wrapped both the sidebar rail and the content pane
in one scroll container, so scrolling any Capabilities/Settings tab
dragged the Instructions/Skills/MCP rail out of view. Split scrolling on
md+ like settings-page.tsx: the outer container stops scrolling and each
pane scrolls itself. Below md the page still scrolls as one, and the
Overview/Work views keep whole-page scrolling.

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-12 03:36:13 +08:00
Jiayuan Zhang
3a382d92b3 Refine agent access scopes in create flow and inspector (#5269) 2026-07-12 03:15:45 +08:00
YYClaw
b0133261d6 feat(settings): improve API tokens page guidance and token-created dialog (#5254)
* feat(settings): improve API tokens page guidance and token-created dialog

The tokens page never told users what to do with a created token, and
the created dialog was easy to dismiss before the token was saved.

Page:
- Describe how a token is used (multica login --token, Bearer for API)
- Add a security note (full account access, treat like a password)
- Add an empty state instead of rendering nothing

Token-created dialog:
- Clearer title and an info callout emphasizing the token is displayed
  only once
- Ready-to-copy CLI sign-in command (multica login --token <token>)
- "I have securely stored this token" checkbox gating the Done button
- Wider dialog (sm:max-w-xl); token and command render on one line and
  truncate with an ellipsis (copy buttons still copy the full value)

All four locales (en, zh-Hans, ja, ko) updated.

* fix(settings): show load-failed state instead of empty card when token fetch fails

* fix(settings): add aria-labels to token dialog copy buttons
2026-07-12 03:09:59 +08:00
Jiayuan Zhang
2affa34f76 feat(agents): two-level runtime picker on agent settings — machine, then runtime (MUL-4421) (#5276)
A machine-level rename stamps the same custom_name on every runtime of a
daemon (MUL-4217), so the settings-page runtime dropdown rendered N
indistinguishable machine-name rows. Split selection into two levels:
pick the machine first, then a runtime on it, labelled by the runtime
itself. Opening lands inside the selected runtime's machine, and the
trigger now reads 'Claude · <machine>' instead of the bare machine name.

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-12 03:08:52 +08:00
Jiayuan Zhang
a14098288b feat: redesign agent Skills and MCP capabilities (#5277) 2026-07-12 02:53:17 +08:00
Jiayuan Zhang
5541819f98 refactor(ui): unify collection page patterns (#5258)
* refactor(ui): unify collection page patterns

* refactor(agents): redesign agent detail workbench (#5263)

* refactor(agents): redesign agent detail workbench

* refactor(agents): refine capability and settings surfaces

* refactor(agents): rebuild general settings form

* refactor(agents): refine overview and settings

* refactor(agents): redesign custom args editor

* docs(ui): record consistency audit
2026-07-11 21:12:04 +08:00
Jiayuan Zhang
65ccab172a fix(issues): float find bar above sticky resolve collapse bars (MUL-4414) (#5264)
The in-page find bar (absolute, z-20) and the resolve collapse bars
pinned at the timeline's top-0 (sticky, z-20) tied on z-index, so the
later-in-DOM collapse bar painted over the find bar, half-hiding it and
orphaning its close button. Raise the find bar to z-30 so the transient
overlay reliably paints above every sticky affordance in the content
column (comment headers z-10, collapse bars z-20).

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-11 18:27:54 +08:00
Jiayuan Zhang
12e3c393d7 Add auto-save confirmation toasts (#5261) 2026-07-11 17:26:45 +08:00
Jiayuan Zhang
d51a3cbbbd Unify settings layout and auto-save (#5257) 2026-07-11 15:28:56 +08:00
Jiayuan Zhang
b9b9e73e49 fix(issues): keep detail content column centered under classic scrollbars (#5255)
On platforms where scrollbars take layout space (macOS with a mouse or
'always show', Windows, Linux), the global 'scrollbar-width: thin'
reserves ~11px on the right edge of the issue detail scroll container
only, so the centered max-w-4xl column reads 32px left vs 43px right
whitespace. Mirror the gutter with 'scrollbar-gutter: stable both-edges'
on the detail scroller and its loading skeleton; overlay-scrollbar
platforms reserve nothing and are unchanged.

Measured before: 32px / 43px. After: 43px / 43px.

Co-authored-by: Lambda <lambda@multica.ai>
2026-07-11 14:36:32 +08:00
Jiayuan Zhang
ca46fdb483 fix(ui): lighten menu shadows with dedicated --menu-shadow token (#5256)
Dropdown, context menu, select, and popover surfaces used
--floating-shadow, which is sized for window-level overlays and reads
too heavy on trigger-anchored menus. Introduce a lighter --menu-shadow
tier (surface < menu < floating) and drop the shadow-lg override on
ContextMenuSubContent so submenus match their parent menu. Dialogs and
sheets keep --floating-shadow.

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-11 14:33:04 +08:00
Jiayuan Zhang
4efcfb96e3 feat(ui): establish surface system (#5248) 2026-07-11 13:43:44 +08:00
Jiayuan Zhang
489657cfb5 fix(inbox): align inbox list left padding with chat list (#5240)
The inbox row used px-3 inside the px-2 list container (20px content
inset) while the chat thread row uses px-2 (16px), so switching between
the Chat and Inbox pages made the avatar column visibly jump. Align the
inbox row to px-2, which also lines the avatars up with the shared
PageHeader title (px-4 = 16px) and matches the row convention used
elsewhere. Update the two loading-skeleton rows (px-4) to match so the
list no longer shifts when real rows load in.

Closes MUL-4396

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-11 04:15:12 +08:00
Jiayuan Zhang
e10bb3fff0 fix(chat): unify unread badge counting across sidebar, mobile, and thread list (MUL-4286) (#5239)
The aggregate chat unread badge used two different definitions: web/
desktop's sidebar summed unread messages while mobile's tab badge (and
the since-removed ChatFab badge it mirrored) counted sessions — the same
account state showed e.g. 5 on web and 2 on iOS. The sidebar also summed
ALL sessions while the thread list zeroes the row being viewed, so a
reply landing in the open conversation flashed a sidebar count with no
matching row.

- packages/core/chat/unread.ts: countUnreadChatMessages() as the single
  shared definition (IM-style message total, optional viewed-session
  exclusion); pure so mobile can import it.
- app-sidebar: use the helper and exclude the actively-viewed session,
  but only while a chat surface is actually showing it (chat route or
  floating window open) — a remembered selection with both surfaces
  closed still counts, since nothing will auto mark-read there.
- mobile: tab badge switches to the shared message count (99+ cap like
  the sidebar), ChatSessionSchema parses unread_count, and markRead's
  optimistic patch zeroes unread_count alongside has_unread.

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-11 02:46:12 +08:00
Jiayuan Zhang
835b1d5e4f feat(issues): thread quick-jump minimap on issue detail (MUL-4389) (#5234)
* feat(issues): add thread quick-jump minimap to issue detail (MUL-4389)

A Linear-style rail of tick marks overlaid on the left edge of the issue
detail scroll area, one tick per comment thread (folded resolved bars
included). Ticks whose thread intersects the viewport render darker, so
the rail doubles as a scroll minimap. Hovering a tick grows it and opens
a preview card (bold first line + muted body excerpt, both clamped);
clicking jumps the timeline to the thread and flashes it like an inbox
deep-link landing.

Jumps go through Virtuoso's scrollToIndex in virtualized mode (the
target row may be unmounted) and direct container scrollTop math in the
flat deep-link/find modes, never native scrollIntoView (#3929).
Viewport tracking reads DOM rects on scroll/resize instead of an
IntersectionObserver because Virtuoso mounts/unmounts rows while
scrolling. Hidden on mobile: no hover, and the gutter is too tight.

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

* feat(issues): Dock-style hover wave on the thread minimap (MUL-4389)

Hovering the rail now magnifies ticks with a cosine falloff of their
distance to the cursor — the hovered tick peaks at 1.7x and neighbours
taper off across ~4 tick pitches, following the pointer continuously.

Driven per-pointermove with direct style writes on the native `scale`
property (compositor-friendly, no React re-render), batched
read-then-write inside one rAF; a 100ms ease-out transition smooths
between pointer samples and settles the collapse on leave. Clearing the
inline value hands control back to the CSS floor states (popup-open,
focus-visible), and prefers-reduced-motion swaps the wave for a plain
hover grow. Only the hovered tick darkens — neighbours grow but keep
their color.

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

* feat(issues): single glide-follow preview card on the thread minimap (MUL-4389)

Scanning the rail continuously re-paid the 150ms open delay plus the
close/open animation on every tick crossed, because each tick owned an
independent PreviewCard popover — hover felt laggy while gliding.

Replace the per-tick popovers with ONE card owned by the rail, driven
by the same rAF rect pass as the hover wave: the intent delay is paid
once when the pointer enters the rail; after that, gliding retargets
the card instantly (~1 frame) and slides it to the hovered tick with a
150ms transform transition. Leaving starts a grace timer long enough to
travel onto the card (which keeps it open for text selection); keyboard
focus anchors the card immediately. The anchor is clamped so the card
never sticks out of the column at the rail's extremes, and previews are
cached per thread content so unrelated timeline updates don't
re-flatten every comment.

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

---------

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-11 00:51:33 +08:00
Jiayuan Zhang
932bbf2bb5 fix(editor): guard Mod-Enter submit against open IME composition (#5231)
The bare-Enter submit path already refuses to fire while view.composing
is true, but Mod-Enter had no such guard. Pressing ⌘↵ while a pinyin/kana
composition is still open submits the document WITHOUT the composed text
— e.g. paste a screenshot, type a Chinese sentence, hit ⌘↵ before the
buffer commits, and the submission carries only the screenshot. Apply the
same composing guard to Mod-Enter.

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 22:44:23 +08:00
Jiayuan Zhang
06df00f414 refactor(views): remove Runtimes tab CLI-update red dot (#5228)
The sidebar Runtimes nav item showed a red dot (introduced in #533)
whenever the current user owned a local, CLI-launched runtime whose
reported cli_version was older than the latest GitHub release. Remove
the dot and the now-unused useMyRuntimesNeedUpdate hook.

The per-runtime update indicator on the Runtimes page
(useUpdatableRuntimeIds) is unchanged.

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 22:13:18 +08:00
Bohan Jiang
53f05cca5e feat(github): fan out PR/check_suite webhooks to all bound workspaces (MUL-4343) (#5218)
* feat(github): fan out PR/check_suite webhooks to all bound workspaces (MUL-4343)

One GitHub App installation can be bound to several workspaces (#4855), but
pull_request and check_suite webhooks were still routed to a single workspace
via resolveWorkspaceForRepo (workspace.repos registry + oldest-binding
fallback). Every workspace but one silently received nothing for a shared repo,
with no way to opt in.

Deliver each repo event to every workspace bound to the installation. Repo
scope is whatever GitHub authorized the installation for; we no longer gate on
the workspace.repos registry (that list means "code the agent clones", not a
webhook subscription). Each workspace independently mirrors the PR, auto-links
against its own issue prefix + github toggles, records check suites against its
own PR mirror, and gets its own realtime broadcast.

- Extract mirrorPullRequestForWorkspace / recordCheckSuiteForWorkspace and loop
  over all installation bindings instead of resolving one workspace.
- Remove the now-dead resolveWorkspaceForRepo / repoIdentityFromURL routing.
- Replace the registry-routing tests with PR + check_suite fan-out tests.

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

* test(github): out-of-order fan-out test + drop dead query + document multi-workspace delivery (MUL-4343)

Addresses review feedback on the webhook fan-out change:

- Add TestWebhook_CheckSuite_OutOfOrderFansOutToBoundWorkspaces: a check_suite
  that arrives before the PR must stash a pending row per bound workspace, and
  each workspace must drain its own row when the PR fans out.
- Remove the now-unused ListWorkspacesWithRepos query (its only caller was the
  deleted resolveWorkspaceForRepo) and regenerate sqlc; fix the stale
  "picks the target workspace via the repos registry" comment on
  ListGitHubInstallationsByInstallationID.
- Document multi-workspace event delivery in the GitHub integration docs
  (en + zh), including an explicit self-host upgrade note: delivery is now
  keyed on the GitHub connection, so a workspace that relied on the
  code-repository list alone (without connecting GitHub) must connect the
  installation to keep receiving events. This is an intentional, documented
  behavior change — the PR description's earlier "single-binding behavior is
  unchanged" claim was inaccurate and has been corrected.

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

---------

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 20:10:25 +08:00
YYClaw
7a405fd1cf fix(daemon): keep the task transcript ordered and complete (#5210)
Two daemon-side fixes to the persisted task transcript:

- Wait for the drain goroutine to flush the final message batch before every
  terminal return (result, timeout, idle-watchdog, upstream cancel), so a
  consumer reading the transcript at completion can't see a truncated tail.
  Bounded (10s, then cancel + 12s) so a backend that never closes its message
  channel cannot stall the terminal transition.
- Share the message seq counter across a resume-failure retry so the retry's
  rows keep ascending seq values instead of restarting at 1 and interleaving
  with the failed attempt's rows.

Server-initiated cancellation read timing is tracked separately in #5219.

Closes #5209
MUL-4369
2026-07-10 20:00:26 +08:00
Multica Eve
37b0a6e72e docs(changelog): add v0.3.43 release entry (MUL-4376) (#5216)
Add today's user-facing release notes to /changelog across en/zh/ja/ko: new Codex gpt-5.6 models, issue-key autolinks, avatar cropping, per-thread agent replies, round avatars, direct batch status, bounded background log, and Lark/Cursor/Claude fixes.

Co-authored-by: multica-agent <github@multica.ai>
v0.3.43
2026-07-10 17:35:34 +08:00
Naiyuan Qing
595f785ac9 fix(desktop): make overflowing tab additions visible (#5215)
* fix(desktop): animate overflowing tab additions

* fix(desktop): recalculate tabs after pin layout changes

* docs: document daemon log rotation settings

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* Revert "docs: document daemon log rotation settings"

This reverts commit a6cbaa99ec.

---------

Co-authored-by: OpenAI Codex <noreply@openai.com>
2026-07-10 17:16:35 +08:00
Bohan Jiang
69132f9fa6 fix(chat): remove unread-count badge from chat FAB (MUL-4374) (#5213)
The floating chat bubble's unread-count badge duplicated the chat tab's
unread indicator in the sidebar and visually overlapped it. Drop the badge;
the hover tooltip still communicates unread/running state.

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 17:15:25 +08:00
Bohan Jiang
cb87dd106b feat(chat): task-owned direct-chat input batches + explicit no_response outcome (MUL-4351) (#5195)
* feat(chat): task-owned direct-chat input batches + explicit no_response outcome (MUL-4351)

Direct (web/mobile) chat no longer uses the last-assistant-row as an implicit
input cursor. Each direct send now owns an immutable input batch:

- agent_task_queue.chat_input_task_id makes a task the owner of the user
  messages it must consume; the send path creates the task + user message +
  attachment bindings + session touch in one transaction, and the daemon is
  notified only after commit. A claim reads exactly that batch, so a message
  that arrives mid-run belongs to the next task and is never absorbed.
- Auto-retry inherits the root input owner and is queued at a bumped priority,
  created inside FailTask's transaction so no newer chat task can jump ahead.
- CompleteTask writes exactly one assistant outcome inside the completion
  transaction: a normal message, or a visible no_response outcome (with a
  non-empty English fallback) when the final output is empty. The write failing
  rolls the completion back and the handler returns 5xx so the daemon retries;
  the status CAS keeps it idempotent. chat:done carries message_kind.
- Web/desktop/mobile render no_response as a localized 'no text reply' state
  (keeping the tool timeline), suppress Copy, keep it unread, and keep the
  session-list preview non-blank.
- Legacy/channel tasks (chat_input_task_id NULL) keep the trailing-message
  selector, so a rolling deploy never replays Slack/Lark history.

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

* fix(chat): scope no_response to direct tasks; don't cancel task on input read error (MUL-4351)

Addresses PR review (Niko):
- writeChatCompletionOutcome only writes a no_response row for task-owned
  direct tasks (chat_input_task_id set). Legacy/channel (Slack/Lark) tasks keep
  the prior behavior: empty output writes no assistant row, so chat:done carries
  empty content and the channel outbound silently drops it — the no_response
  fallback body never reaches an external channel.
- The daemon claim distinguishes a genuine zero-input batch from a failed
  input read: on ListChatInputMessages / ListChatMessages error it returns 5xx
  and preserves the dispatched task for redelivery instead of cancelling a valid
  task on a transient DB error.

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

---------

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 16:22:42 +08:00
Multica Eve
3c417ea631 fix(MUL-4348): authorize + chronologically order per-thread coalesced replies (#5211)
Testing surfaced two problems with the per-thread fan-out:

1. Authorization (blocker): CreateComment rejected any agent comment on the
   task's issue whose parent_id != task.TriggerCommentID, so replies to the
   OTHER coalesced threads were denied ('parent_id must equal this task's
   trigger comment id') and those threads never got a reply. Allow the trigger
   comment OR any comment the task coalesced (taskCoversReplyParent: trigger ∪
   coalesced_comment_ids); every other parent on the issue is still rejected,
   so this stays scoped to the set the run was actually given to answer.

2. Ordering: the agent answered the newest (triggering) comment first. The
   fan-out instruction now numbers the targets and explicitly requires posting
   OLDEST thread first, the newest/triggering thread last, so replies land in
   chronological order. commentReplyThreads already lists oldest-first.

Tests: TestTaskCoversReplyParent (allow-list) and chronological-order
assertions in the cross-thread prompt test.

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 16:15:10 +08:00
Bohan Jiang
6c3b79db19 feat(daemon): bound daemon.log size with rotation (MUL-4330) (#5170)
* feat(daemon): bound daemon.log size with rotation (MUL-4330)

The background daemon redirected its stdout/stderr into daemon.log opened
O_APPEND and never rotated it, so the file grew without limit until it was
too large to open. Every structured log line already flows through slog
(including agent subprocess stderr, forwarded via newLogWriter), so the
daemon's logger is effectively the sole author of the file's volume.

Route the foreground daemon's slog output — both the injected component
logger and the package-global slog default — through a size-based rotating
writer (lumberjack) that keeps the active daemon.log small (20MB default,
5 gzip-compressed backups, 30d), all env-overridable. Raw crash output
(Go runtime panics, pre-logger errors) now goes to a separate daemon.err.log
so the child's inherited fds never hold daemon.log open, which would block
rotation's rename on Windows.

The Desktop app spawns the daemon via this same launcher and its log tail
already handles size-shrink, so both CLI and Desktop are covered.

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

* fix(daemon): address log-rotation review — foreground output, Windows handles, bounded err log (MUL-4330)

Resolves the blocking review items on the daemon.log rotation change:

1. Windows first-upgrade rotation: a foreground managed daemon now re-points
   its own stdout/stderr to daemon.err.log at startup (SetStdHandle) before
   building the rotator, releasing any daemon.log handle an older self-update
   launcher inherited (Go opens files without FILE_SHARE_DELETE, which would
   otherwise block rename-on-rotate). No-op on Unix, where an open fd never
   blocks rename.

2. `daemon logs -f` vs rotation: Unix uses `tail -F` (reopen by name); Windows
   opens the reader with FILE_SHARE_DELETE so it can't block the rotator's
   rename, and reopens the file on size-shrink to follow across rotation.

3. Self-update handoff no longer briefly runs two rotators on one file: the old
   process closes its rotator and moves remaining handoff logs (incl. the slog
   default) to the crash sink before the successor starts.

4. daemon.err.log is now bounded: it rolls to a single ".1" backup once past
   5MB at open time, so a crash loop can't move the growth problem to it. It is
   also surfaced in the troubleshooting docs.

5. Explicit `--foreground` in a terminal keeps live stdout/stderr logging (a
   documented debugging path); only detached/background children rotate into
   daemon.log. Decided by whether stderr is a terminal.

Also: rotation env knobs now reject 0/negative (0 means 100MB / keep-all in
lumberjack), preventing an accidental unbounded config. Adds unit tests for
the err-log rolling and positive-int parsing; Windows/Linux(arm64) cross-builds
and `GOOS=windows go vet` pass.

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

* docs: sync zh/ja/ko troubleshooting with daemon.log rotation + daemon.err.log (MUL-4330)

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

* test(handler): bump the agent's own runtime version in quick-create parent test (MUL-4330)

TestQuickCreateIssueParentTrustBoundary bumped an arbitrary `LIMIT 1`
agent_runtime, but the handler version-checks agent.RuntimeID — the runtime
bound to the request's agent. In the shared handler test workspace, other
tests register additional runtimes, so the two diverge and the agent's real
runtime keeps the seed's empty cli_version, tripping the daemon-version gate
(422 daemon_version_unsupported) before the parent_issue_id assertions run.
Bump the runtime tied to the agent instead, making the setup deterministic.

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

---------

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 15:55:06 +08:00
Multica Eve
f599333f97 [MUL-4348] Route coalesced replies per root thread (#5202)
* feat(daemon): route coalesced replies per root thread (MUL-4348)

When a busy agent coalesces multiple @mentions into one run, the run used
a single --parent (the newest trigger), so questions raised in separate
root threads were answered in one merged comment while the other threads
were left unanswered.

Group the trigger + coalesced comments by root thread server-side in the
prompt builder (commentReplyThreads). When the run spans >=2 distinct
threads, emit a per-thread reply plan (BuildMultiThreadCommentReplyInstructions)
that instructs one reply per thread with the exact --parent, explicitly
overriding the general 'one comment per run' rule. Multiple @mentions from
the SAME thread collapse to a single group upstream, so same-thread
follow-ups keep the ordinary single --parent=trigger path and can never be
split into duplicate replies. Single-thread / non-coalesced runs are
unchanged.

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

* fix(daemon): sync workflow-brief reply step with per-thread fan-out (MUL-4348)

Review of #5202 found the cross-thread fan-out was injected only into the
per-turn prompt (buildCommentPrompt), while the persistent workflow brief
(writeWorkflowComment step 7) still emitted the single --parent=trigger
cookbook for every comment task. A cross-thread run therefore got two
slightly conflicting reply instructions, so the fan-out guarantee rested on
prompt wording/precedence rather than structure.

Carry the computed thread targets on TaskContextForEnv.CommentReplyTargets
(populated from the same commentReplyThreads() the prompt uses, so the two
surfaces cannot drift). When >=2 targets, the workflow reply step now emits
the per-thread fan-out plan too; same-thread follow-ups collapse to a single
group upstream and keep the single-parent cookbook, so they still can never
be split. Also clarified the multi-thread cookbook to show a distinct file
per reply (reply-1.md / reply-2.md).

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

* fix(daemon): reply under the specific mentioning comment per thread (MUL-4348 review nit #1)

Non-trigger threads previously replied under the thread root, while the
trigger's thread replied under the trigger comment — asymmetric, and it put
the answer at the top of the thread instead of next to the actual question
when the mention was a mid-thread reply. Reply under the NEWEST triggering
comment in each thread instead (inputs are chronological, so last-write-wins
per thread), making every thread consistent and nesting each answer beside
its question.

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

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 15:03:05 +08:00
beast
4217de4389 fix(lark): tolerate binding token clock skew (#5191)
* fix(lark): tolerate binding token clock skew

Clamp binding-token expiry against the database clock while preserving the 15-minute TTL cap. Return the persisted expiry so binding cards reflect the value enforced by Postgres.

* docs(lark): correct stale table name in binding token TTL comments

Post-#124 the table is channel_binding_token (with the
channel_binding_token_ttl_cap CHECK); update the two comments in
types.go and binding_token_test.go that still named the pre-generalization
lark_binding_token table.

---------

Co-authored-by: Bohan-J <bohan.optimism@gmail.com>
2026-07-10 14:47:54 +08:00
Wood
f7ca045fb1 feat(daemon): discover Codex model and reasoning catalog dynamically (#5198)
Discover the Codex model list and per-model reasoning efforts from the installed CLI (codex debug models --bundled), with a verified static fallback for old/offline installs. Server gates token syntax; the daemon validates the exact (model, effort) pair.

Closes #5197
MUL-4354
2026-07-10 14:32:05 +08:00
Multica Eve
bf161f2f9c fix(tasks): preserve merged comment delivery (#5192)
Track actual claim-time delivery, support legacy daemons, and repair comment
batches across claim, retry, edit, and delete races.

MUL-4348

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 14:10:10 +08:00
Bohan Jiang
8e0fbecab5 fix(models): codex empty-model effort validation + exact 5.6 aliases (MUL-4347) (#5196)
Follow-up to #5188 addressing the second-round review.

- ValidateThinkingLevel now fails an empty codex model closed instead of
  borrowing the flagged Default (gpt-5.6-sol). An empty model follows
  config.toml, which can resolve to any installed model; Sol alone advertises
  `ultra`, so the old borrow green-lit levels Luna / gpt-5.5 don't support and
  Codex doesn't reject. Checked before ListModels so a discovery error can't
  fail it open. Frontend pickModelEntry mirrors this (no per-model effort
  preview for an empty codex model); the persisted-orphan clear path stays.
- parseCodexDebugModels drops efforts without a known label so the picker
  never advertises a level the Create/Update enum gate would 400 on save; the
  contract test now drives the real parser with an unknown effort instead of
  comparing two hand-written maps.
- gpt-5.6 price aliases anchor to a literal dot (not the [.-] class), so
  dashed variants like gpt-5-6-luna surface as unmapped on both backend and
  frontend rather than silently borrowing a tier.

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 14:00:38 +08:00
Bohan Jiang
cc3daaf3b4 fix: scope claim-time comment fetch to workspace + guard --attachment paths (MUL-4252) (#5190)
* fix(daemon): scope claim-time comment fetches to the task's workspace (MUL-4252)

The daemon claim path embeds the triggering comment and every coalesced
comment's full text into the agent prompt, but fetched them with an
unscoped `GetComment(id)` — a task row carrying a foreign comment UUID
would pull another workspace's comment text into the prompt. On a shared
SaaS backend (tens of thousands of workspaces in one DB) that is a tenant
boundary hole, latent today only because task rows are server-written.

Switch all three claim/reconcile GetComment calls to
GetCommentInWorkspace, scoped by the runtime's workspace (claim path) or
the issue's workspace (completion reconcile). The task's issue workspace
is already asserted equal to the runtime workspace, so same-workspace
delivery is unchanged; a foreign UUID now resolves to "missing" and is
skipped — matching buildCoalescedCommentData's documented behavior.

Adds DB-backed claim tests: same-workspace trigger comment is still
delivered; a foreign-workspace comment's content never surfaces.

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

* fix(cli): extend the workdir guardrail to --attachment paths (MUL-4252)

#5167 fenced --description-file/--content-file to the working directory
but left --attachment uncovered — the same /tmp stale-file leak in image
form: an agent that writes chart.png to a machine-shared path and attaches
it could upload another run's (possibly another workspace's) stale file.

Apply ensureAttachmentWithinWorkdir to each local --attachment path in
`issue create` and `comment add` (URL values are still skipped upstream),
reusing #5167's symlink-resolving fileWithinWorkingDir and the existing
--allow-external-file escape hatch. Rejection happens before the issue is
created, so a bad path never yields a half-created issue.

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

* fix(service): scope trigger-summary + originator resolution to the task's workspace (MUL-4252)

PR review P1: the claim-time full-comment fetch was already scoped, but
the trigger_summary snapshot (first ~200 chars) still leaked. On the real
enqueue/merge paths a foreign comment UUID flowed through
buildCommentTriggerSummary / resolveOriginatorFromTriggerComment, which
used an unscoped GetComment; the truncated text was stored on the task row
and later returned in the claim / task-history response
(handler/agent.go trigger_summary).

Thread the issue's workspace through both helpers (and their exported
merge-path wrappers) and switch to GetCommentInWorkspace, so a
cross-workspace comment resolves to "missing": trigger_summary stays NULL
and no foreign originator is inherited. Every caller already has the
issue's WorkspaceID in scope (enqueue, mention/leader, deferred fallback,
merge, completion reconcile).

Rework the claim test to drive the REAL TaskService.EnqueueTaskForIssue
path (which snapshots the summary) and assert the stored row's
trigger_summary + originator_user_id stay NULL and the claim response
carries neither the foreign body nor the foreign summary. Verified the
test fails when the summary fetch is left unscoped.

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

* fix(cli): validate all --attachment paths before uploading any in comment add (MUL-4252)

PR review P2: `issue comment add` checked-read-uploaded each attachment in
one loop, so a valid workdir attachment followed by an invalid (external /
symlink-escaping) one uploaded the first file — orphaning it as an
issue-level attachment — then aborted before posting the comment, and a
retry duplicated it.

Extract the URL-filter + workdir-guard + read step `issue create` already
used into a shared collectLocalAttachments helper and have comment add use
it: every attachment is validated and read up front, and nothing is
uploaded unless all pass. Adds a command-level test asserting a
valid-then-external attachment pair aborts with ZERO upload requests and
no comment (fails against the old interleaved loop).

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

---------

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 13:43:45 +08:00
YYClaw
78591f6022 test(server): cap pkg/agent test concurrency under -race (#5077)
* test(server): cap pkg/agent test concurrency under -race

pkg/agent spawns many subprocess-backed tests with hard 5s deadlines. On
high-core machines the default GOMAXPROCS fan-out (across packages via -p
and within a package via -parallel) starves the parent event loops so they
miss the deadline. Run the rest of the suite at full concurrency and cap
only pkg/agent so those tests stay within budget without slowing the whole
Go suite's CI wall-clock.

* test(server): fail make test when go list package discovery fails
2026-07-10 13:01:49 +08:00
Bohan Jiang
6b980a8e71 feat(models): add Codex gpt-5.6 series (sol/terra/luna) to model list & pricing (MUL-4347) (#5188)
* feat(models): add Codex gpt-5.6 series (sol/terra/luna) to model list & pricing (MUL-4347)

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

* fix(models): official gpt-5.6 pricing, exact aliases, max/ultra effort levels (MUL-4347)

- Replace provisional gpt-5.6 rates with OpenAI's official announcement
  values (sol 5/30, terra 2.5/15, luna 1/6); cache read 0.1x input, cache
  write 1.25x input (frontend + backend, kept in sync).
- Anchor gpt-5.6 price aliases to exact match so unknown suffixed variants
  surface as unmapped instead of borrowing a tier.
- Add Codex 0.144.1 max/ultra effort levels to the label map and server
  enum so the daemon-advertised catalog matches what the API can persist;
  add a catalog->API contract test.
- Clarify that the codex Default flag is the effort-validation anchor, not a
  user-facing badge.
- Note the cache-write measurement limitation (codex usage stream doesn't
  report cache-write tokens yet).

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

---------

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 13:00:34 +08:00
ZIce
fe46dfdbf6 MUL-4203: Fix Cursor MCP auth source seeding (ZIC-52)
Merge approved PR.
2026-07-10 12:59:56 +08:00
CAVIN
521052a00b fix(daemon): recover stale Claude resume sessions (#5173)
Co-authored-by: CAVIN <zzz163519@users.noreply.github.com>
2026-07-10 12:49:23 +08:00
Naiyuan Qing
302662aee3 fix(issues): batch status applies directly; coalesce staged parent notifications (MUL-4155) (#5151)
Batch sub-issue status changes triggered two wrong behaviours from one user
action:

- Frontend popped the pre-trigger "现在开始处理?" confirm modal for every
  non-backlog target, but done/cancelled can never start a run, so it degenerated
  into a misleading "won't start → OK" step. handleBatchStatus now applies
  directly (product decision: batch status, including backlog → active promotion,
  applies like a single-issue/CLI change). Assign agent/squad and delete still
  confirm. The now-unreachable status mode is removed from RunConfirmModal and
  its locale keys.

- Backend evaluated the stage barrier per-child inside the batch loop, using a
  mid-batch sibling snapshot. A batch closing several stages at once emitted one
  comment per intermediate stage, pinned the parent assignee's wake to a stale
  "advance Stage N+1" instruction (the accurate wake was swallowed by the
  pending-task dedup), and the outcome depended on issue_ids order.
  BatchUpdateIssues now collects terminal transitions and evaluates each parent
  once against the batch's final state (notifyParentsOfBatchChildDone): at most
  one accurate comment + one wake per parent, order-independent. Single-issue
  UpdateIssue is unchanged; WillEnqueueRun is untouched.

Tests: cross-stage batch done/cancelled (forward + reverse) and lower-stage-only
on the backend; status-direct / assign-confirm / delete-confirm routing on the
frontend.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 09:11:29 +08:00
Naiyuan Qing
f4de0948a2 refactor(ui): unify ActorAvatar size tiers + round all avatars & cropper (MUL-4277, MUL-4184) (#5133)
* refactor(ui): converge ActorAvatar size to semantic tiers (MUL-4277)

Replace the free-form numeric `size` on ActorAvatar with a constrained
`AvatarSize` union (xs/sm/md/lg/xl/2xl) so avatar dimensions are chosen by
role instead of ad-hoc pixels. This eliminates the magic-number drift where
the same role rendered at different sizes across pages.

- Add `@multica/ui/lib/avatar-size` (AvatarSize union + AVATAR_SIZE_PX map +
  default tier).
- Base `ActorAvatar` (packages/ui) and business `ActorAvatar`/`AgentStatusDot`
  (packages/views) now take `AvatarSize`; internal font/icon math and the
  presence-dot threshold read px from the map.
- Migrate all web/desktop call sites (packages/ui + packages/views) from
  numeric sizes to tiers using the role table
  (12,14->xs 16,18,20->sm 22,24,28->md 30,32,34->lg 40,44->xl 56,64->2xl).
- Token-ise the derived consumers `AgentAvatarStack` and
  `IssueAgentActivityIndicator` (px looked up internally for overlap/+N math).

Out of scope (per plan): ui/avatar.tsx primitive, account-tab/AvatarPicker,
mobile, and component-name disambiguation.

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

* fix(ui): unify all avatars and the upload cropper to round (MUL-4277, MUL-4184)

main's avatar-shape decision rendered non-human actors (agent, squad,
system) and the workspace logo as rounded squares, and the upload cropper
mirrored that with a square crop window. Per the updated decision
(avatars_and_cropper_round_required), every avatar and the crop UI are now
circular; the square path is removed rather than left as dead config.

- Base ActorAvatar: always rounded-full (drop the isHuman/rounded-md split).
- avatar-crop-dialog: remove the AvatarCropShape/square path; crop window is
  always cropShape="round".
- avatar-upload-control: drop VARIANT_SHAPE; the control is always round and
  no longer threads a shape to the dialog (variant still drives the fallback).
- Strip rounded-md/rounded-none square overrides from agent/squad/member
  ActorAvatar call sites; round the read-only agent/squad static wrappers.
- WorkspaceAvatar: round the org logo so it matches the (now round) workspace
  upload/crop and the shared avatar shape.

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

* fix(ui): make round avatar shape a hard invariant (MUL-4277)

Close the two remaining square squad-avatar paths flagged in review and
prevent call sites from re-squaring the avatar:

- base ActorAvatar: keep `rounded-full` as the last class in cn() so a
  call-site `className` can no longer override the circle.
- SquadHeaderAvatar: drop `className="rounded"` (was overriding the base
  circle into a small rounded square).
- SquadsPage no-avatar fallback: route through the shared ActorAvatarBase
  (`isSquad size="lg"`) instead of a hand-written rounded-md tile, so the
  fallback matches the image path — one shape source of truth.

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

* fix(views): round the agent/squad avatar loading skeletons (MUL-4277)

The avatar placeholder skeletons on the agent/squad list, detail, and
profile-card loading states were still rounded squares (rounded-md/lg) from
the pre-round era, so the avatar visibly popped from square to circle on
load — inconsistent with the round avatars and with the member/inbox/issue
skeletons that already use rounded-full.

Round all five: agents-page, agent-detail-page, squads-page,
squad-detail-page, squad-profile-card.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-10 08:31:40 +08:00