562 Commits
Author SHA1 Message Date
7c186016a5 feat(core): multica 1.0 part 1 (MUL-8317) (#9635)
* feat(core): add app space model for rail navigation

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(layout): replace left sidebar with app rail and space panels

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* test(e2e): drive navigation through the app rail

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(layout): finish the rail and panel shell against the mock

- The sidebar toggle always works: in Inbox, Chat and Settings it collapses
  the page's own list column instead of being disabled.
- Move create workspace from the rail into the account menu, and complete
  the menu: profile, theme and language submenus, shortcuts, docs, change
  log, Discord, desktop download, log out.
- Add an explicit New issue button to the Issues and My issues headers.
- Autopilot panel gets a create button and template shortcuts; Team panel
  gets an invite-member button for owners and admins.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(desktop): draw tabs as pills on the title bar

The active tab no longer merges into the content card: with the space panel
inside the card, the merged tab landed over whatever column happened to sit
below it. Every tab is now a pill on the title bar; the active one uses the
card's own surface, inactive ones a lighter tint.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(layout): give list columns the space panel's width

Inbox and Chat defaulted to 260px with their own saved widths, Settings was a
fixed 240px, and the space panel used the sidebar width. They now share one
ListColumn sized by --sidebar-width with the panel's resize edge, so the
divider stays in place across spaces and one drag resizes them all.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(layout): return each rail space to where you left it

Rail items always linked to the space's home, so switching spaces dropped
the project, squad, chat session or settings tab you had open. The shell
now remembers the last page (path and query) per space and workspace, and
the rail links there; the current space's item still leads home, so a second
click resets it.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(layout): animate the list column when the sidebar toggles

Inbox, Chat and Settings unmounted their list column on toggle, so it
snapped away while the space panel slid. ListColumn now stays mounted and
eases its width like the panel (200ms, off under reduced motion and while
dragging), which also keeps the list's scroll position across a collapse.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(layout): one title band for every left column

The space panels used PanelHeader (title-sm, no rule), Inbox and Chat used
the page header (body size, bottom border) and Settings its own 64px title.
PanelHeader now takes a heading level, a leading slot and a meta slot, and
Inbox, Chat and Settings render their list title with it.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(layout): collapse the whole sidebar with one toggle across spaces

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(layout): switch workspaces from one rail tile

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(layout): preview Inbox, Chat and Settings lists on rail hover

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(inbox): keep the list column's own loading skeleton

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(desktop): lay out the shell for the page on screen during rail switches (MUL-8317)

The tab session URL moves before the router commits the next page (it renders
in a transition), so for a few frames the shell already showed the incoming
space's layout around the outgoing page: switching into Inbox or Chat dropped
the Work panel and the old page's header icon jumped to the top-left.

The router now publishes its committed pathname from a layout effect and the
shell's panel and list columns follow it; space memory keeps following the
navigation URL.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): flatten the settings nav into one level of section headings (MUL-8317)

Scope header rows, sub-group headings and dividers stacked three levels into
the 240px list column. Every section is now a peer with the space panels'
section label and spacing; each page already names its scope in a badge.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): expand projects inline in the work panel (MUL-8317)

Project rows in the Issues panel get a chevron that lists the project's open issues in place, so the sidebar can move between issues without opening the project page. The issue on screen reveals its project and stays lit; expansion persists per workspace. A dedicated open-issues cache follows realtime title patches and refetches only when status or project membership moves.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): sidebar tree settings, load more, expandable views and project order (MUL-8317)

- Expanded projects and views list issues from the server (updated date,
  finished statuses hidden) with Load more / Show less.
- One shared issue display popover on hovered expanded projects; a project
  display popover on the Projects header (ordering, statuses, only mine,
  counts); drag to reorder; collapsible section; no project cap.
- Issue rows shimmer and show agent avatars while an agent works or waits.
- Settings, order and expansion persist per workspace.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(views): show sidebar status filters as checkboxes (MUL-8317)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(ui): auto-collapse the nav only below lg (MUL-8317)

The rail now stays on screen beside the space panel, so laptop-sized
windows (1024-1279px, just under the 1280 desktop default) have room for
the panel; they no longer open Issues with it collapsed.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): one Projects display popover, higher-contrast filter chips (MUL-8317)

Issue settings are shared by every project, so they move from each expanded
row into the Projects header popover beside the project settings. Status
filters go back to chips: a solid outline and tint when on, dashed and
faded when off.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(server): list views across scopes and store sidebar view order

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): starred views sidebar, unified view header and All views page

- Sidebar Views lists only starred views (built-in and saved, user-ordered)
  with an All views row; order lives in a per-user sidebar preference.
- Issues, My issues and project pages share one header: view name menu,
  folding filter chips (+N), assignee-type / relation chip, Reset and Save.
- Saved-view filters are fully editable; new views are created directly
  and renamed inline instead of through the save dialog.
- New All views directory with tabs, search and star toggles.
- Project page drops the breadcrumb and hides the redundant project chip.
- Remove the view bar, save/manage dialogs and the old headers.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(inbox): switch between inbox and archive with header tabs

Replace the Archived row that trailed the notification list (and the back
row inside the archive) with Inbox | Archived tabs pinned under the list
header. The unread count moves onto the Inbox tab, and an empty archive no
longer says the inbox is empty.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* Revert "feat(inbox): switch between inbox and archive with header tabs"

This reverts commit e8a8e1bcea1bf5b45c4e5830d654523a0b77daff.

Co-authored-by: multica-agent <[email protected]>

* fix(inbox): drop the count beside the no-agents-working filter

The count widened the whole filter menu for a toggle whose result is
visible in the list as soon as it is applied.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(inbox): switch to the archive from the page title

The Archived entry trailed the notification list and scrolled away with
it. The list title is now a menu (Inbox / Archived), so the switch stays
in reach without adding a row, and the archive's back row is gone. An
empty archive no longer says the inbox is empty.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(sidebar): keep a pinned issue lit after its URL turns canonical

The issue route replaces /issues/<uuid> with /issues/MUL-123, but a pin
only matched its own UUID href, so clicking a pinned issue lit the same
issue under its project instead. Issue pins now match the open issue by
id, a lit pin no longer auto-expands its project, and pinned issues show
the running agent the same way tree rows do.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(sidebar): line pinned agent avatars up with the tree rows

The avatar sat inside the gap reserved for the unpin button, so pinned
avatars landed left of the project tree's and the label lost room. A
pinned issue with agents now puts the avatar in the row's right edge and
hands that slot to the unpin button on hover.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(sidebar): raise the contrast of the working-agent shimmer

ShimmerText now reads --shimmer-text-base, --shimmer-text-highlight and --shimmer-text-spread, defaulting to the previous values. Sidebar issue rows (tree and Pinned) rest on faint-foreground and use a 22% highlight band so the sweep is visible at sidebar size; chat, the issue page and other shimmer hosts are unchanged.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(issues): keep the view header from overlapping on narrow widths

The view-name button kept the Button base shrink-0, so once the h1 hit its min width the name painted over the add-filter trigger (visible on a 390px web viewport). The button now shrinks and truncates, the add-filter label becomes screen-reader-only in a narrow header, and FoldingFilterChips re-measures its reserved width when its leading or trailing controls resize instead of only when the chips change.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(issues): one Filter dropdown and a header that adapts to its width

The view header no longer lays filters out as chips with a +N overflow. A single Filter button carries the active count and opens the filter menu, which now lists the dimensions already filtering first, each showing its values, with the scope axis (assignee type, relation to me) at the top. The split is taken when the menu opens so a row never moves mid-edit.

The header measures itself: when the title side runs out of room it first folds the display controls to icons, then the agents chip and New issue, before the title truncates, and only releases a step once the row is wider than the width that forced it. This fixes the overlap in saved views with table controls and after toggling agents working.

FoldingFilterChips and single-row-fit had no other users and are removed.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(sidebar): keep the highlight on the row that was clicked

