Commit Graph

474 Commits

Author SHA1 Message Date
Jiayuan Zhang
ead32a8e50 fix(terminal): Phase 4 review round 2 — Desktop token first-frame auth, opened-frame rollback (MUL-2295)
Two fixes for Emacs Round 2 findings:

1. Desktop TerminalPanel now sends `{type:"auth", payload:{token}}` as
   the first WS frame in token mode and waits for `auth_ack`. Previously
   it transitioned straight to "connected" without authing, so the
   server's 10s first-frame deadline closed the connection and the
   terminal never opened. Cookie-mode apps still skip the handshake.
   Exposes ApiClient.getToken() so the panel can snapshot the bearer
   without reaching into auth-store internals.

2. terminalBridge.handleOpen no longer treats `terminal.opened` as a
   droppable control frame. sendFrame now returns bool; if the ack
   can't be enqueued the bridge rolls the half-open session back
   (cancel ctx, drop the route, sess.Close) so the spawned PTY doesn't
   orphan until the idle sweep — which also pinned the GC active-env
   mark for that window.

Regression coverage:
- TestTerminalBridge_OpenedEnqueueFailureClosesSession: drop-only
  sender, spawn must happen exactly once, manager and bridge route map
  must drain to zero.
- packages/views/issues/components/terminal-panel.test.tsx: asserts
  the auth frame is sent in token mode and NOT sent in cookie mode.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-16 19:27:51 +08:00
Jiayuan Zhang
0a97663acb feat(terminal): Phase 2 — daemonws routing + server proxy + Desktop xterm.js (MUL-2295)
Wires the Phase 1 terminal.Manager into the live daemonws transport so a
browser tab on the Multica Desktop app can attach to a PTY running in
the daemon's task workdir. End-to-end frame path:

  Desktop (xterm.js)  → /ws/issues/{id}/terminal (server proxy, cookie
                       or first-frame JWT auth, workspace membership
                       enforced before upgrade)
  → daemonws hub      (new SendToRuntime + TerminalRouter routing
                       terminal.* frames back to the proxy by
                       request_id, then re-keyed on session_id)
  → daemon            (new terminal_bridge.go owns one Manager per WS
                       connection, drains PtySession.Output() into
                       terminal.data frames, surfaces ExitC as
                       terminal.exit; closeAll on WS disconnect)
  → terminal.Manager.OpenWithInfo (new method) — server resolves
    task.work_dir/issue_id/prior_session_id from its DB and embeds
    them on the protocol; daemon trusts the server payload, no
    daemon-local task cache needed.

Auth + ACL:
- Browser proxy enforces workspace membership before the upgrade.
- TerminalOpenPayload carries the resolved task info; cross-workspace
  is structurally impossible at the bridge layer because both
  OpenParams.WorkspaceID and TaskInfo.WorkspaceID come from the same
  server-resolved field.
- terminal_bridge maps terminal.{Manager.OpenWithInfo} errors to the
  protocol error codes (workspace_mismatch / task_not_found /
  unsupported_os / spawn_failed / internal).

Resume:
- Server passes prior_session_id; daemon injects CLAUDE_SESSION_ID +
  MULTICA_{WORKSPACE,ISSUE,TASK,USER}_ID into the PTY env per the RFC.
  `claude --resume \$CLAUDE_SESSION_ID` continues the agent's session.

Lifecycle:
- Daemon installs a fresh terminalBridge per daemonws connection and
  tears every PtySession down on disconnect; session_ids minted on one
  WS cannot be reused on a reconnect because the server-side routing
  registration is also gone.
- daemonws client.send buffer raised from 16 to 256 and read limit
  from 4KB to 64KB so PTY traffic fits without evicting connections
  used for heartbeat / wakeup hints.

Desktop UI:
- packages/views/issues/components/terminal-panel.tsx renders an
  xterm.js console with FitAddon, base64 wire encoding, ResizeObserver
  → terminal.resize, reconnect button, and a clear web-only placeholder
  when window.desktopAPI is absent.
- TerminalPanelSection wrapper hangs collapsed in the issue-detail
  sidebar next to the execution log so bootstrap doesn't run for
  every issue view.

Tests:
- terminal/manager_test.go: OpenWithInfo happy path + cross-workspace
  reject (16 tests total, all -race clean).
- daemonws/terminal_test.go: TerminalRouter request_id → session_id
  re-keying and unknown-session drop.
- daemon/terminal_bridge_test.go: server-supplied workdir round-trips
  through OpenWithInfo, missing-workdir surfaces task_not_found and
  never spawns, data+exit round trip.
- GOOS=windows go test -c clean for both daemon and daemon/terminal.