An issue that is both pinned and listed in the tree always lit its pin, so clicking it under a project moved the highlight away from the row just clicked. The work panel now remembers which section a click on an issue link came from: a click in the tree keeps the tree row lit and the pin unlit, a click on the pin keeps the pin. Arriving any other way (search, a link, a shortcut) still lights the pin.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(sidebar): let Views and Pinned collapse like Projects

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(sidebar): keep a dragged row at its own height while reordering (MUL-8354) (#9662)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(sidebar): create an issue in a project from its row

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(issues): put the workflow beside the title and set the panel toggle apart

The project workflow now sits on the left with the title and Filter, in
the same borderless style. New issue matches Display's height, and a
divider separates the right-sidebar toggle from the header actions.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(issues): fold the layout switcher into the Display panel

One Display button whose icon follows the layout. The panel opens with
the layouts as a segmented row, then grouping, ordering and sub-issues,
then the current layout's own options. The table's grouping and column
pickers move in from the header.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(issues): keep hovered Display chips from looking selected

Light mode's muted and accent are the same grey, so a hovered chip
matched a pressed one. Hover now only darkens the text and border, and a
pressed chip fills with the stronger input tone.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(sidebar): give the Projects settings the Display panel's controls

Both panels now share one chip and one sort-direction button. The
sidebar popover drops its dividers and dashed off-chips, takes the same
section headings, and its selects match the Display panel's width.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(issues): quiet the issue view header down to its title

The header carried six pieces of text, half of which said nothing: the
project's workflow name, "0 agents working" and "Display".

- Workflow moves into the project title menu as a submenu; the properties
  panel keeps its row.
- Filter becomes an icon beside the title, followed by Reset / Save, so the
  count sits next to the actions it causes. Its tooltip names the active
  filter fields.
- Display and New issue become ghost icon buttons named by tooltips, with
  New issue showing its C shortcut.
- The agents chip shows only while someone is working or its filter is on,
  as avatars and a number.

With every control already an icon, the crowding steps are gone.

MUL-8361

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(issues): is / is not per filter, Relation to me, and a hover summary (MUL-8353) (#9663)

* feat(issues): is / is not per filter, a Relation to me filter, and a hover summary (MUL-8353)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(issues): match the Relation filter's Chinese labels to My issues (MUL-8353)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): advanced filter mode, menu search, and due date filter

Advanced mode turns the simple filters into a rule tree (match all/any,
is/is not per condition, one level of groups) edited in a panel behind
the same Filter trigger. Switching back is offered when the tree is
something simple mode can say.

The filter menu gets a search box that matches fields and values; a
value hit reads as the condition it adds. Due date joins as a dimension
with relative buckets (overdue, today, this week, next 7 days, none).

The issue table query accepts filters.advanced, filters.due_dates and
filters.today, compiled to SQL with the same IS NOT TRUE negation.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* test(e2e): menu search, due date and advanced filter

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Cursor <[email protected]>

* test(e2e): let menus settle before filter screenshots

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(views): advanced filter keeps status columns, property filters and negated totals

- Columns follow a status the rule tree requires, and a status it names
  un-hides its column, so switching to advanced no longer drops rows.
- Status branch totals drop the excluded keys for "is not" instead of
  falling back to the loaded count.
- The advanced panel shows property filters as editable rows with an
  "Add property filter" entry, and Clear all clears them too.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): make custom properties ordinary advanced filter conditions

Properties are fields in the advanced tree: they sit in the field picker
after the built-in fields, can live in groups and follow all/any, and
edit their values with the property's own options. The server compiles a
property:<id> condition with the existing property predicate. Entering
advanced mode moves property filters into the tree, so the separate
property section and its Add property filter button are gone.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): typed filter operators and direct condition groups

- Add condition group inserts an empty group and opens its own add menu,
  instead of asking for a field first. The group's header carries its
  match ("Match any of these"); rows only show the joining word.
- A row's menu can turn it into a group, and a tree with groups reads
  back as one sentence under the rows.
- Operators follow the field's type: is / is not, includes any / none of
  for labels and multi-value properties, contains for text, comparisons
  for numbers, before / after for dates, and is empty / is not empty for
  anything that can be unset. Text, number and date values are typed in
  the row. Every operator is a reading of the stored negated + values, so
  saved views and the server query are unchanged.
- Actor value headings sit in a menu group, which Base UI requires.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(layout): New issue and search at the far end of the desktop title bar

New issue lived only as a pen in the Issues panel header, so Inbox, Team,
Chat and Auto had no way to start an issue but the C shortcut. Search sat
in the rail between the workspace tile and the spaces.

- Desktop: New issue and search are icon buttons at the right end of the
  title bar, styled like Back/Forward, with search last. The tabs give way
  to them. The rail drops search.
- Web has no title bar, so its rail keeps both under the workspace tile,
  New issue first.
- The button does what C does: a project page files the issue into that
  project. Both now share one helper.
- The Issues panel header loses its pen; the page header keeps New issue.

MUL-8359

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(issues): remove horizontal scrollbar from the filter menu

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(sidebar): every view expands in the sidebar

Saved views preview through the table query, which expresses every view filter (is not, relation, due date, rule trees, my-any). Built-in views expand too, scoped like their page and listed with the sidebar issue settings.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(sidebar): group expanded rows by spacing, folder icon for projects

Issues under an expanded row drop to 26px rows with no gap and a 12px status
icon, an open group ends with 8px of space, and the expanded row's name goes
full-strength. Sections sit 20px apart. Sidebar projects show a folder icon
instead of their emoji.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(sidebar): expandable rows give their label the full width

The hidden chevron, an empty spacer and their gaps reserved about 90px at the
end of every expandable row, so names truncated with space to spare. The
chevron now takes the icon's place on hover.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(sidebar): tree row chevron swaps back to the icon immediately

Drop the opacity fades, and show the chevron for keyboard focus only, so a
clicked row no longer keeps it until focus moves.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(issues): drop the hint beside Switch to advanced filter

The grey text sat where the menu shows current values, so it read as a
setting rather than an explanation.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(layout): tighten the gap under panel titles, keep selected rows at regular weight

The first section kept the 20px that separates sections, so it sat far
below the panel title. Selected and expanded rows, desktop tabs, the
Settings nav, agent tabs and the skills file tree no longer switch to
medium weight: the wider glyphs made the label grow when it was clicked.
Selection reads from the background and text colour.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(sidebar): keep stars while loading, leave view state to in-place clicks, page past 100

- Star, unstar and reorder write the stored order, not the resolved items,
  and wait until that order is known, so views still loading are kept.
- Sidebar rows switch the active view only when the click navigates this
  page; a new tab takes its target from the URL.
- Sidebar issue lists walk 100-row pages so Load more keeps growing.
- Drop a trailing blank line in advanced-filter-panel.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(views): open the clicked view in desktop tabs, prepare view state only on in-place navigation

- Desktop tabs carry their own ?view=: list pages adopt it on mount and on
  back/forward, write view changes back with replace, and the active-view
  map is swapped with the active tab. A saved view is part of tab identity,
  so cmd+clicking view B from a tab on view A opens B in a new tab.
- AppLink and useRowLink call onNavigateInPlace right before they push, so
  sidebar and All views rows prepare view state exactly when the page
  navigates in place (desktop shift-click included, web shift-click not).
- Rail items and the workspace tile use named radius tokens.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): one page system for lists and objects

Every page now follows the Issues page: one 48px header, nothing stacked
under it.

- Lists entered from the sidebar (agents, squads, skills, runtimes,
  projects, views, autopilots, issue wakeups) title themselves with an
  icon and a scope menu, and keep only icon tools: filter with its count
  and Reset, display, and "+". Counts left the header; list search is
  removed.
- Objects (agent, squad, skill, runtime, autopilot, member) open under a
  breadcrumb with at most one solid action and a ⋯ menu. Their tabs became
  body sections with actions on the section titles, in a 760px column,
  with a 300px properties panel the header toggles.
- Analytics keeps its tabs, now in the header; settings, chat and the
  create-agent flow share the same header.
- Shared CollectionPage* and ObjectPage* primitives in views/layout.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): one CollectionList for every list page

Replace the per-page tables and ListGrid with a shared CollectionList in
packages/views/layout: single-line 36px rows with inline grey
descriptions, a shared column/grid template, header sort, row selection
with shift-range, keyboard navigation (arrows, J/K, Enter, X, Cmd+A,
Esc), right-click and kebab row menus, load-more, and a shared batch bar.

- createCollectionViewStore factory in core; display menus are generated
  from the column definitions.
- Agents, Squads, Skills, Projects, Autopilots, Runtimes (page and
  RuntimeList), Views and Wakeups migrate to it; Wakeups pages with
  load-more instead of prev/next.
- The Issues batch toolbar uses the shared CollectionBatchBar.
- Agents hides owner and access by default so the default columns fit at
  1280px; persisted v1 defaults migrate, custom choices are kept.
- Remove ListGrid and the now-unused per-page locale keys.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(layout): smooth the nav panel toggle

- Ease the panel and list columns with a soft-start curve (220ms ease)
  instead of ease-out, whose first frame jumped a quarter of the width
  and, when that frame dropped, nearly half of it.
- End-align the panel content and clip it while it grows, so it slides
  in with the edge instead of spilling under the page; the resize handle
  moves to a zero-width sibling so the clip does not cut it.
- The floating chat stays mounted while closed and re-rendered on every
  frame of the toggle as its container resized; it now re-renders only
  when the clamped window size changes.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(issues): give My agents' work its own URL