Phase 1 / 2 / 3 / 4 mapping unchanged: Phase 3 (CLI) and Phase 4
(execenv GC hook + issue runs entry + audit log) are still untouched.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-16 17:45:53 +08:00
Jiayuan Zhang
380c6b5122 feat(usage): add Time and Tasks to daily-trend toggle (MUL-2283) (#2709)
Extends the workspace /usage page Daily tokens chart toggle from
Tokens | Cost to Tokens | Cost | Time | Tasks, so users see daily
run-time and task-count trends alongside spend without leaving the page.

- New SQL `ListDashboardRunTimeDaily`: per-date totals from
  agent_task_queue (terminal tasks only), scoped to workspace and
  optionally project. Same time anchor as ListDashboardAgentRunTime
  so day boundaries line up.
- New handler GET /api/dashboard/runtime/daily + TanStack Query option.
- New DailyTimeChart (single-series, smart h/m/s unit) and
  DailyTasksChart (completed + failed stacked).
- Empty-state is per-metric so a workspace with tokens but no terminal
  runs (or vice-versa) doesn't get a false "no data".
- i18n: en + zh-Hans daily.metric_time / metric_tasks + titles.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 18:51:02 +02:00
Naiyuan Qing
0079a73430 fix(views): narrow agent/squad create dialogs to max-w-2xl (#2706)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 23:09:15 +08:00
Jiayuan Zhang
3698fd85d5 feat(views): show Total in daily token/cost chart tooltips (MUL-2282) (#2704)
* feat(views): show Total in daily token/cost chart tooltips (MUL-2282)

Add a Total row at the bottom of the daily-tokens-chart and daily-cost-chart
tooltips so users can see the precise stack sum on hover, in addition to the
per-stack breakdown.

Implemented by extending shared ChartTooltipContent with an optional `footer`
prop (ReactNode | (payload) => ReactNode) that renders below the items with a
top divider; backwards-compatible (no behavior change when footer is omitted).

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

* fix(views): i18n Total label in chart tooltips (MUL-2282)

Lint rule i18next/no-literal-string flagged the hardcoded "Total" string
in daily-cost-chart and daily-tokens-chart tooltips. Move it to
runtimes.charts.tooltip_total and read via useT.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 15:50:20 +02:00
Jiayuan Zhang
8e88156356 Add assignee grouping for issue boards (#2693) 2026-05-15 18:44:08 +08:00
iYuan
d8635ad580 fix(issues): prevent duplicate active issue creation (MUL-2225) (#2602)
* fix: prevent duplicate active issue creation

* fix(issues): address duplicate guard review

* fix(autopilot): skip duplicate issue admissions

* fix(issueguard): tighten duplicate lookup edge cases

* test(issues): cover duplicate guard autopilot skips

* feat(autopilots): group skipped runs in history
2026-05-15 18:27:56 +08:00
Bohan Jiang
57be69517f feat(views): progressive disclosure for issue sidebar properties (MUL-2275) (#2675)
* feat(views): progressive disclosure for issue sidebar properties (MUL-2275)

Split sidebar Properties into a core group that always renders
(status / priority / assignee / labels) and an optional group
(due_date / project / parent) that only appears when the issue has
the value set or the user explicitly added it via a new
"+ Add property" picker. A field cleared in-session stays visible
to avoid row flicker; navigating to a different issue reseeds
visibility from that issue's set fields. The standalone "Parent
issue" card is folded into Properties as one of those optional
rows. Adds `defaultOpen` to DueDatePicker / ProjectPicker so a
newly-added row drops the user straight into edit state.

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

* refactor(views): swap sidebar optional set to due_date + labels

Per design feedback: status / priority / assignee / project / parent
are all required and should always render in the sidebar; only
due_date and labels are progressive-disclosure optionals. Move project
and parent rows out of the optional block (drop their +Add property
menu entries and the parent special-case in addOptionalProp). Move
labels into the optional block, gated on the issue's actual attached-
label count (queried via issueLabelsOptions), with defaultOpen wired
through LabelPicker so picking "Labels" from +Add property drops the
user straight into the picker. Tests updated for the new split.

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

* refactor(views): restore standalone parent card, move priority to optional

Parent goes back to its own collapsible section, rendered only when the
issue actually has a parent — matching the pre-MUL-2275 behavior. It is
no longer interleaved with Properties rows.

Priority joins the progressive-disclosure set (priority / due_date /
labels). New issues default to priority "none", so the row is hidden
until set or added via "+ Add property", and PriorityPicker gains
defaultOpen so the field drops straight into edit state when chosen
from the add-property menu.

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

* refactor(issue-detail): tighten Add-property popover visual rhythm

Picked up a small visual inconsistency while reviewing the PR's UI:
the "Add property" dropdown floated above the inspector at a noticeably
larger type scale than the property rows, and each item was bare text
while the rows it sat above all rendered with an icon + value pair.

Tweaks:
- Items: `text-sm py-1.5` → `text-xs py-1`, matching the inspector
  row typography and trimming row-to-row gap from 12px to 8px.
- Each option leads with the icon the resulting picker uses
  (`PriorityIcon` bars / `CalendarDays` / `Tag`) so the dropdown reads
  as a preview of what will appear in the new PropRow.
- Focus indicator: replace the default thick focus ring with
  `focus-visible:bg-accent + outline-none`, matching the hover state
  language — keyboard focus and mouse hover now look the same.
- Popover width: `w-48` → `w-44` since the labels are short and the
  visual is now denser; still leaves room for translated strings.

* fix(issue-detail): dismiss Add-property popover when an option is picked

Base UI's `Popover` doesn't auto-dismiss when a child is clicked (it's
not a Menu primitive), so picking an option left the "+ Add property"
popover sitting behind the picker that auto-opens for the newly added
row — two popovers visibly stacked.

Make the Popover controlled with a local `addPropPopoverOpen` state and
close it inside `addOptionalProp` right after enqueuing the row's
auto-open. The picker still pops on mount via `defaultOpen={autoOpenProp
=== key}`, so the user flow is unchanged from their perspective:

  Click "+ Add property" → menu opens
  Click an option         → menu closes AND target picker opens

(Was the same flow on paper before; just had the orphan popover behind
the picker.)

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 18:04:33 +08:00
Naiyuan Qing
f64d182fd1 fix(views): narrow agent/squad create dialogs from max-w-5xl to max-w-4xl (#2688)
Both create dialogs were too wide at 5xl (1024px). Align with the
codebase convention for full create dialogs (create-project,
create-issue expanded) which use max-w-4xl (896px). Keeps both
modals consistent.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 17:59:45 +08:00
Naiyuan Qing
5ad1641b72 Revert "Squad archive dialog + role editor + transactional DeleteSquad (#2680)" (#2687)
This reverts commit 2980ead4c7.
2026-05-15 17:44:59 +08:00
Naiyuan Qing
1cb926d52d feat(views): refine navigation progress bar with brand color and glow (MUL-2269) (#2681)
* feat(views): refine navigation progress bar with brand color and glow (MUL-2269)

The previous 1px bg-primary bar read as near-black on light theme and
snapped on/off in a single frame, which felt abrupt despite being a small
visual element. Switch to a 2px brand-colored sweep with right-edge glow,
slower 1.4s cubic-bezier easing, and a 200ms fade-out so completion
doesn't pop.

- Container: h-px → h-0.5 (2px); always mounted with opacity-driven fade
- Bar: bg-primary → bg-brand + two-layer box-shadow glow via color-mix
- Keyframe: 1.1s ease-in-out → 1.4s cubic-bezier(0.4, 0, 0.2, 1)

Zero new design tokens (reuses existing --brand) and zero tailwind config
changes. Desktop unaffected — same component, same prefetch=no-op path.

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

* fix(views): unmount nav progress sweep when hidden (MUL-2269)

Hiding the bar with opacity-0 left the inner element's `infinite` keyframe
animation running on every dashboard page, defeating the perceived-perf goal.
Mount the sweep only while navigating, plus the 200ms fade tail (unmount on
opacity transitionend), so nothing animates while hidden.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 17:39:28 +08:00
Naiyuan Qing
2980ead4c7 Squad archive dialog + role editor + transactional DeleteSquad (#2680)
* docs(squad): address plan-review feedback for archive + role plan

Resolve the 4 items the reviewer raised on MUL-2265:

1. TS schema: declare `active_issue_count` as optional (`number | null | undefined`)
   so list/create/update Squad responses don't lie about their shape; only
   `getSquad` parses through SquadSchema.
2. Archive semantics: restrict TransferSquadAssignees to active issues
   (status NOT IN done, cancelled) so dialog count and SQL operate on one set
   and terminal-state issues keep their historical assignee.
3. Index assumption: corrected — `idx_issue_assignee (assignee_type,
   assignee_id)` exists and is sufficient at realistic squad cardinality;
   no new index needed.
4. Fixed `*int64` test comparison and added `.loose()` to SquadSchema per
   the local schemas.ts convention.

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

* docs(squad): plan v3 — revert to count-all/transfer-all on archive

Reviewer round 2 surfaced two structural problems with plan v2's
active-only carve-out:

1. useActorName resolves squad names via ListSquads, which filters
   archived_at IS NULL. A closed issue with an archived-squad assignee
   would render as "Unknown Squad".

2. The status-only update path in UpdateIssue skips validateAssigneePair,
   so a done/cancelled issue with an archived-squad assignee could be
   reopened to in_progress, violating the "no active issue on an archived
   squad" invariant enforced elsewhere.

Both problems disappear by reverting to count-all + transfer-all: after
ArchiveSquad runs, no issue points at the archived squad, so neither
case can occur. The product trade-off is that closed historical issues
now show the leader agent instead of the archived squad in their
"Assigned to" badge — consistent with existing agent-level reassignment
behavior elsewhere in the product.

Field rename: active_issue_count -> issue_count.
TransferSquadAssignees SQL is unchanged (already transfers all).

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

* docs(squad): add Task 2b — wrap DeleteSquad transfer + archive in one tx

Reviewer round-3 flagged that the v3 invariant ("after archive no
issue points to the squad") was asserted on the happy path only.
DeleteSquad's current best-effort impl breaks it two ways:
- transfer failure → slog.Warn but archive proceeds (Unknown Squad,
  reopen-into-archived-squad bugs reappear)
- archive failure after a committed transfer → 500 with squad still
  active but emptied

Task 2b rewrites DeleteSquad to run TransferSquadAssignees +
ArchiveSquad inside one pgx tx, mirroring the project.go:266-314
pattern. Publish moves below Commit. Adds two regression tests that
lock both partial-write failure modes.

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

* feat(squad): replace native confirm() with AlertDialog and rewrite role editor as combobox

Backend:
- Add CountIssuesForSquad sqlc query (counts every issue assigned to a squad,
  no status filter — matches the existing transfer-all archive semantics).
- Extend SquadResponse with optional `issue_count` (`*int64` + omitempty,
  populated only by GetSquad to avoid an N+1 in the list endpoint).
- Wrap DeleteSquad's transfer + archive in a single pgx transaction so the
  v3 invariant ("after archive, no issue points to the squad") is durable
  rather than best-effort. Promote slog.Warn to slog.Error and check the
  parseUUIDOrBadRequest ok flag (silent zero-UUID was a #1661-class latent
  bug). Publish only after Commit so realtime never sees rolled-back state.
- Tests cover happy path (count, transfer-all including terminal statuses)
  and both rollback directions (transfer fail / archive fail) via a
  fault-injecting tx wrapper.

Frontend:
- Extend Squad TS type with `issue_count?: number | null` (optional —
  list/create/update legitimately omit it). Add SquadSchema with `.loose()`
  and wrap getSquad with parseWithFallback so older servers and count-error
  responses degrade to the dialog's "no count" copy variant.
- Replace `window.confirm()` with shadcn `ArchiveSquadConfirmDialog`
  (destructive variant, leader name + count + closed-issue caveat in the
  copy, Loader2 while pending). i18n keys added under squads.archive_dialog.
- Rewrite RoleEditor as a Popover + Command combobox: Pencil affordance is
  always visible, suggestions aggregate other members' roles, commit only
  on Enter or selecting a suggestion (blur discards), per-member savingId
  drives Loader2 so the spinner only renders on the row being saved.

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

* fix(squad): discard RoleEditor draft on close and no-op blank Enter

Two reviewer findings on e0d754bf:

1. Closing the Popover (outside click, Esc, trigger re-click) left `query`
   in state, so reopening + Enter would commit the stale draft. Clear
   `query` on every non-saving close path.
2. With an existing role, opening the editor and pressing Enter on an
   empty input committed "" — `commit` only no-op'd when trimmed matched
   value. Treat blank Enter as a no-op; clearing a role would need an
   explicit clear action that doesn't exist yet.

Add two regression tests:
- close (via outside click) → reopen surfaces a clean input; Enter does
  not commit the stale draft
- blank Enter on an existing role does not call onSave

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

* fix(squad): add explicit Clear button to RoleEditor

Role is optional, but the previous fix turned blank Enter into a no-op
without exposing any other way to clear an existing role — that broke a
valid terminal state. Keep blank Enter as no-op; add a "Clear role"
button at the bottom of the popover that only renders when value is
non-empty and routes through onSave("").

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

---------

Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 17:29:37 +08:00
Naiyuan Qing
e8d6c912c4 feat(views): prefetch + transition + skeleton for snappy web navigation (MUL-2269) (#2677)
Internal navigation on web feels laggy because clicking a sidebar link blocks
0.2–0.6s with zero visual feedback — no prefetch, no Suspense fallback in the
dashboard segment, and no React transition to mark the route commit as pending.

This change adds the three pieces App Router needs to make the click→commit
window feel instant, scoped to the (dashboard) segment so auth/landing keep
their existing chrome:

- NavigationAdapter gains an optional prefetch(path). The web adapter wires
  it to router.prefetch; desktop leaves it undefined (react-router has no
  equivalent and doesn't need one). AppLink prefetches on hover/focus and
  preserves caller-supplied onMouseEnter/onFocus/onClick.
- NavigationProvider wraps push/replace in useTransition and exposes the
  pending flag via useIsNavigating(). Every useNavigation().push caller —
  sidebar AppLink, command palette, post-create modal jumps — picks this up
  automatically.
- New apps/web/app/[workspaceSlug]/(dashboard)/loading.tsx renders a minimal
  skeleton during cold transitions inside the dashboard segment only.
- DashboardLayout renders a 1px top progress bar driven by useIsNavigating.

packages/views remains free of next/* imports; desktop is unaffected by
construction (no prefetch, transition flips quickly, no loading.tsx).

Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 17:01:42 +08:00
Bohan Jiang
461be83970 feat(views): collapse activity blocks in issue timeline (#2585)
Each consecutive run of activities renders as a single "N activities"
summary by default. Clicking expands the block in place. Comments are
unaffected; the most recent activity block stays expanded so users see
"what just happened" without a click.

Refs MUL-2188

Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: J <j@multica.ai>
2026-05-15 14:39:59 +08:00
Jiayuan Zhang
2f0e5b589e [codex] Add member and agent task views 2026-05-15 07:23:00 +02:00
Naiyuan Qing
f29bd93444 feat(squads): rework Create Squad modal (MUL-2233) (#2645)
* feat(squad): accept avatar_url on CreateSquad

Threads avatar_url through the SQL query, sqlc-generated code, and the Go
handler so the create-squad flow can persist an avatar at creation time
instead of forcing a follow-up PATCH.

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

* feat(squad): add avatar_url to CreateSquadRequest

Extends the TS contract for the new backend field so the frontend can pass
an uploaded avatar URL through api.createSquad.

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

* feat(squads): rework Create Squad modal to match CreateAgentDialog (MUL-2233)

Replaces the cramped small-dialog flow with the same large-dialog shape used
by Create Agent: identity row (AvatarPicker + name + description with char
counter), grouped Leader picker (My Agents first, then Workspace Agents),
and a new multi-select Additional Members picker covering agents and
workspace members. The members trigger collapses to "+N" once more than
three are selected; promoting an agent to leader auto-drops it from the
additional-members list.

After createSquad, additional members are attached via Promise.allSettled
so a single failure surfaces a warning toast without blocking navigation —
the squad still exists and the user can retry from the Members tab.

Adds packages/views/modals/create-squad.test.tsx covering identity binding,
leader-group ordering, leader/member conflict sanitization, the empty- and
partial-failure success paths, and the create-failure recovery path.

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

* fix(squads): valid trigger HTML + drop conflicted leader from members

Two issues from PR #2645 review:

1. AdditionalMembersPicker's PopoverTrigger was a <button> containing
   MemberChip's remove <button>, which React/HTML flags as nested
   interactive content (hydration + a11y warning). Render the trigger as
   a <div role="combobox"> via Base UI's render prop so the chip's
   remove button is valid.

2. sanitizedMembers only hid the leader from rendered/submitted output,
   so promoting an additional member to leader then switching leader
   away resurrected the hidden pick. Drop it from selectedMembers at
   the moment of promotion via handleLeaderChange; sanitizedMembers is
   no longer needed.

Adds a test that promotes → switches leader and asserts the member is
not resubmitted.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 13:11:08 +08:00
Bohan Jiang
2acc454ea5 fix(repos): accept scp shorthand in repo URL inputs (MUL-2250) (#2661)
Backend now validates http/https/ssh/git scheme plus scp-like
`git@host:owner/repo.git` shorthand, but three repo URL inputs were
still `type="url"`. The browser's native URL validation rejected scp
shorthand with "Please enter a URL" before the value could reach the
backend.

- Switch the three inputs to `type="text"` so submission isn't blocked
  client-side (project resources picker, workspace repositories tab,
  create-project repo picker).
- Extend the en/zh placeholders to show a scp shorthand example
  alongside the existing https one.
- Add a repositories-tab test that types `git@github.com:...` and
  asserts the input is text-type, passes native validity, and reaches
  the update mutation.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 13:09:06 +08:00
Bohan Jiang
968ef1ca84 test(runtimes): pin combined provider+dotted+dated Claude normalization (#2657)
Adds a regression test for `anthropic/claude-opus-4.7-20251001` that
exercises all three resolvePricing tolerances at once (provider strip,
Claude dot→dash, date trim). Each step was already covered pairwise;
this nails down their composition so a future change to candidate
ordering can't silently drop a step.

Follow-up to #2654 (MUL-2243); raised in second review.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 12:35:31 +08:00
Bohan Jiang
833032ed9c fix(runtimes): price Claude IDs reported as dotted / provider-prefixed (MUL-2243) (#2654)
Copilot's `meta.agentMeta.model` reports Claude SKUs with dots
(`claude-opus-4.7`, `claude-sonnet-4.6`, ...), and openclaw / opencode
emit the `<provider>/<model>` form (`anthropic/claude-opus-4.7`). The
maintained MODEL_PRICING table only keys on Anthropic's canonical
dashed form (`claude-opus-4-7`), so every Copilot-routed turn was
falling through to the "Custom model pricing" dialog and silently
contributing $0 to cost totals.

Teach `resolvePricing` two new tolerances, in order before date stripping:

  1. Strip a leading `<provider>/` segment — that's routing metadata,
     not part of the SKU.
  2. For `claude-*` IDs only, normalize dots to dashes. Scoped to
     Anthropic because for OpenAI the separator is semantic (`gpt-5.4`
     is a distinct SKU from a hypothetical `gpt-5-4`).

Custom pricing still wins over nothing, but the maintained catalog
still wins over a stale custom override (existing invariant preserved
by the test suite).

Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 12:29:25 +08:00
Jiayuan Zhang
e7db644563 fix(chat): make session dropdown width track its trigger (MUL-2223) (#2630)
The chat header dropdown was capped at max-w-80 while the trigger
could grow unbounded with the current chat title, so the popup
appeared narrower than the trigger and titles inside were truncated
early. Cap the trigger at max-w-96 and let the popup inherit the
trigger width via --anchor-width with the same upper bound, so the
two stay visually consistent and only truncate at extreme lengths.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 06:25:24 +02:00
Naiyuan Qing
cc3a510952 fix(issues): respect create-mode preference at generic entry points (#2640)
Sidebar "新建 issue" button, command palette "New Issue", and the `c`
shortcut all hard-coded which create modal to open, ignoring the
persisted lastMode in useCreateModeStore. Pressing `c` after switching
from agent → manual reverted to agent on the next open.

Add `openCreateIssueWithPreference(data?)` helper next to the store.
Generic entries call it; entries that pre-seed manual-only fields
(status, project_id, parent_issue_id from board / list / project /
sub-issue actions) keep opening "create-issue" directly because agent
mode does not honour those seeds.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 10:00:53 +08:00
Jiayuan Zhang
9517536d49 fix(runtimes): keep base name visible, truncate hostname first (#2629)
The RUNTIME cell rendered base name + (hostname) with both spans using
flex: 0 1 auto, so the longer hostname dominated and squashed the name
to a single letter. Give the base name shrink priority and let the
hostname own the flex slot with basis-0, so hostname truncates first
while the name stays readable.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 15:47:28 +02:00
Jiayuan Zhang
675ed02aa6 MUL-2216: persist Mine/All tab selection on Agents and Squads pages (#2624)
* MUL-2216: feat(agents,squads): persist Mine/All tab selection per workspace

Tab selection on the Agents and Squads list pages was held in
component-local state, so navigating into a detail page and back
remounted the list and reset the tab to the default "Mine". Move
`scope` into Zustand stores backed by `persist` +
`createWorkspaceAwareStorage`, matching the pattern used by the
Issues view store. Selection now survives list → detail → back
navigation and page reloads, scoped per workspace.

Only `scope` is persisted; `search`, `sort`, and other ephemeral
filters intentionally still reset on remount.

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

* fix(views): reset scope to mine when switching to a workspace with no persisted value

zustand persist.rehydrate() is a no-op when storage returns null, so
workspaces with no entry kept the previous workspace's in-memory scope
("all" leaked from one workspace into the next). Provide a custom merge
that resets to the default "mine" when no persisted state is present.

Add coverage for the missing-storage workspace-switch case for both
Agents and Squads.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 14:11:22 +02:00
Jiayuan Zhang
9da52add15 feat(settings): view/edit toggle for repositories tab (MUL-2217) (#2625)
* feat(settings): view/edit toggle for repositories tab

Saved repos render as static rows (truncated, monospace) with hover/focus-revealed
Edit + Delete affordances. Clicking Edit flips to the existing Input; on
successful Save the row returns to display mode. Save button is gated on a
dirty check (URL arrays in order) so a clean state reads as "All changes
saved". Resolves user feedback that the always-visible input made saved
state ambiguous (MUL-2217).

- Track editingIndices with a Set; new rows auto-enter edit mode; deleting
  a row remaps indices so the wrong row never opens.
- Touch devices and focus-within keep the action buttons reachable.
- New i18n keys in en + zh-Hans (saved_hint, empty, edit/delete_aria, url_empty).

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

* fix(settings): add Cancel affordance to exit clean edit mode

Clicking Edit on a clean saved row opened the row in edit mode with
no way back to display mode unless the user changed the URL and saved,
re-introducing the original saved-state ambiguity after an accidental
click. Add a per-row Cancel (X) button visible only in edit mode that:

- reverts the URL to the saved value for existing rows
- removes the row entirely for never-saved (newly added) rows
- exits edit mode without dirtying Save

Action group is always visible (no hover gate) while editing so the
exit is discoverable. Adds en/zh-Hans cancel_aria string and three
regression tests covering clean-cancel, dirty-cancel, and new-row-cancel.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 13:59:26 +02:00
Prateek Bhatnagar
681d720671 fix(issues): file-card render for self-host with local storage (#2349)
* fix(issues): file-card render for self-host with local storage

Fixes #1520. When self-hosting without S3, the upload handler returns
site-relative URLs like /uploads/workspaces/<wsId>/<file>. Four
frontend regexes only matched https?://, so persisted
!file[name](/uploads/...) markdown failed to parse and leaked through
as raw text in the issue view, chat, skill file viewer, and board
card preview.

Narrow allow-list: the relative branch only accepts /uploads/ — not
any /-prefixed href — so protocol-relative //evil.com/x, path-traversal
/../api/x, and other internal /api/... paths are rejected. Without
this, a stored file-card with an attacker-chosen filename and a
//host/x href would turn into a one-click external-site jump via
window.open from inside an issue (per review feedback on #2349).

Single source of truth: packages/ui/markdown/file-cards.ts now exports
isAllowedFileCardHref + FILE_CARD_URL_PATTERN. The four sites use one
of them, so the next regression is cheaper than restoring four parallel
regexes.

- packages/ui/markdown/file-cards.ts: helper + URL pattern.
- packages/views/editor/extensions/file-card.tsx: Tiptap tokenizer
  composes from FILE_CARD_URL_PATTERN.
- packages/views/editor/readonly-content.tsx: sanitiser uses helper.
- packages/ui/markdown/Markdown.tsx: sanitiser uses helper.
- packages/views/issues/components/board-card.tsx: strip markdown
  tokens from the line-clamped board preview so raw !file[...] no
  longer leaks there either.
- packages/ui/markdown/file-cards.test.ts: covers accept (/uploads/ok,
  https://cdn/x) and reject (javascript:, data:, //evil.com/x,
  /../api/x, /api/x, empty, ftp:, bare 'uploads/x') for both the
  helper and the parser composed from the pattern.

javascript:, data:, and other dangerous schemes remain rejected.

* test(markdown): move file-card href allow-list test into @multica/views

Per review feedback on #2349: keep the test where vitest is already
running instead of bootstrapping a new test runner inside @multica/ui.
The test now lives at packages/views/editor/file-card-href.test.ts and
imports isAllowedFileCardHref / FILE_CARD_URL_PATTERN /
preprocessFileCards from the @multica/ui/markdown public surface,
exercising the same 30 cases.

Reverts the @multica/ui package.json test script + vitest devDep + the
local vitest.config.ts that the previous commit added; the package
goes back to typecheck + lint only, matching every other ui-only
package in the monorepo.

---------

Co-authored-by: Lalbadshah <11599756+Lalbadshah@users.noreply.github.com>
2026-05-14 18:32:40 +08:00
Naiyuan Qing
43b9a1173c refactor(agents): drop template chooser from create-agent dialog (#2615)
* refactor(agents): drop template chooser from create-agent dialog

Removes the blank-vs-template chooser, the template picker, and the
template detail step. The "Create agent" entry point now opens directly
on the form. The createAgentFromTemplate API and types remain
untouched — this only removes the UI entry.

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

* docs(squads): fix stale comment about createAgentFromTemplate

Squad-scoped create flow no longer goes through the template path;
the dialog now only calls api.createAgent then api.addSquadMember.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 18:05:37 +08:00
Naiyuan Qing
77b929fd3e feat(squads): add agent live peek hover card on member avatars (#2608)
* feat(squads): add agent live peek hover card on member avatars

Squad members tab now opens a live-state peek card on agent avatar
hover/focus — workload, current issue (clickable), and last activity.
Identity (description / runtime / skills / owner) stays on the existing
AgentProfileCard; new AgentLivePeekCard is the second `hoverCardVariant`
on ActorAvatar so the 23+ existing profile-card call sites keep their
behaviour. Reuses the workspace agent-task snapshot already fetched by
the presence dot, so this adds zero new requests per row. Failed
terminal tasks surface as a small ⚠ on the last-activity line without
polluting workload (workload stays current-state only, matching the
deliberate split documented in core/agents/types.ts).

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

* fix(squads): only enable hover card for agent avatars

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 17:30:08 +08:00
Naiyuan Qing
5eb04f73e3 feat(squads): add tooltips and agent detail link to squad member row (#2603)
* feat(squads): add tooltips and agent detail link to squad member row

Replace native title attributes on the make-leader and remove buttons
with proper Tooltip components, and add a new icon button on agent
rows that navigates to the agent detail page. All three tooltips are
localised.

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

* fix(squads): keyboard focus visibility + AppLink for agent detail

- Add group-focus-within:opacity-100 so Tab to the row's hover-only
  action buttons makes the container visible (previously opacity-0
  kept buttons focusable but invisible).
- Replace the agent-detail jump button's onClick+push() with AppLink
  href, restoring middle/Cmd+Click new-tab behavior. Removes the
  now-unused onViewAgent callback chain.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 16:35:15 +08:00
Naiyuan Qing
bc613c08b3 fix(squad): align squad detail tab width with agent detail (#2600)
Drop mx-auto + max-w-2xl wrappers around the Members and Instructions
tab content so the right pane fills the available width like the agent
detail page (TabContent uses flex h-full flex-col p-4 md:p-6).

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 16:09:45 +08:00
Naiyuan Qing
2c7738b03a feat(issues): close composer attachment preview loop end-to-end (#2594)
Text/code attachments (markdown, JSON, .ts, .log, …) need an attachment id
to render through `/api/attachments/{id}/content`. The composer pipeline
was dropping that id at the upload-hook boundary, so the Eye preview gate
only fired for media (PDF / video / audio via filename fallback).

- `useFileUpload` now returns the full `Attachment` (with `link` kept as a
  `url` alias) so editor providers can resolve content-type and id.
- New-comment and reply composers hold a `pendingAttachments` state and
  feed it to `ContentEditor`; the active subset (those still referenced in
  the markdown) is sent on submit as before.
- Comment edit modes (CommentRow + CommentCardImpl) merge pending uploads
  with `entry.attachments` for the editor and pipe `attachment_ids` into
  `onEdit` so newly uploaded files actually bind to the comment.
- Issue description editor pushes pending `attachment_ids` on every
  debounced save and invalidates `issueKeys.attachments` so the preview
  Eye survives a refresh.
- `UpdateComment` and `UpdateIssue` handlers accept `attachment_ids` and
  call the existing `linkAttachmentsByIDs` / `linkAttachmentsByIssueIDs`
  helpers; the bind is idempotent so re-sending an existing id is safe.

Closes MUL-2153.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 15:06:21 +08:00
Naiyuan Qing
0c4133ef5b feat(agents): rewrite template catalog as 25 lightweight starters (#2587)
* feat(agents): rewrite template catalog as 25 lightweight starters

Replaces every Phase-1 template with a curated set built around the
"persona + intake + scaffold + hard negatives" instruction shape. Cross-
platform survey (Cursor / Cline / Roo / Continue / Custom GPTs) showed
the industry baseline for starter agents is "few but sharp" — single
intent, no methodology buy-in, mostly prompt-only. The original catalog
went the opposite direction (avg 2.5 skills, six-skill Full-stack
methodology stack) and felt heavy for first-time use.

Catalog shape:

- 25 templates across 7 categories: Engineering (8), Product (4),
  Writing (5), Design (3), Communication (2), Team (1), Productivity (2).
  New Product / Design / Communication / Team domains fill gaps the old
  Eng-heavy catalog ignored.
- 16 / 25 are prompt-only (no skill fan-out). Avg 0.56 skill per template
  vs. 2.5 prior. Heaviest is 2 skills, only for templates whose intent
  cannot be expressed in instructions alone (Playwright runner, single-
  file HTML bundlers, design + UX-guidelines pair).
- Universal top-frequency intents that the old catalog missed are now
  covered: Code Explainer (intent #1 across every platform surveyed),
  Translator (中英), Summarizer, Writing Critic, PRD Drafter/Critic,
  RCA Writer, ADR Writer, PR Description Writer, Commit Message Writer.

Loader allows 0-skill templates:

- server/internal/agenttmpl/loader.go drops the "must declare at least
  one skill" validation; comment explains the picker's "Prompt only"
  rendering path.
- loader_test.go: removed the corresponding negative case, added
  TestLoadFromFS_PromptOnlyTemplate as a regression guard.
- agent_template.go handler is unchanged — every len(tmpl.Skills) call
  site was already 0-safe (empty fan-out short-circuits the fetch phase
  and the in-tx loop both skip cleanly).

Frontend:

- template-picker.tsx: 18 new lucide icons (BookOpen, Bug, GitPullRequest,
  GitCommit, AlertTriangle, Scale, ClipboardList, Microscope, UserRound,
  Target, Highlighter, Languages, AlignLeft, GraduationCap, Lightbulb,
  Type, MessageSquare, Briefcase). Card renders a "Prompt only" badge
  when skills.length === 0 instead of "0 skills".
- template-detail.tsx: skill list section is hidden entirely for prompt-
  only templates — a header reading "Includes 0 skills" above an empty
  list was just visual noise. Instructions section below carries the
  agent's identity for these.
- locales/en + zh-Hans agents.json: new create_dialog.template_card.
  prompt_only key ("Prompt only" / "纯指令").

Verification:

- go test ./internal/agenttmpl/ — 9/9 pass, including
  TestLoad_RealTemplates which fails closed if any new JSON is malformed.
- pnpm typecheck — all 6 packages clean.
- pnpm --filter @multica/views test — 482/482 pass.
- pnpm lint — 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(agents): add category filter pills to template picker

25 templates across 7 categories made the picker scroll-heavy on first
open. Add a single-select category filter row above the grid so a PM
can isolate Product templates in one click, an engineer can jump
straight to Engineering, etc.

Visual reuses the IssuesHeader scope-toggle pattern verbatim — Button
variant="outline" + active class swap (bg-accent / text-muted-foreground)
— so the affordance reads the same as the existing filter pills in
issues / squads / runtimes / my-issues. flex-wrap keeps the 8 pills
(All + 7 categories) honest on narrow widths.

Counts are inlined into the label ("Engineering (8)") rather than
shown as a separate badge — single-line-tall pills look right next to
the picker grid, and surfacing the per-category density up front
doubles as a hint at the catalog's "less but sharper" intent.

When a specific category is active, the grid renders flat (no
section headers) — the active pill already names what's on screen,
and a header reading "Engineering" above an only-Engineering grid is
visual duplication. "All" falls back to the prior grouped layout.

State is component-local (no URL sync, no persistence) since the
picker is dialog-internal transient state — closing the dialog
naturally resets the filter, which is the expected behaviour for a
"choose from a catalog" surface.

i18n: new `create_dialog.template_picker.filter_all` key in en + zh.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 14:12:18 +08:00
LinYushen
0cb759b446 fix(squad): suppress no-action leader comments (#2583) 2026-05-14 14:07:26 +08:00
LinYushen
053a37d19c feat: add pinyin search to subscriber popover in issue-detail (#2584)
Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 13:57:46 +08:00
LinYushen
d1c8c213e4 feat: extend pinyin search to all Agent/Member/Squad selectors (#2582)
Integrate matchesPinyin into:
- AssigneePicker (issue assignee selector)
- IssuesHeader (assignee filter bar)
- AgentPicker (autopilot agent selector)
- SquadDetailPage (add member/agent picker)
- QuickCreateIssue (agent/squad picker)
- CreateProject (lead picker)
- ProjectDetail (lead picker)
- ProjectsPage (lead filter)
- AgentsPage (agent search)
- SquadsPage (squad search)

Closes MUL-2179 extended scope.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 13:57:38 +08:00
Bohan Jiang
f15a745182 feat(squads): add Create Agent entry on Squad detail (MUL-2178) (#2579)
Adds a Create Agent button on the Squad detail Members tab, visible
only to workspace owner/admin (matching the AddSquadMember backend
gate). The dialog reuses the existing CreateAgentDialog — both the
manual and template paths now accept an optional squadId; when set,
the dialog runs addSquadMember after createAgent / createAgentFromTemplate
and skips the navigation to the agent detail page so the user lands
back on the Members tab.

Atomicity is best-effort frontend-serial (no new backend transaction):
on partial failure the dialog surfaces a warning toast and the agent
remains addable from the existing Add Member flow.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 13:32:28 +08:00
LinYushen
ca10535bb6 fix: execution log name rendering and squad assignee support (#2575)
* fix: execution log name rendering and squad assignee support

- Strip mention markdown in trigger_summary ([@Name](mention://...) → @Name)
  so execution log rows show clean text instead of raw markdown
- Add squad to ActorFilterValue type so squad assignees are filterable
- Add squad section to assignee filter dropdown in issues-header
- Add i18n keys for squads_group (en/zh-Hans)

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

* fix: address PR #2575 review feedback

1. Extract stripMentionMarkdown as reusable helper with proper regex
   - Handles escaped brackets in names (e.g. David\[TF\])
   - Skips backslash-escaped mentions (\[@...])
   - Handles issue mentions (no @ prefix)
   - Does not touch regular markdown links
   - 10 unit tests added

2. Squad only appears in Assignee filter, not Creator
   - Added showSquads prop to ActorSubContent (default true)
   - Creator filter passes showSquads={false}

3. Squad included in Agents scope
   - issues-page scope filter now includes squad in agents scope
   - 2 regression tests added for scope coverage

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 13:08:05 +08:00
LinYushen
c628958fdd feat: support pinyin search in @mention suggestions (#2572)
* feat: support pinyin search in @mention suggestions

Add pinyin matching for Chinese names in the mention suggestion popup.
Users can now search by:
- Full pinyin: 'liyunlong' matches '李云龙'
- Initial letters: 'lyl' matches '李云龙'
- Partial/hybrid: 'liyu' or 'liyunl' matches '李云龙'

Implementation:
- New pinyin-match.ts utility using pinyin-pro library
- Integrated into member, agent, and squad filters in mention-suggestion.tsx
- 21 tests passing (9 unit + 12 integration)

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

* fix: normalize ü→v in pinyin matching for names like 吕布

Enable pinyin-pro's v:true option so 吕→lv instead of lü.
Add test case for 吕布/lvbu matching.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-14 12:44:43 +08:00
LinYushen
9256743549 fix(mention): prefetch squads so @mention list shows all squads
Closes MUL-2176
2026-05-14 11:52:13 +08:00
Naiyuan Qing
c49c778613 fix(editor): align Preview gate with Download — survive URL-only sources (#2566)
The Eye button required a fully resolved Attachment record (URL-lookup
via `resolveAttachment(href)`) before showing. Download only required
the URL, falling back to `openExternal(href)` when the lookup missed.
Result: any case where the URL in markdown couldn't be reverse-matched
to the entity's `attachments` prop (cross-comment copy-paste, stale
caches) silently hid the Preview button while Download kept working —
edit and readonly surfaces diverged for the same content.

Widen the Preview gate to mirror Download: show the Eye whenever the
filename indicates a previewable type. Introduce a `PreviewSource`
tagged union — `{ kind: "full", attachment }` for the existing path,
`{ kind: "url", url, filename }` for the fallback. Media kinds
(pdf/video/audio) render directly from the URL; text kinds still
require an attachment id because the /content proxy is ID-keyed, so
`tryOpen` rejects URL+text combinations and PreviewContent has a
defensive fallback for direct mounts.

Side effects:
- `getPreviewKind` gains filename-extension fallbacks for video/audio
  (was PDF-only); without these the URL-only path can't infer kind
  when content_type is empty.
- AttachmentList in comment-card.tsx unchanged behaviorally — only the
  tryOpen call site is updated to the new signature.

Pre-existing architectural issues (AttachmentList readonly-only,
URL-based attachment lookup, per-entity ownership) are intentionally
out of scope.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 11:33:48 +08:00
Naiyuan Qing
52d032335a feat(agents): expose runtime + model on create-from-template (#2565)
Template create used to silently default the runtime to "first usable"
and never collected a model — users had no idea where the new agent
would run or which model it would use until they opened the detail
page. Add a Runtime + Model picker pair above the skill list on the
template-detail step so the choice is visible (and overridable) before
the one-click Use action.

- Extract RuntimePicker out of create-agent-dialog so the form and the
  template-detail step share one popover; selection seeding moves into
  the picker too, since it's the only place that knows the active
  filter (mine/all). Parent keeps just the duplicate-mode pre-fill.
- Mirror RuntimePicker's label-row + trigger DOM in ModelDropdown so
  the two pickers render at identical heights when sat side-by-side
  (fixes a 6-8px misalignment caused by inconsistent label-row sizing).
- Send model in createAgentFromTemplate; server side already accepts
  the field (CreateAgentFromTemplateRequest.Model, omitempty), empty
  string still falls through to the runtime's default model.
- Drop the runtime_register_first fallback hint that made the Runtime
  trigger two-line in the empty state, breaking alignment with Model's
  one-line trigger.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 11:33:39 +08:00
Bohan Jiang
0345285b86 feat(quick-create): searchable actor picker + squad support (#2552)
* feat(quick-create): searchable actor picker + squad support (MUL-2163)

- Replaces the flat agent dropdown in the "Create with agent" modal with a
  searchable PropertyPicker that lists Agents and Squads in separate
  sections, so users can filter by name and pick a squad as the creator.
- Persists the selection as (lastActorType, lastActorId), removing the
  agent-only lastAgentId field on the quick-create store.
- Adds squad_id to the quick-create API request and stamps it onto the
  task's QuickCreateContext. The handler resolves the squad to its leader
  agent (re-using validateAssigneePair) and the daemon claim path injects
  the squad-leader briefing when the task carries a squad hint, matching
  the behavior of issue-bound squad tasks.

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

* fix(create-issue): forward squad picks across manual→agent switch

Manual mode → agent mode previously only carried `agent_id`, so picking
a squad and then flipping to agent silently fell back to the persisted
actor / first visible agent and lost the user's choice. Carry `squad_id`
on the same branch so the agent panel honors the squad pick.

Adds a sibling test alongside the existing project-carry case.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-13 22:31:17 +08:00
DimaS
efddb2284b fix(issues): clean caches after issue delete (#2487)
* fix(issues): clean caches after issue delete

* fix(issues): restore partial batch delete snapshots
2026-05-13 22:30:16 +08:00
Jiayuan Zhang
7e20ca27bb fix(issues): unify assignee menu with shared AssigneePicker (MUL-2157) (#2543)
* fix(issues): unify assignee menu with shared AssigneePicker (MUL-2157)

The Assignee submenu inside IssueActionsMenuItems was a parallel
implementation: no search, no squads, no agent permission check, no
archive filter, no frequency sort. The divergence was most visible from
the Inbox (where the issue detail's sidebar starts collapsed, so users
reach for the 3-dot menu).

Replace the submenu with a single menu item that closes the
surrounding dropdown / context menu and hands off to the shared
AssigneePicker popover — same component already used in the issue
detail sidebar, board cards, batch toolbar, and create-issue modal.

The picker is conditionally mounted to avoid every row in list / board
views subscribing to the members / agents / squads / frequency queries
on mount.

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

* test(issues): mock squadListOptions + add Assignee picker handoff test

`AssigneePicker` reads `squadListOptions` and `assigneeFrequencyOptions`
from `@multica/core/workspace/queries`. Tests that render IssueDetail
or IssueActionsDropdown without those mocks throw at the picker's
useQuery call and cascade into unrelated assertion failures — this is
what was leaving the `@multica/views` test job red on the MUL-2157 PR.

Add the missing mocks. Add a regression test that clicks the Assignee
menu item and asserts the shared picker (search input + Members group)
takes over, so a future regression to the parallel-implementation bug
this PR fixes fails loudly instead of silently.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-13 22:11:18 +08:00
Bohan Jiang
4c1bef2e1f feat(usage): mirror Tokens metric toggle onto Usage page Daily chart (MUL-2148) (#2540)
* feat(usage): mirror Tokens metric toggle onto Usage page Daily chart (MUL-2148)

#2537 added the Cost/Tokens metric toggle to the Daily chart inside the
runtime-detail Usage section (packages/views/runtimes/components/
usage-section.tsx). The workspace-level Usage page at /{slug}/usage
imports the same DailyCostChart primitive but renders it from
dashboard-page.tsx without any toggle wrapper, so #2537 only landed on
half of the surface that says "Daily cost".

This PR mirrors the same pattern to dashboard-page.tsx so users see
the toggle wherever a "Daily" chart appears.

Changes
- `packages/views/dashboard/utils.ts`: new `aggregateDailyTokens` helper
  that folds DashboardUsageDaily[] into the same DailyTokenData[] shape
  the DailyTokensChart consumes (mirrors aggregateByDate's dailyTokens
  branch from the runtimes side, adapted to DashboardUsageDaily field
  names).
- `packages/views/dashboard/components/dashboard-page.tsx`: rename
  `DailyCostBlock` → `DailyTrendBlock`, add a Cost/Tokens Segmented
  next to the section title, switch chart and title based on the
  active metric, per-metric empty-state (so a workspace with unmapped
  pricing but recorded tokens still gets a real Tokens chart while
  the Cost view falls through to the empty-state — same convention as
  DailyTab in usage-section.tsx).
- usage.json (en + zh-Hans): split `daily.title` into `title_cost` +
  `title_tokens`, add `metric_cost` + `metric_tokens` toggle labels.

* feat(usage): default Daily chart to Tokens metric

Most users land on /{slug}/usage to gauge "how much agent work
happened" rather than "how much was spent." Tokens is the more
universally meaningful axis on first read (Cost depends on having
pricing mapped for every model and on whether the workspace has
unmaintained models). Cost stays one click away via the same toggle.

Also reorder the Segmented so Tokens sits first, matching the new
default.
2026-05-13 22:07:47 +08:00
Jiayuan Zhang
291c2c7898 feat(usage): reuse runtime timezone picker on the usage page (#2533) (#2546)
* feat(usage): add timezone picker to usage page (#2533)

Extracts the runtime detail page's timezone dropdown into a shared
TimezoneSelect at packages/views/common/timezone-select.tsx and reuses
it in the usage page header, immediately to the right of the 7d / 30d
/ 90d segmented control. Defaults to the browser-resolved zone with
the same "(browser)" suffix rendering as the runtime page.

The runtime-detail TimezoneEditor still owns the PATCH mutation; only
the dropdown UI moved. UI-only — no API client / handler changes.

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

* fix(usage): make header wrap so timezone picker fits on narrow widths

The h-12 PageHeader is a single non-wrapping flex row. Adding the
timezone picker with a 180px min-width pushed the title + project
filter + range switch + tz select past the viewport on narrow and
medium widths. Drop the picker's hard min-width, let the header grow
vertically (h-auto + min-h-12) and let the right toolbar wrap. Wide
viewports still render the original single row.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-13 15:58:53 +02:00
Jiayuan Zhang
9ad5eb5ffe fix(tests): add squad mocks to unblock views test suite (MUL-2158) (#2544) 2026-05-13 13:51:24 +02:00
Bohan Jiang
87464f6c03 fix(squads): i18n the Squad pages to unblock views#lint (CI red on main) (#2542)
#2505 (Squad MVP) merged with 29 hardcoded English strings in JSX text
nodes — packages/views/squads/components/squads-page.tsx (4) and
squad-detail-page.tsx (25). The package's eslint config enforces
`i18next/no-literal-string` as ERROR for every .tsx file, so
@multica/views#lint has been red on main, which Turbo cascades to
@multica/web#build, @multica/desktop#build, and @multica/views#typecheck
— effectively blocking every open PR's frontend CI (#2538, #2540, etc.).

Rather than disabling the rule for the Squad files (which would just
hide debt in a high-visibility surface), wire up a proper i18n
namespace and replace every flagged literal.

Namespace plumbing
- New `packages/views/locales/en/squads.json` and
  `packages/views/locales/zh-Hans/squads.json` covering all 29 flagged
  strings, grouped by surface (page / inspector / name_editor /
  add_member_dialog / description_dialog / discard_changes_dialog /
  members_tab / instructions_tab).
- Registered in `packages/views/locales/index.ts` and
  `packages/views/i18n/resources-types.ts` so `t($ => $.squads.*)` is
  type-safe.

Component replacements
- `squads-page.tsx`: add `useT("squads")`, replace 4 literals.
- `squad-detail-page.tsx`: add `useT("squads")` to seven inner
  components that hold flagged text (`SquadDetailPage` / `InlineEdit
  Popover` / `AddMemberDialog` / `RoleEditor` / `SquadDescriptionEditor`
  / `SquadDescriptionEditorBody` / `SquadOverviewPane` / `SquadMembers
  Tab` / `SquadInstructionsTab` / `SquadDetailInspector`), replace all
  flagged literals.
- Plural members count uses i18next's standard `_one` / `_other`
  suffixes via `t(..., { count })` — matches the convention already
  used in `runtimes/usage` and `agents`.

Notes
- A few unflagged user-facing strings remain (tab labels in
  squadDetailTabs array, ternary alternatives like `"Save"` inside
  `{x ? <Loader/> : "Save"}`, the inline `confirm()` archive prompt,
  the `toast.success("Leader updated")` message). The eslint rule
  uses `mode: "jsx-text-only"` so it only flags string children of
  JSX nodes; attribute strings, object-literal values, and ternary
  alternatives slip past. Those are real i18n gaps too but expanding
  scope here would gold-plate the CI-unblock fix.

Verification
- `pnpm --filter @multica/views lint`: 0 errors (was 29). Remaining 13
  warnings are pre-existing in unrelated files and don't fail CI.
- `pnpm typecheck`: 6/6 packages pass — namespace types resolve, all
  selector calls infer correctly.
2026-05-13 19:35:31 +08:00
Naiyuan Qing
cde3867d3b feat(sidebar): top/bottom scroll fade mask (MUL-2150) (#2536)
* feat(sidebar): top/bottom scroll fade mask (MUL-2150)

Apply useScrollFade to SidebarContent so the menu list softly fades
into the header / footer when overflowing, matching the existing
pattern used in chat list and onboarding steps.

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

* fix(ui): useScrollFade re-evaluates on content mutations

ResizeObserver only fires on the observed element's own box. When a
flex / auto-height container's children grow asynchronously (sidebar
pinned items loading from TanStack Query, collapsibles expanding),
scrollHeight changes but clientHeight does not — mask stayed 'none'
until the user scrolled. Add a MutationObserver on childList to
recompute fade when content is inserted or removed.

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

* test(paths): include squads in workspace route consistency check

main added the squads parameterless route to paths.workspace() in #2505
but the C4 consistency assertion wasn't updated, turning frontend CI
red on every PR. Add 'squads' to both the parameterless-method set and
the segment-mapping table.

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

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-13 19:02:08 +08:00
Bohan Jiang
c6ccc49650 feat(runtimes): add Tokens metric toggle to Usage Daily chart (MUL-2148) (#2537)
The runtime Usage page's Daily timeline only showed daily $ cost, which
hides the underlying usage shape: cost varies wildly by model price, so
a quiet day on Opus can outspend a busy day on Haiku. Add a Cost/Tokens
toggle next to the Daily/Hourly/Heatmap tabs that swaps the chart over
to a four-segment stack of raw token counts (input / output / cache
read / cache write).

No backend changes needed — the existing /api/runtimes/{id}/usage
response already carries the per-day per-model token breakdown; this
just wires up DailyTokensChart on top of the dailyTokens aggregate that
aggregateByDate was already producing.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-13 18:51:26 +08:00
LinYushen
29082f7cfe feat: implement Squad feature MVP (#2505)
* feat: implement Squad feature MVP

- Add migration 084_squad: squad, squad_member, squad_activity_log tables
- Extend issue.assignee_type to support 'squad'
- Add sqlc queries for squad CRUD, member management, activity logs
- Add Go handler with full Squad API (CRUD, members, activity log)
- Register routes: /api/squads/*, /api/issues/{id}/squad-activity, /api/squad-activity
- Add Squad trigger logic:
  - Assign Squad immediately triggers leader
  - Every external comment on squad-assigned issue triggers leader
  - Anti-loop: squad members' comments don't trigger leader
  - Dedup: skip if leader already has pending task
- Add squad activity log API (方案 B) for leader no-op recording
- Add frontend TypeScript types (Squad, SquadMember, SquadActivityLog)
- Add protocol events: squad:created, squad:updated, squad:deleted

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

* fix: address PR review blocking issues

1. validateAssigneePair now accepts 'squad' assignee_type
2. All squad endpoints validate workspace ownership via GetSquadInWorkspace
3. CreateSquadActivityLog restricted to squad leader agent only
4. AddSquadMember validates member exists in workspace
5. UpdateSquad auto-adds new leader to squad members
6. DeleteSquad transfers assigned issues to leader before deletion
7. IssueAssigneeType includes 'squad' in frontend types

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

* feat: soft-delete squads via archive instead of hard delete

- Add migration 085: archived_at + archived_by columns on squad table
- ListSquads now excludes archived squads (ListAllSquads for admin)
- DeleteSquad → ArchiveSquad (sets archived_at, preserves all records)
- Transfer squad-assigned issues to leader before archiving
- SquadResponse includes archived_at/archived_by fields
- Frontend Squad type updated with nullable archived fields

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

* feat: re-add Squads frontend entry (sidebar nav + pages)

Re-applies the frontend squad entry that was lost during a merge:
- Sidebar nav: Squads item with Users icon
- Paths: squads() and squadDetail() in workspace paths
- Routes: /squads and /squads/[id] pages
- Views: SquadsPage (list) and SquadDetailPage
- i18n: en 'Squads' / zh '小队'
- Reserved slug: 'squads'

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

* fix: fix SquadsPage rendering - use PageHeader children pattern

PageHeader takes children, not title/actions props. The incorrect
usage caused a React rendering error. Now matches the pattern used
by autopilots and agents pages.

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

* fix(squads): add API client methods and package export for squads pages

* feat: complete Squad frontend - create dialog, member management, API methods

- Add CreateSquadModal with name/description/leader selection
- Register 'create-squad' in modal registry
- Wire 'New Squad' button to open the modal
- Add full API client methods: createSquad, updateSquad, deleteSquad,
  addSquadMember, removeSquadMember
- Rewrite SquadDetailPage with:
  - Member list showing resolved names
  - Add/remove member UI
  - Archive squad button
  - Back navigation to squads list

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

* feat: improve Squad UI - match create agent dialog style

- CreateSquadModal: proper Dialog with Header/Description/Footer,
  agent picker with avatars, textarea for description
- SquadDetailPage: centered max-w-2xl layout, ActorAvatar for members,
  Crown badge for leader, textarea for member description,
  improved spacing and visual hierarchy
- Renamed 'role' field label to 'Description' in add member form
  (describes the member's responsibilities in the squad)

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

* feat(squad): add avatar, instructions; drop unique-name constraint

- 086: add squad.avatar_url
- 087: drop unique constraint on squad.name (squads with the same
  name are legitimate across teams; uniqueness was an accidental
  product constraint)
- 088: add squad.instructions (text, default '')
- UpdateSquad now COALESCEs avatar_url + instructions
- handler exposes Instructions in SquadResponse and accepts it in
  UpdateSquad

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(squad): assignable + mention target; trigger leader on assign

- assignee picker and @mention suggestion list squads alongside
  agents and members; renders squad avatar/icon
- creating or updating an issue with assignee_type=squad enqueues
  a task for the squad's current leader (mirrors agent-assignee
  parking-lot rule: skip backlog only)
- workspace queries/hooks expose squads where needed for the
  pickers
- locales updated for new picker copy

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(squad): agent-style detail page with members + instructions tabs

- restructure squad detail page to mirror the agent detail page:
  320px inspector (creator, leader, created/updated) + tabbed
  pane (Members | Instructions) with dirty-guard AlertDialog
- inline name + avatar editing on the inspector
- inline description editor (modal textarea)
- members tab: leader + member picker with role descriptions,
  swap leader, edit member roles, remove
- instructions tab: ContentEditor + Save (mirrors agent pattern)
- squads list shows the squad avatar/icon
- core types + api.updateSquad accept avatar_url + instructions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(squad): inject leader briefing on claim (protocol + roster + instructions)

When a squad's leader agent claims a task on a squad-assigned issue,
append a system-level briefing to the agent's Instructions composed of:

1. Squad Operating Protocol — hard-coded rules: leader is a
   coordinator, dispatch via @mention, stop after dispatching,
   resume on re-trigger, do not work outside the roster.
2. Squad Roster — leader self-row plus one row per non-archived
   member with a literal mention markdown string ([@Name](mention://
   agent|member/<UUID>)) the leader can paste verbatim. Round-trips
   through util.ParseMentions, enforced by a contract test.
3. Squad Instructions — the user-defined squad.instructions block,
   omitted entirely when empty so we do not leave a dangling heading.

Non-leader members claiming the same issue receive no briefing.

Tests cover: full squad with mixed agent/human members, lone leader,
archived agents skipped, empty user instructions, mention round-trip,
and the leader/non-leader claim-handler gate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(squad): tell leader not to restate issue context in dispatch comment

After observing leaders padding their delegation comments with full
re-summaries of the issue body and prior discussion, make the
Operating Protocol explicit:

- assignees on Multica already have the full issue (title,
  description, all comments, attachments) and workspace context;
- delegation comments should add only what cannot be inferred
  (who is picked, why, extra constraints), aim for two or three
  sentences;
- restating context is now an explicit hard rule violation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(squad): unify leader evaluation into activity_log, add CLI command

- Squad member comments now trigger leader (only leader self-excluded)
- Replace squad_activity_log with activity_log (action: squad_leader_evaluated)
- Add CLI: multica squad activity <issue-id> <outcome> --reason
- Add API: POST /api/issues/{id}/squad-evaluated
- Update squad operating protocol to require evaluation recording
- Remove squad_activity_log table from schema and generated code

* feat(cli): add squad list, get, member list commands

* fix(squad): address review findings (P1+P2)

P1 fixes:
- Add 'squads' to reserved_slugs.json (source of truth)
- Add 'create-squad' to ModalType union
- Remove unused leaderOpen/selectedLeader in create-squad modal
- Replace literal JSX strings with i18n selectors (en + zh-Hans)

P2 fixes:
- Add 'squad' to mention regex (MentionRe)
- Fix human member lookup in squad briefing (use GetUser directly)
- Add squads routes to desktop app
- Add squad:created/updated/deleted to WSEventType + invalidation
- Reject archived squads as issue assignees

* fix(squad): restore zh-Hans key, publish activity event, invalidate issues on archive

- Restore create_project.title in zh-Hans modals.json (dropped by prior edit)
- Publish activity:created WS event after squad leader evaluation
- Invalidate issue queries on squad:deleted (archive transfers assignees)
- Add creator info to squad list cards

* fix(squad): realtime sync, rerun support, leader validation

- Use workspaceKeys.squads prefix for detail/member queries (realtime invalidation)
- Publish squad:updated after add/remove/role-change member mutations
- Support rerun for squad-assigned issues (targets leader agent)
- Reject assignment to squads whose leader is archived

---------

Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-13 18:46:20 +08:00