My agents' work shared /my-issues with My issues and was told apart only by
a persisted global scope, so it could not open in its own tab, and switching
one tab flipped every other. It now lives at /my-issues?scope=agents: the
page, header and sidebar read the scope from the URL, desktop tab dedupe
keys on it, and the tab gets its own title and icon.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(views): preserve My agents' work in copied links

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-31 15:26:51 +08:00
ac131c55cb MUL-8416 fix(comments): keep thread owner when root folds into a queued task (#9735)
A member reply merged into a still-queued task moves trigger_comment_id to
the reply and leaves the thread root only in coalesced_comment_ids.
ListConversationRootOwners matched the root against trigger_comment_id alone,
so the thread lost its owner and later un-mentioned replies under the root
were silently dropped. Treat a root in coalesced_comment_ids as owned, the
same coverage rule taskCoversReplyParent and CancelAgentTasksByTriggerComment
already use.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-31 02:40:59 +08:00
53c4bbe5cd MUL-8395 fix(lark): distinguish bound bots from verified message reception (#9709)
* fix(lark): verify message reception separately from binding

* fix(lark): preserve verification evidence during rollout

* fix(lark): remove stray space in Chinese verification hint

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Multica Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-31 02:05:37 +08:00
3191de1930 MUL-8385 feat(wecom): show the run's steps and reasoning in the bubble while it works (#9696)
* feat(wecom): show the run's steps and reasoning in the bubble while it works

The bubble used to open, spin for the whole run and close with the answer.
It now fills in while the run is going: each tool call as a sentence in the
reader's language, the agent's own reasoning under it, and how long it has
been. Only the bot's principal sees any of it, and only in their own 1:1 —
a group or anyone else gets the plain spinner, decided again for every round.

- Steps come from task:message (the transcript), plus the daemon's first
  task:progress milestone, which is said as "starting the agent" in the
  bubble's language rather than the daemon's English. Only runs asked over
  WeCom (a WeCom channel_task_delivery row) show steps.
- Progress reaches the replica that holds the round, whichever replica
  served the daemon's request. The bus subscriber only classifies the event
  and queues it on a per-run lane, so the daemon's request does no database
  read and no socket write. The holder reads the run's new transcript rows
  past a per-run watermark (ListTaskProgressSince), redacts, throttles and
  writes the frame. A replica without the round publishes a progress relay
  frame that carries ids and a seq, no text; it takes no claim and never
  enters the dispatcher's queues, so it cannot delay or crowd out an answer.
  Duplicates, replays and out-of-order frames read nothing new, and do not
  count as a sign of life for the rotation guard.
- Credentials are redacted from steps and reasoning: labelled values, known
  key prefixes, long high-entropy runs, and the same checks on each part of
  a URL or of a word glued to shell punctuation, plus webhook URLs whose path
  is the secret. Nothing the renderer strips afterwards can join two pieces
  into a key the redactor never saw. Reasoning arrives in
  500ms increments, so redaction and the </think> defence run over the
  joined buffer, where a key and its value are seen together, and a run that
  may still be the start of a key is held back until it has finished.
- A bubble near the server's ten-minute window is handed over to a fresh
  stream; a hand-over the server refuses stops rotation, and any stream the
  server has disowned takes no further frame — every closer falls back to a
  plain message instead of spending another refusal. Every frame a step
  writes, and both frames of a hand-over, take the reply write guard.
- The identity lookup is wired on the real boot path, and its absence logs
  a warning, since without it no bubble ever shows a step. A relay with no
  typing indicator attached warns once when progress frames arrive.

* fix(wecom): show a step the throttle held back without waiting for the next report

A step that landed inside the 1.5s refresh window was only shown when the
run reported again. The step most worth showing is often the last before a
long silence (a file read, then a test suite that runs for minutes), so the
bubble sat on the earlier step for the whole wait.

A held step now schedules one trailing refresh per feed for when the window
ends. It goes through the run's lane like any report, is not counted as a
sign of life for the rotation guard, is never published to other replicas,
and finds nothing once the round has ended.

Co-authored-by: multica-agent <[email protected]>

* fix(wecom): keep the reasoning buffer across a bubble hand-over

Redaction runs over the joined reasoning buffer so a label and its value
split across two increments are seen together. A rotation started that
buffer over, so `password=` at the end of the old bubble and `hunter2` at
the start of the new one were judged apart and the value went out in the
clear.

The new bubble now starts with no step lines and a fresh throttle but the
same raw reasoning buffer. The old bubble is sealed with the hand-over line,
so the reasoning is not shown twice.

Also gofmt the two-replica test.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-31 01:39:14 +08:00
16f0d307dd fix(realtime): announce comments folded into a queued run (#9702)
A reply posted while the agent already has a queued run in the same thread is
folded into that run on the server: the reply becomes the run's trigger and the
earlier comment joins its covered set. The run's status does not change, so no
task event fired and open issue pages kept the run under the earlier comment,
leaving the new reply with no visible queue row.

Publish task:coalesced after the merge (and after a delegated-failure merge).
Web and desktop already refresh the issue's runs on any task: event, which moves
the queued block under the newest comment it answers; mobile now listens for it
too.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-30 17:45:54 +08:00
2083970b51 MUL-8374: fix: keep private chat content to the session owner (realtime + REST) (#9677)
* MUL-8374: fix(realtime): deliver private chat content only to the session owner

Chat events and chat-task transcript (task:message) frames were fanned out
to every connection in the workspace. Route every chat:* event, and
task:message for chat tasks, to the chat session creator's user scope
instead. Producers stamp the owner on the bus event (from the same
chat_session row they already read); an event without an owner is dropped
rather than widened. Chat-task lifecycle events keep workspace fanout for
agent presence.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(server): keep chat-task content private on REST task reads

The transcript endpoint now 404s chat tasks for anyone but the chat
session's creator, and the agent run list and workspace task snapshot keep
foreign chat-task rows but blank their result and error.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(realtime): scope private chat delivery to the owner's workspace connections

User-scope delivery reached the chat creator's connections to every
workspace. Those are authorized against their own workspace only, so a
creator removed from the chat's workspace kept receiving its transcript
and reply through a socket to another workspace. Chat content now goes to
a workspace_member scope each connection joins at connect time; it is
re-authorized against the chat's workspace on every write.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-29 19:13:45 +08:00
Multica Eveandmultica-agent f2a6398d6e MUL-7257: refactor(server): remove unused compatibility queries and capabilities (#9671)
* refactor(comments): drop redundant RFC3339 fallback parse

RFC3339Nano parsing already accepts timestamps without fractional
seconds, so the second time.Parse(time.RFC3339) attempt can never
succeed where the first failed. Keep the existing 400 responses and add
a test pinning second-precision since and the invalid since/before cases.

Co-authored-by: multica-agent <[email protected]>

* refactor(daemon): stop advertising unread plugin capabilities

execution-manifest-v1, agent-skill-v1 and remote-mcp-v1 have had no
server-side reader since the Plugin V1 gates were removed in #7144.
Drop the constants and the daemon advertisement.

Co-authored-by: multica-agent <[email protected]>

* refactor(autopilot): remove legacy no-reservation terminal queries

UpdateAutopilotRunCompleted, UpdateAutopilotRunFailed and
UpdateAutopilotRunSkippedWithResult have no production callers; terminal
transitions go through UpdateAutopilotRunTerminalWithQuota. Their
generated Params structs were still used as service argument types, so
replace them with service-local structs. The reconciler test now seeds
its crash window with an explicit fixture UPDATE.

Co-authored-by: multica-agent <[email protected]>

* refactor(tasks): remove unused StartAgentTask query

Production start paths use StartAgentTaskWithSupplement. Move the seven
test fixtures to the same query with both capability flags off, which
performs the same running transition. The empty-body start handler
branch is unchanged.

Co-authored-by: multica-agent <[email protected]>

* refactor(channels): remove unused pending_fresh queries

MarkChannelChatSessionPendingFresh, LockChannelChatSessionPendingFresh
and ClearChannelChatSessionPendingFresh have no callers. The context
generation path and ClearChannelChatSessionPendingFreshForRevision
remain.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-10-29 16:49:57 +08:00
bb3747186e feat: let agents steer running turns they could start (MUL-8285) (#9630)
* feat(server): let agents steer running turns they could start (MUL-8285)

An agent comment can now be added to another agent's running turn, the way
a member steers from the composer: exactly when the comment could start that
agent (canInvokeAgent, judged on the human at the top of the chain), never
its own turn.

- steer_task_ids now work for agent authors; steer: true asks the server to
  add the comment to the current running turn of every recipient without a
  named turn.
- Agent-authored bindings are capped per turn (10) and per authoring agent
  (3). The task row is locked in its own statement before the bind, so the
  cap count sees what an earlier lock holder committed.
- An agent comment that steered a turn but was never read is replayed on
  completion like the mention it replaced. Member receipts keep today's
  behaviour (the member sees the failure and can retry or resend).
- The supplement claim returns author_type and the agent's name. A daemon
  that does not send task-supplement-author-v1 gets the attribution in the
  content, so an agent message never reads as a human's.
- can_supplement on task-runs is computed for an agent caller by the same
  rule.

Co-authored-by: Tim Smart <[email protected]>
Co-authored-by: WitMiao <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(daemon): present an agent's added message as an agent's (MUL-8285)

Claims send the task-supplement-author-v1 capability and render agent
messages as peer input that cannot replace the objective or override humans.

Co-authored-by: Tim Smart <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(cli): add --steer and --steer-active to issue comment add (MUL-8285)

Docs: CLI help, CLI_AND_DAEMON.md, the CLI docs in every locale, the squad
leader briefing, and a steering section in the multica-platform mentions
reference.

Co-authored-by: Tim Smart <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix: close the review gaps in agent steering (MUL-8285)

- Replay an unread agent steer even when it reached the turn through an
  implicit route (a worker's reply to its leader): a comment bound to the
  completing run counts as planned input for the replay filter.
- Drop the receipt of an agent steer once completion has replayed it, so no
  client (old or new) offers to resend what the follow-up already handles.
- Never hand an agent's message to a daemon without
  task-supplement-author-v1: it would frame it as a human's. The message
  stays pending, settles when the turn ends, and is replayed as a mention.
- Tell a squad leader about --steer only when its daemon advertises
  cli-comment-steer-v1, since an older CLI rejects the flag; the skill
  reference says to drop the flag on 'unknown flag'.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Tim Smart <[email protected]>
Co-authored-by: WitMiao <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-28 17:09:59 +08:00
Bohan Jiangandmultica-agent d7d1831fcf feat(cli): add run-targeted retries with a fresh option (#9592)
Co-authored-by: multica-agent <[email protected]>
2026-10-27 18:18:32 +08:00
16a067f30a MUL-8270: fix(db): keep lease claims within query limits (#9563)
* fix(storage): claim one cleanup object per statement

* fix(tasks): bound stale dispatch recovery candidates

* fix(db): restore English lease claim documentation

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-27 12:59:48 +08:00
bcf40040e0 MUL-8233: fix Pi cancellation and guard unconfirmed cleanup (#9557)
* fix(agent): join Pi cancellation cleanup and guard continuation

Co-authored-by: multica-agent <[email protected]>

* fix(agent): preserve Pi completion and negotiate cleanup

Co-authored-by: multica-agent <[email protected]>

* perf(agent): avoid Pi exit delay and index cleanup guards

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Multica Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-27 12:37:13 +08:00
小七 f9b3065ae1 fix(handler): serialize project resource read-modify-write with a row lock (#9240)
UpdateProjectResource derives every column it writes back from the row
read at the top of the request, but the read and the write ran outside
any transaction. Two concurrent partial updates could interleave as
read-read-write-write, and the second writer's stale snapshot silently
reverted the first writer's change: a label-only PUT reverted a racing
ref update to an old Git ref, and a label-only PUT on a local_directory
restored a stale execution mode (reported in #9238 with a deterministic
interleaving that fails 15/15 under -race -count=3).

Serialize the flow with SELECT ... FOR UPDATE inside a short transaction
(GetProjectResourceInWorkspaceForUpdate + Begin/WithTx, same shape as
CreateAgent). Competing updates block on the row until the write lands,
then re-read fresh. This keeps the local_directory legacy label-mirror
writeback intact, which a presence-aware column rewrite alone would have
broken for mirror-triggered ref updates.

Review follow-ups:

- The regression test no longer leaves the race to the scheduler. Before
  resuming the paused label request, it waits until the ref request has
  either completed outright (the only exit on the pre-fix code, which
  never blocks) or is blocked waiting for the label request's row lock
  (the only exit with the fix, seen as wait_event_type='Lock' on the
  backend running the FOR UPDATE query, checked via pg_stat_activity).
  The two signals are mutually exclusive, so the test fails
  deterministically without the lock and passes with it.
- findLocalDirectoryConflict and requireWorktreeCapableDaemon now read
  through caller-provided queries instead of the handler's pool: on the
  update path they run inside the transaction, so a locked request no
  longer borrows a second pooled connection per request.
- Added a local_directory concurrency case: a label-only update racing
  an execution_mode update through the label-mirror ref rewrite.

The regression tests fail on the pre-fix code ("stored ref = main, want
the concurrent ref update (release)" / "stored execution_mode = worktree,
want the concurrent mode update (cleared = in_place)") and pass with the
lock, 20/20 runs each way.

Fixes #9238

Generated-by: GLM-5.3-Flash (ZCode)
2026-10-26 15:20:52 +08:00
Multica Eveandmultica-agent b51f42c54b MUL-8200: fix(autopilot): don't fail create_issue runs on issue blocked (#9472)
* fix(autopilot): settle create_issue runs by first task outcome, not issue blocked (MUL-8200)

Co-authored-by: multica-agent <[email protected]>

* fix(autopilot): settle create_issue runs only from their own dispatch chain (MUL-8200 review)

Co-authored-by: multica-agent <[email protected]>

* fix(autopilot): root create_issue dispatch chain at the recorded task and let its outcome win (MUL-8200 re-review)

Co-authored-by: multica-agent <[email protected]>

* fix(autopilot): resolve unlinked create_issue dispatch by agent and wait for pending enqueue (MUL-8200 review)

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-10-26 10:19:38 +08:00
Multica Eveandmultica-agent 4dc39434ef MUL-8198: fix(daemon): keep pre-588 Remote MCP tokens working for their issuing owner (#9469)
Co-authored-by: multica-agent <[email protected]>
2026-10-23 16:47:53 +08:00
04931df162 MUL-8174: fix OpenCode resume after host clock rollback (#9460)
* fix(opencode): retire future-dated resume sessions (MUL-8174)

Co-authored-by: multica-agent <[email protected]>

* fix(opencode): check resume clocks across CLI versions

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-23 14:41:13 +08:00
Multica Eveandmultica-agent 79d801f45c MUL-8188: fail fast on Kiro panic and stop resuming undrivable sessions (#9458)
* fix(kiro): fail fast when the runtime panics mid-turn (MUL-8188)

Kiro can panic inside its agent loop while the ACP process stays alive, so
session/prompt never answered and the task hung until the 2h idle watchdog
or a manual cancel. Watch stderr live for a panic header, abort the prompt,
and surface the panic text. A resumed session that panics before its first
message signals ResumeRejected so the daemon retries once on a fresh session.

Co-authored-by: multica-agent <[email protected]>

* fix(resume): stop re-resuming sessions the runtime cannot drive (MUL-8188)

- taskfailure.UnresumableHistory matches "invalid conversation history",
  so the daemon's fresh-session retry, classifyPoisonedError and
  ResumeUnsafeFailure all treat Kiro's panic as resume-unsafe; the
  GetLastTaskSession / GetLastChatTaskSession text guards mirror it for
  rows written by older daemons.
- A resumed run cancelled after >=5 minutes with zero agent output now
  retires its session: the daemon sends retired_session_id on cancel-ack
  and the server records it on the cancelled row (status CAS, never
  overwrites) and clears a chat pointer still naming it.

Co-authored-by: multica-agent <[email protected]>

* fix(resume): address review on retired sessions (MUL-8188)

- Manual retry no longer revives a retired session: the exact-source rerun
  claim path now checks IsTaskSessionRetired (fails closed), still reuses
  the source workdir, and sets PriorSessionResumeUnavailable.
- Silent-resume retirement measures awakeclock time, so host suspend
  (Windows monotonic clock counts through sleep) cannot retire a healthy
  session; adds a suspend regression test.
- Handler tests use dbfx.Issue/Task and testutil.Call per AGENTS.md.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-10-23 12:03:19 +08:00
Bohan Jiangandmultica-agent ff674c4e1d MUL-8185: Support sharing runtimes with specific members (#9455)
* feat: share runtimes with selected workspace members

Co-authored-by: multica-agent <[email protected]>

* refactor(runtimes): one-click private/public and popover member picker

Private and Public apply immediately again (restrictions still go through
the reviewed revocation dialog). Specific members opens a draft popover
with search, Cancel and Save; selected recipients are summarized under
the scope control with an Edit entry point. The editor moves to its own
runtime-access-editor component.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-10-23 09:28:22 +08:00
Multica Eveandmultica-agent 72d9058cc6 feat(telemetry): add visiting and delegating member counts (MUL-8161) (#9435)
* feat(telemetry): report visiting and delegating member counts in self-host snapshot v2 (MUL-8161)

Co-authored-by: multica-agent <[email protected]>

* fix(telemetry): count delegating members from a human-action ledger, drop v1 fallback (MUL-8161)

- member_delegation_day records (member, UTC day) at the human action itself,
  so retries, system wakeups and creator-fallback runs never count, and a
  comment folded into a queued run keeps both humans and their own days.
- Activity read failures now fail collection and use the existing retry
  instead of silently sending a v1 snapshot.

Co-authored-by: multica-agent <[email protected]>

* fix(telemetry): record create-with-assignee, honor DO_NOT_TRACK, build ledger PK concurrently (MUL-8161)

- A member creating an issue assigned to an agent or squad now records a
  delegation day, on both the immediate and deferred create paths.
- TaskService.RecordMemberActivity gates the ledger and is set from the
  startup telemetry config, so DO_NOT_TRACK stops collection too.
- member_delegation_day's primary key is built with CREATE UNIQUE INDEX
  CONCURRENTLY + PRIMARY KEY USING INDEX (589-591), with cleanup registered.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-10-23 07:40:05 +08:00
13eb4bda93 MUL-8154 fix(daemon): enforce runtime ownership on daemon routes (#9423)
* fix(daemon): enforce runtime ownership on daemon routes (MUL-8154)

/api/daemon/* accepts any workspace member's PAT/JWT, and authorization only
checked workspace membership. Any member could claim another member's tasks
(receiving the agent's custom_env, MCP config and an owner-scoped task token),
write task results/messages, and take a runtime over by re-registering its
daemon_id.

- Runtime-level check on every runtime/task daemon route, HTTP heartbeat,
  deregister, batch claim (and so the WS claim RPC) and the WS handshake:
  user credentials must own the runtime (NULL owner fails closed); daemon
  tokens must match a non-empty runtime daemon_id.
- Register: pre-check that every existing row on the daemon_id belongs to the
  caller, plus a required_owner_id fence in both upserts (incl. the
  failed-profile path) so a concurrent registration cannot win the row.
  Daemon-token registration keeps the existing owner.
- Legacy merge: only for user-credential registrations, and only when the
  caller owns both rows, verified on the FOR UPDATE-locked rows.
- Tasks without a runtime are not writable through daemon routes; status
  stays readable.
- GetTaskStatus / WS lease queries carry runtime owner/daemon so the hot
  paths keep one round trip.

Co-authored-by: multica-agent <[email protected]>

* test(daemon): authenticate fixtures as the runtime owner (MUL-8154)

Most handler tests drove daemon routes with a daemon token whose daemon_id
did not match the fixture runtime, relying on the workspace-wide token scope
this fix removes. They now authenticate the way real daemons do, with the
runtime owner's user credential (newOwnerDaemonRequest). Tests about daemon
token scoping keep mdt_ and get fixtures with a matching daemon_id; the
ownerless-runtime tests also assert that a user credential is refused.

Co-authored-by: multica-agent <[email protected]>

* fix(daemon): serialize registration per daemon and keep Qoder WS claims working (MUL-8154)

Review of #9423:

- P1: the owner pre-check and the per-provider/profile upserts hit different
  conflict keys, so two members registering different providers of the same
  daemon could both pass the pre-check and both insert, leaving one daemon
  with two owners (and a daemon token for either runtime reaching the
  other's). Registration now runs the pre-check and every runtime write in one
  transaction under a pg_advisory_xact_lock on (workspace_id, daemon_id), on a
  single connection so queued registrations cannot starve the holder. Legacy
  merges, analytics and the register event run after commit; failed-profile
  records use savepoints so they stay best-effort.
- P2: the Qoder revocation check read the Authorization header, which the WS
  RPC bridge's in-process request does not carry, so valid Qoder WS claims
  silently returned nothing. DaemonAuth now records the mdt_ token hash in the
  request context, the WS identity keeps it, the RPC bridge re-attaches it,
  and the check validates that hash on every call (revocation still applies
  to an open connection).
- Regression tests for cross-provider and provider+profile concurrent
  registration, and for valid/revoked Qoder credentials over WS RPC.

Co-authored-by: multica-agent <[email protected]>

* fix(daemon): bind daemon tokens to the runtime owner they were issued for (MUL-8154)

Second review of #9423: a daemon_id can be re-registered by another member
after its runtimes are deleted, and a daemon token was bound only to
(workspace, daemon_id), so the previous owner's Remote MCP token reached the
new owner's runtime (heartbeat, claim, owner-scoped task token).

- daemon_token.owner_id (migration 588). Remote MCP tokens are bound to the
  locked runtime owner inside FinalizeTaskClaim; live tokens are backfilled
  only where their daemon has exactly one owner.
- DaemonAuth carries the owner binding (DB and cache; cache key bumped to v2
  so pre-upgrade entries are never read), the WS identity keeps it and the
  RPC bridge replays it.
- Daemon-token authorization now also requires token owner == runtime owner.
  Qoder-managed tokens stay unbound (governed by cloud_agent_connection), and
  user credentials can no longer register the reserved qoder-managed-* ids.
- Tests: the reuse lifecycle over HTTP, a new WS handshake and an already
  open WS connection; the reserved daemon_id; the concurrency barrier now
  hooks the registration transaction (TxStarter) so the interleaving is real.

Co-authored-by: multica-agent <[email protected]>

* fix(daemon): owner-bind daemon-token registration; drop unsafe token backfill (MUL-8154)

Third review of #9423:

- Migration 588 no longer backfills daemon_token.owner_id from the daemon's
  current owner: that owner may be a later re-registrant, which re-bound the
  previous owner's live token to them. Existing non-Qoder tokens stay NULL and
  are refused (24h Remote MCP credentials, re-issued on the next claim).
- owner_id is a plain UUID column, no foreign key / cascade (AGENTS.md).
- Register via a non-Qoder daemon token now uses the token's owner for the
  in-transaction pre-check and the SQL owner fence, and refuses unbound
  tokens. Qoder keeps its connection-based check; daemon tokens still never
  trigger legacy merges.
- Tests: an old owner's bound token and an unbound legacy token can neither
  register over nor heartbeat the replacement owner's runtime.

Co-authored-by: multica-agent <[email protected]>

* fix(daemon): grant the Qoder registration exemption only with a live connection (MUL-8154)

Fourth review of #9423: register skipped the token owner binding for any
qoder-managed-* daemon token, but checked the Qoder connection only when the
body declared a qoder_cloud runtime. A pre-upgrade Remote MCP token under that
prefix (no owner, no connection) could register type=claude and rewrite
another member's runtime.

Register now verifies the token against a live cloud_agent_connection before
anything else whenever the token is in the Qoder namespace, regardless of the
declared runtimes, and restricts that namespace to the bridge's own shape: a
built-in qoder_cloud runtime, no custom profiles, no failed_profiles.

Test covers built-in, custom-profile, failed-profiles-only and qoder_cloud
bodies with an unconnected token, plus the connected token's allowed and
refused shapes.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-22 16:47:06 +08:00
0fe2ebf02e MUL-8045: feat(runtimes): Qoder Cloud Agent as a managed cloud runtime (#9402)
* feat(runtimes): add Qoder Cloud Agent managed remote execution

* fix(qoder): satisfy migration and localization CI contracts

* fix(qoder): bound execution and scope managed credentials

* fix(qoder): avoid upstream migration number collisions

* fix(qoder): gate issue creation and correlate tool results

* fix(qoder): synchronize guarded issue lifecycle transitions

* test(agent): publish Hermes holder identity atomically

* feat(agents): runtime-first create flow and cloud runtimes section for Qoder Cloud Agent

- Three-step create-agent flow (runtime -> configure -> done) with local,
  remote and cloud sections; Qoder connect form inline in the runtime step
- Runtimes page regrouped into remote and cloud sections with Qoder card
- Generalize Qoder storage to cloud_agent_connection (migrations 586/587)
- Locales for en, zh-Hans, ja, ko, fr; desktop and web routes for done step
- Fix status-picker fixture owner_id and diagnostics route coverage after main merge

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(runtimes): unify runtimes page into machines and cloud services

- One Add menu replaces Add remote runtime, the duplicated Add a computer
  and the web-only Cloud Runtime header button
- Group by machines / cloud services instead of local / remote / cloud, so
  web and desktop share one structure; this device is pinned with a badge
- Cloud services use the machine row anatomy in a single ListGrid table;
  unconnected Qoder is a row with Connect, coming-soon providers are rows
- Empty groups show the next step inline; search and online/offline filter
- Rename computer to machine across runtimes copy in all locales

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* test(runtimes): cover unified runtimes page; pin status and action tracks

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* refactor(runtimes): trim redundant descriptive copy on runtimes page

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(runtimes): keep row actions visible at narrow widths

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* refactor(runtimes): align Qoder add-menu item with other cloud services

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(runtimes): close the Qoder dialog after a first-time connection

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(agents): pick the runtime from one grouped list

The runtime step showed four kinds of cards, a comparison panel and
per-section descriptions. It is now one list in the runtimes-page layout:
machines (this one open, others folded to a row) and cloud services. An
unconnected Qoder opens the runtimes-page connect dialog in place and is
selected once it comes online.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(agents): show owners as 'you' and bare version numbers in the runtime list

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(qoder): disconnect action and dedicated Qoder runtime page

Disconnect removes the encrypted connection and the managed runtime, unbinding
its agents after the admin confirms them. The Qoder row now opens its own page
(status, agents, usage, configuration, pause/resume, disconnect) instead of the
machine layout; generic runtime delete and visibility refuse a managed runtime.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(agents): land on the done page after creating from step 2 on desktop

Desktop's location is the tab URL, which moves to /agents/new/done/<id>
while the configure page is still mounted. Its missing ?runtime= tripped
the skipped-step-1 redirect and replaced the tab back to /agents/new, so
Create looked like it did nothing. Only redirect on the manual route.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(agents): show only settings a Qoder agent can run with

The Qoder bridge fails every task whose agent carries a model, thinking or
speed override, env, custom args, custom skills, MCP config or connected
apps, and it runs issues only. The agent detail page still offered all of
those editors, so one save could break every later run.

For a qoder_cloud agent the page now keeps Instructions under Capabilities,
General / Access / QCA Agent under Settings, drops the model rows and the
header model chip, shows the QCA agent in the summary, and locks the
runtime. Other agents no longer see the Qoder runtime in their picker,
since moving onto it needs a QCA agent the server would otherwise reject.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(agents): hide conversation starters for Qoder agents

Starters only appear in a chat, and the Qoder bridge runs issues only.
The create flow already leaves them out for this runtime.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(runtimes): show the Qoder connection as cards with per-section editing

The Qoder runtime page rendered the whole connection as one always-editable
form. It now summarizes the connection, repositories and limits as read-only
cards; each section opens its own dialog and saves only that section.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(qoder): reuse a follow-up session only with the same repository and credential

Resolve repository credentials before deciding to continue a prior session,
and stamp each session with a fingerprint of its repositories, branches and
tokens. A follow-up continues only when the fingerprint still matches, so a
changed repository, branch or rotated/revoked token never rides into an old
session.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(qoder): keep a paused connection paused when its settings are saved

Saving repositories, limits or the connection no longer re-enables a paused
runtime. Resume goes through a new POST /qoder/resume.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(agents): drop settings a Qoder agent cannot run when creating one

A draft started on another runtime could carry hidden skills, model
overrides and starters into a Qoder agent that then failed every task. The
create request now drops them for Qoder, and the API rejects skills and
execution overrides on Qoder agents at save time.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* chore(db): regenerate sqlc models for cloud_agent_connection

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(runtimes): never list the Qoder runtime under Machines

When the server could not run Qoder (no MULTICA_VCS_SECRET_KEY /
MULTICA_QODER_STATE_DIR), the managed Qoder runtime fell back into the
Machines group as if it were a computer.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* refactor(qoder): drop MULTICA_QODER_STATE_DIR

The managed bridge keeps its run journal encrypted in cloud_agent_connection
and a database advisory lock already elects one supervisor, so the state
directory only held a redundant file lock and acted as a feature switch.
Qoder is now available whenever MULTICA_VCS_SECRET_KEY is set. The
standalone qoder-runtime command still uses its on-disk state_dir.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(qoder): keep the branch in the session fingerprint after a journal restore

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(agents): validate Qoder agents' effective settings on every write

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: 安辰 <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-22 01:33:38 +08:00
e720fe2219 MUL-8135: feat(wakeups): wake on linked PR merge conflicts (--until-pr conflicts) (#9406)
* MUL-8135: feat(wakeups): wake on linked PR merge conflicts (--until-pr conflicts)

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* MUL-8135: fix(wakeups): conflict wakes per PR identity, rotate the watch sweep

- A linked-PR condition only fires for a PR key the rule has not seen; a PR
  leaving the satisfying set no longer re-wakes the agent for the others.
- Conflict keys use the PR id, so same-numbered fork PRs on one head stay
  distinct.
- The conflict-watch sweep rotates an address cursor so failing fetches
  cannot starve the rest.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* MUL-8135: fix(wakeups): count repeated linked-PR keys when deduping

Checks and merged keys carry only the PR number, so same-numbered PRs from
two repositories share a key. Compare occurrence counts so the second one
satisfying still wakes the agent.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-22 01:23:23 +08:00
Multica Eveandmultica-agent 2b64569138 MUL-8126: feat(agent): steer running Kiro turns via _session/steer (#9396)
* feat(agent): support steering running Kiro turns via _session/steer

Kiro CLI 2.33.0 exposes an undocumented ACP extension, _session/steer,
that queues a message and injects it into the in-flight session/prompt at
the next tool or message boundary. Wire it up as the kiro backend's
Session.Supplement (active only while the prompt is in flight, and only
when the daemon negotiated task supplements), and gate kiro >= 2.33.0 in
SupportsTaskSupplement.

MUL-8126

Co-authored-by: multica-agent <[email protected]>

* fix(supplement): allow kiro in server-side steering whitelists

The capability handshake (StartAgentTaskWithSupplement), direct steering
(CreateTaskSupplement) and composer reply binding (BindCommentTaskSupplement)
each whitelist providers, and none included kiro, so a negotiated Kiro run
never got a capability row and steering stayed unreachable. Add kiro to all
three, regenerate sqlc, and cover kiro in the negotiation and both delivery
path tests. Also widen the kiro supplement test's readiness wait, which was
flaky under full-package parallel load.

MUL-8126

Co-authored-by: multica-agent <[email protected]>

* fix(supplement): allow grok in composer reply binding

BindCommentTaskSupplement was the only steering whitelist without grok, so
a reply from the normal composer to a running Grok turn fell back to the
next run even though direct steering worked. Add grok and cover it in the
composer binding test.

MUL-8126

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-10-21 19:01:56 +08:00
Multica Eveandmultica-agent 0beaeddc0e MUL-8120 feat(workflows): owner-based workflow edit/delete permissions (#9388)
* MUL-8120 feat(workflows): record workflow owner and let owners edit their workflows

Any workspace member may now create a workflow and becomes its owner.
Editing, deleting and changing handoffs is open to the owner and to
workspace owners/admins. Default has no owner and stays admin-only.
Copying a workflow for one project now also requires managing that
project. Migration 585 adds issue_workflow.owner_id and backfills
existing named workflows with the workspace's earliest owner.

Co-authored-by: multica-agent <[email protected]>

* MUL-8120 feat(workflows): mirror owner-based workflow permissions in the UI

Members can create and duplicate workflows; the editor, column handoff
and flow panel are editable by the workflow's owner and admins. The
save-for-this-project option only shows to admins and the project lead.

Co-authored-by: multica-agent <[email protected]>

* MUL-8120 fix(workflows): only admins add statuses through workflow writes

Workflow create/update could add statuses to the shared workspace
catalog without a role check. Non-admins now get 403
status_create_forbidden when a step names a status that does not exist;
reusing a status by key or by name stays open.

Co-authored-by: multica-agent <[email protected]>

* MUL-8120 fix(workflows): hide new-status for members in the workflow editor

Members pick from existing statuses only. A template that needs statuses
the workspace lacks shows which ones and keeps Save disabled until they
are removed or an admin adds them.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-10-21 18:34:21 +08:00
m2keloopandm2keloop d908e4882d MUL-6710: fix(runtimes): revoke access when making runtime private (#7580)
* fix(runtimes): revoke access when making runtime private

* fix(runtimes): regenerate queries for workflow step

---------

Co-authored-by: m2keloop <[email protected]>
2026-10-20 12:49:37 +08:00
Dante a790bc8c08 fix(comments): retain delivered supplement root owners (#9061) 2026-10-19 22:22:32 -04:00
Riley Rogersandsoulmate 2f247cc92f fix: respect explicit comment-trigger cancellation during reconciliation (#9300)
Co-authored-by: soulmate <[email protected]>
2026-10-19 03:15:40 -04:00
de198cd4c1 feat: allow quick actions without a target (MUL-7990) (#9210)
* test: cover quick actions without an agent

* feat: allow quick actions without a target

* test(quick-actions): cover clearing an existing agent target

* fix(quick-actions): keep existing locale copy

* test: consolidate quick-action regressions in existing test file

* chore(migrations): renumber quick action optional target migration to 584

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Multica Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-19 14:41:48 +08:00
dfa8bdaa53 MUL-8070: feat(cursor): support live-run steering via Cursor hooks (#9322)
* MUL-8070: feat(cursor): support live-run steering via Cursor hooks

Cursor's headless --print mode has no mid-turn input channel, but its
postToolUse/postToolUseFailure hooks can return additional_context that
reaches the model in the same turn. A steerable Cursor run now writes a
managed .cursor/hooks.json whose command is the multica binary in a hidden
relay mode; the relay forwards the hook event to a token-protected loopback
endpoint owned by the run, which hands back pending guidance tagged with a
per-run nonce. The prompt declares that tag so the model treats the context
as genuine user guidance instead of a prompt injection.

Steering is offered only for daemon-managed workdirs on Unix with cursor-agent
2026.10.14+, and the server allowlists cursor for capability negotiation,
supplement creation and composer binding.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* MUL-8070: fix(cursor): confirm steering only on Cursor's receipt and gate on installable hooks

- Count hook-delivered guidance as delivered only when the tool call's
  completed stream event echoes it in hookAdditionalContexts. Guidance
  Cursor dropped for the same call is retried at the next tool boundary;
  guidance without a receipt fails when the turn ends.
- Offer steering only when the workdir can carry the managed hooks.json,
  and fail supplements immediately if installation still fails after
  negotiation.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-18 16:23:05 +08:00
da1671ca20 MUL-7420: Project workflows — columns and handoffs on the workflow (#8814)
* feat(workflows): workspace workflows with step handoffs (MUL-7420)

A workflow is a named, workspace-level selection of statuses from the
shared status catalog, in board order, with an optional handoff on each
step. Projects opt in through project.workflow_id; a project without one
keeps the whole catalog (the implicit Default workflow), so existing
workspaces see no change.

- issue_workflow table + project.workflow_id, CRUD API under
  /api/issue-workflows, POST /api/projects/{id}/workflow with a dry-run
  status mapping plan. Only issues on statuses the target workflow does
  not list are remapped, and remapping never hands issues off.
- Issue writes keep the status inside the project's workflow. Entering a
  step with a handler through a status change assigns the issue to that
  handler and starts its run with the step's brief as the handoff note;
  an explicit assignee in the same write wins. Creating an issue starts
  at the workflow's starting step.
- Task claims carry the issue's project workflow, and the brief renders
  a Project Workflow section so agents move along this project's
  statuses instead of the built-in ones.
- Statuses a workflow lists cannot be archived.
- Adoption is gated by the project_workflows_v1 flag; existing
  workflows keep working and projects can always switch back to Default.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): workflow settings, project picker and workflow-aware boards (MUL-7420)

- Settings → Workflows (behind project_workflows_v1): the Default
  workflow plus named workflows, each shown as its step chain with
  handlers and the projects using it; an editor to pick statuses from the
  library, order them, mark the starting step, and set each step's
  handoff, instructions and next/back statuses.
- Project Workflow property: switching dry-runs first and asks where
  issues on unlisted statuses go; nothing else moves and nothing is
  handed off.
- A project board's columns are its workflow's steps, and each column
  head names who entering it hands the issue to.
- Status pickers offer only the issue's workflow steps and say who each
  hands off to; boards refuse drops onto statuses the dragged card's
  workflow does not list.
- New issues in a workflow project start on its starting step, with a
  hint naming who they will be handed to.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(workflows): name built-in steps once in the brief; roomier picker hints (MUL-7420)

Built-in statuses have no stored name, so the handoff brief printed
`todo` todo. Print the key alone when the name adds nothing.

The status picker widens for workflow projects and shows the handoff as
an arrow and name, so step names no longer truncate to one letter.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): handoff previews and a single timeline entry per handoff (MUL-7420)

- GET /api/issues/{id}/workflow-handoff?status= says what moving an issue
  would do: who it goes to, which runs of its current agent are still
  active, and the brief the handler receives. Backs the confirmation.
- POST /api/issue-workflows/preview-brief renders a step's brief from an
  unsaved workflow, for the editor's preview.
- Mapping plans list the statuses that stay, so the switch dialog can say
  which issues are untouched.
- A handoff is recorded on the status_changed activity (workflow, handler,
  whether a run started, the brief) instead of a separate assignee row.
- The brief now reads like the one shown in the design: the workflow, the
  current step, its instructions, the statuses, and the commands to hand
  off.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): rebuild the workflow UI to the reviewed design (MUL-7420)

The first cut used generic controls where the design had purpose-built
ones. This brings every surface up to the mockup:

- Settings → Workflows is one page: the workflows (step chain with
  handler avatars, handoff count, projects using it; Default first) and,
  below, the shared status library with each status's workflow usage.
- The workflow editor is a full page instead of a dialog: draggable step
  cards summarising each handoff, and a panel for the selected step —
  swap its status, pick the handler with a segmented control and a
  searchable target, handoff instructions, next/back statuses, a preview
  of the brief the handler receives, duplicate and delete.
- The project Workflow picker lists each workflow's statuses, handoffs and
  projects, links to settings, and always confirms the switch with what
  moves and what stays.
- Moving an issue into a handoff step (status picker, table, board drop)
  asks first: who it goes from and to, a default-on option to stop the
  previous agent's run, and the brief.
- Status pickers name the workflow and project, show each step's handler
  with an avatar or that the assignee stays, and explain themselves.
- Boards: the handler chip in project column heads, hatched columns a
  dragged card cannot enter with the reason, and a drop hint naming who
  the card will go to.
- The timeline records a handoff under its status change, with the brief
  the run received; the issue sidebar shows the workflow it follows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(workflows): word a queued previous run as queued in the handoff confirm

The confirm dialog said the previous agent was "still running (for N
minutes)" even when its task had only been queued. Queued runs now read
as queued, and the checkbox cancels them instead of stopping them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): refuse a run's status change once the issue left its step

A run decides where the issue goes next from the step it was queued for.
When someone else moved the issue while the run worked, applying that
decision undid their move: in the live Grok run, a PM parked NW-1 as
blocked and the still-running agent moved it straight back to code
review.

Tasks now record the issue's status when they are queued
(agent_task_queue.workflow_step, written by the task INSERTs; automatic
retries keep their parent's step), and a run's own status changes move
it along. In a workflow project, an agent status change from a run whose
step no longer matches the issue is refused with 409
workflow_step_moved, naming where the issue is now and who moved it, so
the agent stops instead of retrying.

A handoff away from a squad now also stops the runs made on the squad's
behalf, and stopped runs record who stopped them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(workflows): keep every status entry point inside the issue's workflow

The issue context menu listed every catalog status and applied handoff
steps without the confirmation the status picker and board show, so a
right-click could pick a status the workflow refuses or start an agent
unasked. It now offers the workflow's steps and opens the handoff
confirmation through a shared modal.

One-click status actions no longer assume built-in statuses a workflow
may not list: "mark done" lands on the workflow's done step (hidden when
it has none), un-marking a duplicate returns to the workflow's start,
and "mark duplicate" is offered only when the workflow lists cancelled.
The batch toolbar offers a single project's workflow statuses when the
selection sits in one project.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(workflows): keep issue wakeups consistent with workflow steps

Three places where project workflows and issue wakeups met:

- A wakeup run is queued when it fires, so it took the issue's status at
  that moment as its workflow step. A wakeup an agent registered then
  bypassed the stale-step refusal: parked by a PM, the issue could be
  moved on by the agent's "check CI, then hand to review" wakeup. It now
  keeps the registering run's step; a member's wakeup still starts from
  the issue's status.
- Retries carried handoff_note only for wakeup runs, so a retried
  workflow handoff lost its brief. Rows with a workflow step keep it.
- Switching or editing a workflow remaps statuses in bulk without the
  close rule, so an issue remapped into a done status kept its wakeups
  and pending wakeup runs. The remap now ends them like any status write.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(wakeups): don't also wake the agent a write hands the issue to

A workflow handoff or an assignment already starts the receiving agent's
run. If that agent also subscribed an event wakeup to the issue's status
or assignee, the same write queued a second run for one fact, and
workflows made such writes frequent.

The write now names the agent it hands the issue to in the
transaction-local multica.handed_to_agent (the new agent assignee, a
squad's leader, or a step's handler even when it already owned the
issue), and wakeup capture skips that agent's subscriptions for the
write's issue.* events. Other agents' wakeups, and task, comment and
reaction events, are captured as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): tell agents not to watch workflow issues with wakeups

The handoff brief and the runtime's project workflow section now say
that each step's handler gets a run when the issue enters the step, so
wakeups on a workflow issue are for waiting on time or on something
outside it, not on its status, assignee or runs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(workflows): refresh project usage after project changes

Co-authored-by: multica-agent <[email protected]>

* feat(workflows): MUL-7420 split columns from per-project handoffs

Workflows now only define a board's columns between a fixed head
(backlog, todo) and tail (blocked, done, cancelled). Handoffs move to
project.workflow_handoffs, so projects can share columns and still hand
off to different people. Default becomes a real, editable row that every
project without a named workflow follows. Adds project handoff, add-column
(project fork or shared) and project brief preview endpoints; same-agent
adjacent columns re-run the agent.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(core): MUL-7420 project handoffs and Default workflow client model

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): MUL-7420 column-header handoffs and project workflow menu

Board column headers set the project's handoff for that column; the
project toolbar gets a workflow menu (full flow, switch, templates) and an
add-column action. Views outside a project show Default's columns, with
other statuses reachable through a status filter. Adds the issue flow card,
create-project workflow choice, a columns-only workflow editor and a
lighter status picker.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(views): MUL-7420 wrap create-project workflow label in a menu group

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): MUL-7420 keep workflow structure in settings; no squad handoffs

Projects no longer add columns or create workflows: the add-column
endpoint is removed and creating a workflow is admin-only. Handoffs go
to one agent, a member or the project lead; squads route work themselves,
so a handoff to one is rejected, as is the issue-creator handler.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* refactor(core): MUL-7420 drop the project add-column client

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): MUL-7420 simpler handoff picker and workflow editor form

The handoff form is one searchable "Hand to" picker (no handoff, the
project lead, an agent or a member); the next status defaults to the next
column and next/back overrides sit under "More options". The workflow
editor gets labelled name and description fields and shows the fixed
statuses as one start and one end row. Templates move from the project
menu to Settings → Workflows → New, and the project board loses its
add-column action.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* refactor(server): MUL-7420 keep handoffs on the workflow, make backlog and blocked optional

Handoffs move back onto workflow steps: a workflow is the whole pipeline,
and a project only picks one. The project lead stays a relative handler, so
projects can still share a workflow. Handoff edits are admin-only, through
the workflow write or PUT /api/issue-workflows/{id}/handoffs/{status}; the
brief preview moves to the workflow too. Drops the unreleased
project.workflow_handoffs migration.

Todo, done and cancelled are required; backlog and blocked are optional and
keep their fixed places. Without blocked, the brief points agents at the
step's back status.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Cursor <[email protected]>

* feat(views): MUL-7420 edit handoffs on the workflow, optional backlog and blocked

The workflow editor sets each column's handoff in its side panel, and
Backlog and Blocked switch on and off. The board's column popover and the
flow panel edit the workflow's handoff, say how many projects it reaches,
and are read-only for non-admins. Drops the per-project handoff client
code.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(wakeups): MUL-7420 keep platform rules on writes that hand off nobody

Migration 579 skips an issue.* event for the agent a write hands the issue
to. Its filter compared agent_id with the transaction's handed_to_agent
setting using IS DISTINCT FROM, which is false when both are NULL. Main's
platform rules (child_done) have no agent_id, so every issue.* event that
handed the issue to nobody was dropped for them: a parent leaving backlog
never woke its agent for sub-issues that closed meanwhile
(TestChildDoneHeldWhileParentInBacklog).

Rules without an agent are never the handoff target, so they always
capture. Extends TestHandoffDoesNotAlsoWakeTheHandler with a platform rule
on a handoff and on a plain status change; the latter fails on the old
filter.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): MUL-7420 three-way handoff choice; quieter move confirm

The handoff form picks one of three directions, as in the mockup: no
handoff, hand to an agent (starts a run), or hand to a member, project lead
included (assignee only). What the step does, where it goes next and the
back status only reach an agent's brief, so they show and save for agent
handoffs alone.

The board's move confirmation keeps the agent's brief collapsed. Create
project shows the workflow picker even when Default is the only workflow,
with a note on where to make more. The hidden-columns panel says a board's
columns come from its workflow, with a link to manage workflows for admins.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): confirm handoff on project move, per-project handoff copy, two-pane editor (MUL-7420)

- A single issue moved into a workflow project from issue detail confirms
  the handoff of the column it lands on; the write opts in with
  enter_workflow_step. Batch, API and CLI moves stay administrative.
- The handoff preview takes project_id to preview such a move.
- Saving a shared workflow's handoff from a board can keep the change to
  that project: the server copies the workflow for it and switches the
  project in one transaction (copy_for_project_id).
- The column popover says which workflow it edits and who else uses it.
- The workflow editor is a step list with a detail panel for the
  selected step.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* style: gofmt workflow tests

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): MUL-7420 show next/back always; edit workflow name and description in place

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): MUL-7420 trim copy on the workflows settings page

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix: MUL-7420 keep pre-workflow behavior for workspaces that never adopt workflows

The run step guard now applies only to workflows that hand off, and the
standalone Statuses settings page keeps its original header.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(views): MUL-7420 use a solid placeholder tone on the workflow header

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(workflows): keep the runtime brief cache-stable; fix project board and batch status gaps (MUL-7420)

- Move the project workflow out of the runtime brief into the per-turn
  prompt so CLAUDE.md stays byte-identical across steps and issues.
- An explicit status filter overrides the project workflow's columns; the
  hidden-columns panel lists statuses outside the workflow.
- Batch status offers only statuses every selected workflow allows; a
  partial batch rolls back, refetches and warns.
- setProjectWorkflow returns void instead of an unparsed body.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Lambda <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-10-16 16:49:49 +08:00
86020b88a4 MUL-8047: chore(server): remove the pre-v3 desktop onboarding shim (#9291)
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-16 16:41:46 +08:00
Jiayuan Zhangandmultica-agent 231dedb552 MUL-8034: chore: clean historical comments across the codebase (#9274)
* chore: clean historical comments (MUL-8034)

Co-authored-by: multica-agent <[email protected]>

* docs: preserve pending Composio expiry contract

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-10-15 21:54:33 +08:00
d3c1b44d75 MUL-8014: fix(realtime): revoke workspace WebSocket access (#9249)
* fix(realtime): revoke workspace access on membership removal

Close removed members' sockets, revalidate registration, and gate captured write batches with fresh coalesced membership queries. Cover relay failure, replay, queue races, and transactional removal/rollback (MUL-8014).

Co-authored-by: multica-agent <[email protected]>

* fix(realtime): keep registration free of database waits

Co-authored-by: multica-agent <[email protected]>

* perf(realtime): narrow removal event prefilter

Co-authored-by: multica-agent <[email protected]>

* test(wakeup): avoid global backfill in workspace default test

Co-authored-by: multica-agent <[email protected]>

* fix(realtime): bound concurrent authorization and defer relay revocation

Co-authored-by: multica-agent <[email protected]>

* test(realtime): check caller budget without waiting for expiry

Co-authored-by: multica-agent <[email protected]>

* fix(realtime): decouple member-list invalidation from revocation

Co-authored-by: multica-agent <[email protected]>

* perf(realtime): skip removal decoding for member actor metadata

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-15 12:36:00 +08:00
52b26900c5 MUL-8012: fix(inbox): restrict notifications to current workspace recipients (#9242)
* fix(inbox): restrict notifications to current workspace recipients

Removing a member or leaving a workspace deletes their subscriptions, but
several notification sources keep addressing the user by a retained ID:
reactions to their issues and comments, retained assignments, explicit
mentions, squad mentions through surviving squad-member rows, quick-create
outcomes and children_done for a retained parent assignee. CreateInboxItem
inserted those rows unconditionally and the inbox:new event was then sent to
the user's own connections, so a former member could keep receiving new
issue titles and comment content.

Guard the shared insert instead of each source: a member recipient must still
be a member of the item's workspace and an agent recipient must belong to it.
An ineligible recipient yields no row (pgx.ErrNoRows), which every caller now
treats as a quiet skip without publishing inbox:new or failing the underlying
operation. The check runs in the statement's snapshot and takes no row locks;
historical inbox rows are left untouched.

Existing delivery tests created recipients without membership rows; their
fixtures now add the membership the production path requires.

* test(inbox): assert children_done settles receipts when the recipient left

* refactor(inbox): centralize eligible notification writes

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: 千乘妍 (Xiaoyaner) <[email protected]>
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-14 14:55:37 +08:00
Multica Eveandmultica-agent 9310690e0a MUL-7933: fix(wakeups): drain inputs and due checks in one pass (#9202)
* fix(wakeups): drain pending inputs and due checks in one dispatch pass

A dispatch pass visited a single batch of 100 rules ordered by updated_at.
With ~1,160 ready rules in production, a status-change hint waited about
a dozen 30s passes behind condition checks with nothing new, so
--until-status handoffs fired minutes late (MUL-7933, #9139).

Each pass now takes batches from two lanes until both drain or a limit
is hit: rules with pending inputs (at most 50 per batch, not filtered by
enabled) and due schedules/deadlines/condition checks (the rest of each
batch). Rules are visited at most once per pass; the pass stops after 15s
or 1,500 rules so the sequential scheduler keeps time for other jobs.
Global passes log lane counts, duration, stop reason and the oldest
pending input age (warn when cut short or older than 2 minutes).

Co-authored-by: multica-agent <[email protected]>

* fix(wakeups): keep due checks moving behind slow inputs; report held inputs apart

Review follow-up on #9202.

- A pass ran a whole batch's inputs before its due rules and checked only
  the overall 15s budget, so a few inputs near the 2s dispatch cap could
  starve schedules, deadlines and condition checks. Dispatches now
  alternate between the lanes, and while due work remains the input lane
  stops at half the pass budget; either lane uses the rest once the other
  drains.
- The pass log counted every unprocessed receipt as backlog, but dispatch
  keeps inputs pending on purpose while a run holds them (reserved by a
  claim, the rule's own run claimed, or waiting to join a queued run). Those
  are now logged as held_inputs, and only the oldest input the scheduler
  could act on (oldest_ready_input_age_s) can raise the 2-minute warning.

Co-authored-by: multica-agent <[email protected]>

* fix(wakeups): count only inputs a run can still take as held

Review follow-up on #9202. The pass log's held count hid real backlog in
two cases, so a stalled wakeup could sit for minutes without a warning:

- An input whose task_id pointed at a run that ended (cancelled/failed)
  before starting was counted as held, but takenReceipts releases it on
  the next dispatch. Reserved now means the run has neither started nor
  ended, as takenReceipts decides; an input of a run that started is
  consumed on the next dispatch and also counts as backlog.
- A platform rule's input was held by any queued run on the issue. It is
  now held only by a queued run of the issue's current target agent, run
  as the person the platform rule's run would run as, reusing
  resolveWakeTarget, childDoneRunAs and hasWaitingRun as dispatchSystem
  does.

PendingWakeupInputs is replaced by ListPendingWakeupInputGroups (per-rule
groups, oldest first, bounded) plus pendingInputs in Go.

Co-authored-by: multica-agent <[email protected]>

* fix(wakeups): keep tick tests and passes stable under load

CI failed the tick tests with "timeout: context deadline exceeded": under
load the 100ms scan-order touch after each dispatch timed out, and the
pass reported that as an error. The touch only affects scan order (the
rule's outcome is already committed and an unfinished rule stays a
candidate), so a failed touch is now logged at debug, not returned.

The tests also depended on wall-clock speed. The drain tests now run with
a generous budget, since they pin which rules a pass reaches; the slow
input test checks dispatch order (at most two slow inputs before the last
due check) instead of what fits in 1.5s. Without the input budget it
still fails, with all ten inputs ahead of the last due check.

Co-authored-by: multica-agent <[email protected]>

* fix(wakeups): scan every pending input group; warn when the scan is incomplete

Review follow-up on #9202. The pending scan read only the oldest 1000
groups, so a ready input behind a page of held groups left
oldest_ready_input_age_s empty, and a scan that hit its 1s timeout was
dropped without a trace; either way a stalled wakeup did not warn.

- ListPendingWakeupInputGroups is keyset-paged on (oldest, wakeup_id,
  reserved); pendingInputs reads pages until a short one.
- The scan gets 3s. On an error or timeout the pass logs
  pending_scan=incomplete with the error, at warn level, together with
  the partial counts; a finished scan logs pending_scan=complete.
- A pass with dispatch failures also logs at warn level.
- Platform-rule target checks are cached per issue.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-10-13 14:15:10 +08:00
2penheimerandCursor 41c9bbafec fix(projects): preserve omitted fields in concurrent updates (#9111)
Co-authored-by: Cursor <[email protected]>
2026-10-12 12:47:28 +08:00
Basuotian 3647ec9a9c MUL-7687 fix(wecom): push an issue notification through the acting agent's bot (#8803)
In a multi-bot organization, every issue notification a member received arrived
through whichever WeCom bot they had bound most recently, because
FindChannelBindingForMember keys on the recipient alone. Three agents' comments
all looked like they came from one of them, and the apparent sender drifted
whenever the member re-bound an account.

An inbox item already carries the actor that raised it. When that actor is an
agent, the WeCom push now resolves the bot as the binding the recipient holds
on the acting agent's active installation
(FindChannelBindingForMemberOnAgentInstallation), keeping the recipient-wide
lookup as the fallback — for an agent this member never linked, and for
member-authored notifications. Delivery is never narrowed, which is the
guarantee #7136 named.

When the preferred bot provably put nothing on the wire, the recipient-wide
binding is tried too. Proving that needed a four-state relay verdict
(relayNotWired / relayAccepted / relayNoHolder / relayUncertain) in place of a
bool, plus a non-fencing DedupeStore.ClaimState read so the publisher can
observe a delivery claim without fencing a worker that is mid-delivery. An
unclaimed frame is fenced under a token no delivery worker ever presents, so a
late or replaying dispatcher cannot add a second card after the fallback ran.
An uncertain send is deliberately not retried.

The acceptance wait runs on the request's own goroutine, so it is paid only on
the agent-attributed attempt — the one with a second route behind it — and is
bounded by the caller's context rather than by the window alone.

Reviewed-by: seacen
Reviewed-by: Eve (MUL-7687)
2026-10-12 11:32:52 +08:00
xjh a820c9a0ef fix(task): preserve autopilot attribution after run completion (#9158) 2026-10-10 18:24:05 -07:00
苗大 3bacc41f84 MUL-7573 fix(lark): clear the Typing reaction without trusting in-process state (#8656)
* fix(lark): persist and reconcile typing cleanup across replicas

- preserve terminal trigger targets and per-input cleanup ownership
- reconcile late Adds and source deletion without delaying replies
- recover interrupted ledger indexes and cover lifecycle boundaries

Refs: #8655

* fix(lark): restrict typing cleanup to the current application

- match both app operator type and the current App ID
- preserve foreign-app, human and unknown-owner reactions
- cover ownership filtering in fast and durable cleanup paths

Refs: #8655

* fix(lark): bound typing recovery and credential retention

- recover taskless indicators after lost debounce timers
- cap workspace ledger admission and redact terminal credentials
- isolate maintenance budgets and test retention and quota boundaries

Refs: #8655

* docs(lark): document typing cleanup recovery and rollout limits

- explain ownership, retention, quota and observable terminal outcomes
- document migration upgrades and safe application rollback

Refs: #8655

* fix(deps): require patched Go 1.26.9 and x/net 0.60

- Require the patched Go standard library to clear reachable vulnerabilities.
- Upgrade x/net to the matching HTTP/2 security release.
- Align local development prerequisites with the minimum Go version.
2026-10-08 20:20:15 -07: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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-27 22:43:43 +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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-27 21:08:54 +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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-27 18:15:01 +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 <[email protected]>

* 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 <[email protected]>

---------

Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-27 15:58:00 +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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-27 02:26:24 +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 <[email protected]>

---------

Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-25 14:30:50 +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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* 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 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-25 04:42:26 +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
Bohan Jiangandmultica-agent 0da0b46bbf MUL-7672: only a closing keyword completes an issue on PR merge (#8794)
* feat(pr): only a closing keyword completes an issue on merge (MUL-7672)

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

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

Co-authored-by: multica-agent <[email protected]>

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

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

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

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

Co-authored-by: multica-agent <[email protected]>

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

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

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

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-09-24 14:56:59 +08:00
9df0cae78a MUL-7429: complete issues when every linked PR merges (#8758)
* feat(pr): complete issues when every linked PR merges (MUL-7429)

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-24 02:58:19 +08:00
d45aba1cd7 fix(tasks): settle supplements in application transactions (MUL-7630) (#8755)
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-23 18:02:59 +08:00