mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-05 09:30:05 +02:00
fix/codex-patch-apply-payload
151 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c2a7a5a9e9 | Merge remote-tracking branch 'origin/main' into fix/codex-patch-apply-payload | ||
|
|
a4f79b3f3a |
fix(transcript): correct the Chinese multi-file patch count semantics
Addresses review on #6158. The summary is handed the number of files *beyond* the named one, but the Chinese phrasing stated a total: "a.go 等 2 个文件" reads as two files including a.go, so a three-file patch under-reported by one. English hides the distinction ("+2 more"), which is why it survived the first pass. Rewords zh-Hans to "另有 N 个文件". Japanese (他) and Korean (외) already read as "besides", so their wording is unchanged. Also renames the interpolation variable from `count` to `extra`, for two reasons. i18next treats `count` as the plural selector — this very namespace relies on that for events_one/events_other — so a plain number had no business borrowing it. And the name is what a translator reads: `extra` cannot be mistaken for a total the way `count` was. Guards the whole bug class rather than just this string: a locale test asserts every locale interpolates {{path}} and {{extra}} and never the reserved {{count}}, and a presenter test pins that the injected number is the count of additional files, not the total. Verified: both new locale assertions fail against the reverted string and pass now; rendering the real locale strings for a three-file patch yields "+2 more", "另有 2 个文件", "他 2 件", "외 2개". 53 target tests pass, repo typecheck clean, views lint back to its pre-existing 16 warnings and 0 errors. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
7803a5b9ea |
feat(ui): establish a role-named type scale and migrate ad-hoc font sizes (MUL-5451) (#6136)
tokens.css defined colours, radii and font families but not a single --text-* step, so font sizes had no baseline to align to and grew wherever they were needed: 51 distinct sizes across web + desktop, 370 written as arbitrary values, six at half a pixel (10.5 / 11.5 / 12.5 / 13.5 / 14.5 / 15.5px). text-xs and text-sm carried nearly all UI text while the range between them — 11, 13, 15px — could only be reached with arbitrary values. Hierarchy does not come from having more sizes; past a handful, each extra size makes the hierarchy blurrier. Add ten role-named steps, each with its own line-height so leading cannot fragment the way size did, and move every product-UI call site onto them. Steps are named for what the text is for, not for a t-shirt size, because that is what keeps the scale from drifting again. Six steps deliberately keep the exact size/line-height pairs of the Tailwind defaults they replace, so the ~1,900-call-site rename moves nothing on screen. The visible changes are confined to former arbitrary values snapping to a step: 8/9/10px -> micro (11px) on badges and overlines; 17 -> 18; 22 -> 24; 30 (text-3xl) -> 36 on headings and stat numbers; 12.8px -> label (13px) on small buttons and toggles. Half-pixel sizes are gone. This supersedes #6108, which was reverted by #6116 because the sidebar group labels rendered at the inherited 16px. The cause was not the scale but cn(): `text-<x>` is ambiguous in Tailwind, and tailwind-merge resolves it against a table listing only the default sizes, so it filed every role step under text-colour and dropped whichever of `text-caption` / `text-sidebar-foreground/70` came first. Registering the steps as a font-size class group restores the real conflict groups — size beats size, colour beats colour, the two coexist — and a test pins the list against the scale, since the failure is silent in source. Hand-written CSS is covered too. The transcript kept a 12.5px body long after every Tailwind call site was on the scale, so the "no half-pixel sizes" claim was true of the classes and false of the product; the editor's prose, code and mermaid ramps had the same blind spot, and seven of their eight values already equalled a step exactly. All now reference var(--text-*). The guard test reads raw `font-size:` declarations as well as class names, exempting only the 16px iOS input-zoom workaround in base.css and the landing pages' marketing ramp. apps/mobile (own NativeWind config) and apps/docs (fumadocs' own type system) keep Tailwind's default scale and are untouched. Landing display type (rem/clamp, 2.2-6.4rem) stays on its separate ramp, as do four decorative emoji / serif-hero sizes. Verified on a running local stack: pinned sidebar rows and group labels measure 12px/16px, nav items 14px/20px — identical to pre-migration. An audit of every rendered font size across the product surfaces finds nothing off the scale; the only exceptions are avatar initials and emoji, which actor-avatar.tsx sizes proportionally to the avatar diameter by design. Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
37d66260e8 |
feat(skills): rebuild skill detail page around Overview/Files tabs (MUL-5443) (#6100)
* feat(skills): rebuild skill detail page around Overview/Files tabs (MUL-5443) The skill detail page was the only detail surface that skipped the shared detail-page shape: no identity header, no tabs, and a hard three-column split (w-56 tree / editor / w-72 sidebar). Name and description appeared three times — the editor header, the metadata sidebar, and the SKILL.md frontmatter card — and the 900-character trigger descriptions agents match on were edited through a two-row textarea. Rebuild it on the agent detail page's structure: identity block, underline tabs synced to `?view=`, and the agent second-level nav rail reused for the file list (main file / supporting files), so nothing new is invented. - Overview owns the properties (name, description, labels) plus who uses the skill and the permission note. Description gets a field sized for the data and a character count. - Files pairs the rail with the editor and a Preview / Plain text control. That mode now lives on the page: it used to sit in FileViewer, which the per-path `key` remounted, so every file switch snapped back to preview. - Frontmatter is stripped from the preview — the properties above are the same two fields. - The save bar is page-level and always mounted while editable, so it covers edits from either tab and committing one never shifts the layout. - No Settings tab: UpdateSkillRequest carries only name / description / content / config / files, and edit rights are derived with no writable counterpart, so it would hold a delete button and a read-only sentence. Delete stays in the header, matching Archive on the agent page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(ui): keep page-level action bars out from under the chat launcher The chat launcher is mounted by the dashboard layout, so it overlays the bottom-right corner of every workspace page. Nothing had accounted for that: the skill detail page's Save button and the agent creation studio's footer both run to that corner, and both sat underneath it. The launcher's geometry moves to tokens and the button reads its size and inset from them, so the space it claims is derived rather than measured off a screenshot. A `pe-chat-launcher` utility applies that reserve; page-level bars that reach the corner add the class. Naming it keeps the intent legible at each site, makes every yielding surface findable by one search, and means a launcher that moves or resizes carries its clearance along. Only these two bars need it. The batch-action toolbars float centred, dialog footers are centred and above the launcher, and the composer bars sit inside their editors — none of them reach that corner. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(skills): stop the skill header restating what the Overview tab edits The header carried a 48px mark, the name at text-xl, the description over two lines, and a meta row — around 150px above a page whose only verbs are edit, add and delete. Name and description then appeared a second time on the Overview tab, as fields you can actually change. Every visit paid for a read-only restatement of the next screenful. It is one line now: mark, name, and the counts that say what the skill is made of — origin, files, agents using it, last update. All four are absent from the Overview tab, so none of them is a repeat. The description is dropped; the list this page is reached from already carries it for anyone deciding whether to open it. Adds ExpandableDescription for the descriptions that stay in a header, since neither detail page clamped and a long one pushed the meta row and tab strip down the page. The agent header uses it; its taller form is left alone, as bringing it across is a separate change to a page this branch does not otherwise touch. Also hides the Labels row while agent- and skill-scoped labels are behind their release flag. ResourceLabelPicker renders nothing with the flag off — the server gates the routes on the same flag — so the row was a label above an empty field, reading as broken rather than absent. The flag check is exported as a hook so callers can decide whether to lay out a row at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(skills): put the skill detail page's remaining sizes on the type scale #6108's guard flags five font sizes this page still sets outside the scale: three Tailwind defaults left by the merge and two `text-[10px]` picker headings that predate the scale. Mapped to their role-named steps — the identity strip's title to text-title, section headings to text-title-sm, and the arbitrary 10px to text-micro, the step the scale provides for overline labels. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(skills): rename and delete files from the tree itself Deleting a file meant selecting it, then finding a Trash button in the editor's top-right toolbar — nowhere near the row being deleted, and invisible until something was open. Renaming did not exist at all: the only way to change a path was to delete the file and retype its contents. Both live on the row now, reachable by right-click or by a trailing button that appears on hover, the two entry points opening one menu rather than a context-menu root beside a dropdown root. Renaming happens in the row, where the name is read and the neighbouring paths stay visible to compare against. Validation stays with the caller, so the tree carries no second opinion on what a legal path is. Delete takes the path it acted on, so it no longer removes whichever file happens to be open; the toolbar button keeps working unchanged. Rows offer nothing to read-only viewers, and nothing on SKILL.md, which maps to skill.Content and which the server drops from the files list. The path validator gains two rules the rename path made reachable. Directories here are inferred from slashes rather than stored, so naming a file after an existing folder — or nesting one under an existing file — makes buildTree merge it into that node: the file saves and then cannot be seen. Both directions are refused now, for adds as well as renames. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
beb3e9be65 |
fix(chat): widen the conversation gutter and align its edges (MUL-5497) (#6140)
The chat body hugged its pane: a flat px-5 (20px) on every layer, which reads as cramped once the conversation pane is wider than the floating window it was tuned for. The gutter now scales with the CONTAINER — 20px base, 32px past @2xl (672px), 48px past @4xl (896px) — so the chat tab's resizable pane and the agent builder column breathe while the 360px floating window keeps exactly its current spacing. Viewport variants would have been wrong here: these three surfaces are independent widths inside one window. Layout also drifted between layers. The message list capped `max-w-4xl` with its padding INSIDE the cap while the banners and composer put theirs outside, so past ~936px the text sat 20px narrower than the box below it. Both now come from one CHAT_GUTTER / CHAT_COLUMN pair — gutter outside the cap — which is what keeps the edges locked together. Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
545afea827 |
Revert "feat(ui): establish a role-named type scale and migrate ad-hoc font s…" (#6116)
This reverts commit
|
||
|
|
d68d636c91 |
feat(ui): establish a role-named type scale and migrate ad-hoc font sizes (MUL-5451) (#6108)
tokens.css defined colours, radii and font families but not a single --text-* step, so font sizes had no baseline to align to and grew wherever they were needed: 51 distinct sizes across web + desktop, 370 of them written as arbitrary text-[Npx] values, six at half a pixel (10.5 / 11.5 / 12.5 / 13.5 / 14.5 / 15.5px). text-xs and text-sm carried nearly all UI text while the range between them — 11, 13, 15px — could only be reached with arbitrary values. Hierarchy does not come from having more sizes; past a handful, each extra size makes the hierarchy blurrier. Add ten role-named steps, each with its own line-height so leading cannot fragment the way size did, and move every product-UI call site onto them. Steps are named for what the text is for, not for a t-shirt size, because that is what keeps the scale from drifting again. Six steps deliberately keep the exact size/line-height pairs of the Tailwind defaults they replace, so the 1,900-call-site rename (text-sm -> text-body and friends) moves nothing on screen. The visible changes are confined to former arbitrary values snapping to a step: - 8 / 9 / 10px -> micro (11px): 102 sites, mostly badges and overline labels. Deliberate — under 12px is a counter role, not a text role. - 17px -> title (18px), 22px -> display-sm (24px), 30px (text-3xl) -> display (36px): 21 sites, all headings or stat numbers in flexible containers. - Half-pixel steps are gone entirely. Arbitrary sizes also inherited whatever line-height was above them; the tokens now pin one, which removes latent overflow risk in the fixed-height h-4/h-5 badges those sizes were used in. apps/mobile (own NativeWind config) and apps/docs (fumadocs' own type system) keep Tailwind's default scale and are untouched. Landing-page display type (rem/clamp, 2.2-6.4rem) is marketing typography on a separate ramp and stays out of the scale, as do four decorative emoji / serif-hero sizes. A guard test fails the build on any font size written outside the scale, reporting file:line — without it nothing in a Tailwind build makes an off-scale value look wrong, which is how the drift happened. Verified against a local stack: typecheck, lint and the full TS suite show no new failures, and before/after screenshots of issues, issue detail, runtimes (populated), usage, agents, inbox, my-issues and settings differ only in the intended 10px -> 11px labels, with no row-height, truncation or layout shift. Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
d4dac0e77c |
perf(agents): index-backed latest-terminal lookup in task snapshot (MUL-5436) (#6085)
ListWorkspaceAgentTaskSnapshot took each agent's latest completed/failed
task with a workspace-wide DISTINCT ON, so every presence load read and
sorted the workspace's whole terminal history. Neither existing index
matches that shape: (agent_id, status) has no completed_at, and migration
231's (completed_at) partial index is completed_at-first for the Usage
rollups.
Replace the outcome half with a per-agent JOIN LATERAL Top-1 and add a
partial index on (agent_id, completed_at DESC NULLS LAST, created_at DESC,
id DESC) WHERE status IN ('completed','failed'). On a 40-agent workspace
with 200k terminal rows this goes from 6631 shared buffers / 48.3 ms to
162 buffers / 0.1 ms, with an identical row set.
The (created_at, id) tie-break also makes the pick deterministic when
completed_at ties or is NULL — completed_at DESC alone left the winner up
to the plan.
Report #6075 asked to delete the outcome half as dead code, but PR #2608
made the Squad hover card (AgentLivePeekCard) read those rows for its
"last activity" line, so removing them would be a product regression for
shipped desktop builds. The response contract is unchanged here; splitting
the outcome into a lazy endpoint stays follow-up work.
Also tighten pickLatestTerminal to completed/failed only, matching the
snapshot's filter — it accepted cancelled, which the endpoint never
returns and which would have masked an agent's last real outcome.
Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
cc5ee169f0 |
fix(agents): allow agent owners to manage their own agent's env (MUL-5438) (#6080)
* fix(agents): let agent owners manage their own agent's env (MUL-5438)
GET/PUT /api/agents/{id}/env admitted only workspace owner/admin, which
made env the one endpoint in the agent permission model that ignored
agent ownership: canManageAgent already lets the owner update/archive,
and canViewAgentSecrets already lets the owner read mcp_config. The
asymmetry was worst on create — POST /api/agents accepts custom_env from
any member and stores that member as owner_id — so a member could write
secrets into their own agent and then never read or rotate them, with
UpdateAgent rejecting custom_env outright and no workaround left.
authorizeAgentEnv now admits a workspace owner/admin OR the agent's own
human owner. The agent-actor rejection still runs first and unchanged:
an agent process is denied even when its backing human owns the target
agent. The owner comparison uses member.UserID rather than the raw
X-User-ID header because agent.owner_id is nullable and uuidToString
renders NULL as "", and canManageAgentEnv rejects an empty owner from
the other side too.
The web Environment tab is gated on the same rule via the existing
canEdit decision, so it stops offering a "Reveal & edit" action that is
a guaranteed 403. The server remains the boundary.
Closes #6076
Fixes: https://github.com/multica-ai/multica/issues/6076
Co-authored-by: multica-agent <github@multica.ai>
* docs(agents): correct stale "owner/admin only" env permission wording
Follow-up to the MUL-5438 permission change: several comments and the
published docs still described the env endpoints as workspace
owner/admin only, which now contradicts the code.
- router.go / agent.go / types/agent.ts: the three sites flagged in
review.
- agents-create.mdx (en/zh/ja/ko): the user-facing callout said reading
values requires a workspace owner or admin. It now names the agent's
own owner first, and spells out that the agent-actor denial holds even
for an agent the same human owns.
- daemon.go / middleware/auth.go: these called the env endpoints
"owner-only" as shorthand for "reject agent actors". That property is
unchanged, but "human-only" is what they actually mean now.
Comments and docs only — no behavior change.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Bohan-J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
cf4114cd5d |
MUL-5396: validate agent concurrency limits (#6034)
* fix(agent): validate concurrency limits Co-authored-by: multica-agent <github@multica.ai> * fix(agent): harden concurrency duplication Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
81797b5a03 |
feat(agents): thinking level + Codex speed in the create flow (MUL-5390) (#6027)
* feat(agents): expose thinking level and Codex speed in the create flow (MUL-5390) Creating an agent could only set runtime + model, so a Codex agent that needed Fast had to be created and then fixed in settings — and duplicating one silently dropped both overrides. The create API and the TS contract already carried `thinking_level` / `service_tier`; only the creation surface did not. - AgentDraft carries thinkingLevel / serviceTier, rendered in the Execution section through the same ThinkingSettingField / ServiceTierSettingField the settings page uses. They stay fail-closed: a field appears only when the exact selected model's live catalog on an online runtime advertises the capability, so no value can be sent that the daemon would refuse. - Payload and duplicate-draft assembly move into pure functions (buildCreateAgentRequest / buildDuplicateDraft); empty overrides are omitted instead of sent as "". - Dependency cleanup: a runtime change clears model + thinking + speed, a model change clears the two per-model overrides. Applies to the plain form, the AI-builder setup screen, a committed builder runtime rebind, and a builder draft that moves the model. - Duplicate now follows the rule `multica agent copy` already enforces: same runtime copies model / thinking / speed, a forced fallback to another runtime clears all three (it previously kept a model the target runtime may not serve) and says so. custom_args and concurrency are runtime-independent and still copied. - Settings page: changing the model no longer leaves an orphan override. Only what the authoritative catalog says the new model does not support is cleared, and it travels with the model in one request. An unknown catalog (offline runtime, discovery in flight or failed), an empty "runtime default" model or a model missing from the catalog leaves the stored values untouched instead of deleting a value the daemon would have honoured. - Restores the 255-rune description limit plus counter that the studio lost relative to the old dialog, and gates create on it since a duplicate or builder draft can seed a longer value programmatically. - zh-Hans / en / ja / ko strings, including a fuller duplicate notice (MCP servers and machine-local runtime config are not copied either). Out of scope, tracked separately: max_concurrent_tasks bounds on the API, from-template parity (its UI is unreachable on main), and copying skill enabled state / runtime-skill overrides. Co-authored-by: multica-agent <github@multica.ai> * fix(agents): do not seed a duplicate draft before runtimes resolve (MUL-5390) On a cold start — a direct ?duplicate=<id> link or a refresh — the agent list can resolve while the runtime list is still pending. The one-shot seeding effect ran anyway, so buildDuplicateDraft judged the source runtime unavailable against an empty list and permanently cleared the model / thinking_level / service_tier a same-runtime copy must keep, plus showed the fallback notice. Re-running on every runtime change would instead overwrite edits the user already made, so the gate is on the query being decidable. Seeding now lives in useDuplicateDraftSeed and waits for the runtime query to resolve or fail; a failure still seeds, since an unconfirmable runtime makes the fallback correct. Verified the new test fails without the gate. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
c271f80999 |
MUL-5370 fix: label stalled skill-bundle downloads, align failure-reason copy with the backend taxonomy (#6001)
* fix(daemon): label stalled skill-bundle downloads and make them retryable A skill bundle that could not be downloaded during task preparation surfaced as the bare string "resolve skill bundles: context deadline exceeded". taskfailure.Classify has no rule for a Go context deadline, so it landed in agent_error.unknown — a bucket that is NOT on the server's retry allowlist. A transient stall therefore became a terminal chat failure carrying a label nobody could act on, and the failure was invisible on the Usage page's Errors breakdown. (MUL-5370) - Add the platform-side reason skill_bundle_unavailable and put it on retryableReasons. Retrying is cheap and safe: the agent process never started, and bundles that did arrive are already cached on disk, so successive attempts converge. - Carry a sentinel error from the resolve loop so the reason is derived structurally rather than by matching the wrapped transport error's text, and name the skill, its declared size and the elapsed wait in the wrap — enough to tell "this bundle is too big for the link" from "the link is dead" without reading daemon logs. - Normalise the wire shape an OLD daemon produces (a non-empty catchall plus the previous "resolve skill bundles:" wrapper) on the server side. Installed daemons upgrade on their own cadence, and FailTask only classifies when the caller supplied nothing, so without this the fix would reach only hosts that happened to update — while the un-upgraded hosts most likely to be hitting the bug kept failing terminally. - Teach Classify about "deadline exceeded" and net/http's "Client.Timeout exceeded while awaiting" so any other Go-side deadline that reaches it as text stops falling into the unknown bucket too. - Backfill historical rows in both agent_task_queue and chat_message. Scoped to agent_error.unknown alone — the old wrapper string postdates the in-flight classifier by three weeks, so no row carrying it can hold the legacy coarse value — which keeps the down migration an exact inverse. Co-authored-by: multica-agent <github@multica.ai> * fix(chat): give chat its own failure copy for the refined reasons #5991 rebuilt the operator-facing failure labels around an open wire string with a raw-value fallback, but the chat bubble kept its own exact-key lookup against the six coarse values from migration 055. So all 14 agent_error.* values still missed and rendered the generic "Something went wrong and the agent couldn't finish replying" — the classification the backend had already computed was discarded at the last step, and that is the message the MUL-5370 reporter saw. - Add resolveFailureReasonKey in packages/core: exact match, else degrade an `agent_error.*` value to its family, else undefined. A reason newer than the shipped client now lands on the family line instead of the fallback. - Rekey the chat copy map by wire value and route it through the helper. Chat deliberately degrades to friendly copy rather than adopting the operator surfaces' raw-value fallback: it is read by the person who just sent a message, and the raw error is one click away under the collapsible. - Add refined chat copy (en / zh-Hans / ja / ko) only where it can say something the family line can't — a different next step: network, auth, quota, rate limit, context overflow, missing/outdated CLI, skill download. - Give skill_bundle_unavailable a label on the web and mobile surfaces and a class on the Usage page's Errors breakdown (runtime — the operator response is "check the daemon's link to Multica", the provider is not involved). - Mobile's two label maps were still coarse-only for the same reason; rekey them by wire value and fill in the refined taxonomy. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
4d0475ce89 |
feat(usage): error/failure charts on the Usage page (MUL-5352) (#5991)
* feat(usage): add error/failure visibility to the Usage dashboard The Usage page could only answer "how much did we spend"; nothing on it showed how often agents fail, what kind of failure it was, or which agent is responsible. Operators had to open failed tasks one at a time to spot a pattern. `agent_task_queue.failure_reason` already carries the refined 21-value taxonomy from server/pkg/taskfailure, so this is a read path over data that already exists. Backend — two rollups, both scoped by workspace/project/window like the existing dashboard endpoints: GET /api/dashboard/failures/daily per-(date, failure_reason) GET /api/dashboard/failures/by-agent per-(agent, failure_reason) They return every terminal task, not just failures: the `failure_reason: ""` row carries the succeeded count. That is what makes the error rate's denominator share filters with its numerator. The run-time rollups can't serve as that denominator — they require `started_at IS NOT NULL`, so a task that expired in the queue (the signature of a runtime outage) contributes nothing to their failed_count. A failed row with an empty reason column lands in an `unclassified` bucket rather than being mistaken for a success. Frontend: - "Errors" joins the trend toggle, daily and weekly, stacked by failure class with the bucket's error rate in the tooltip. - An Errors card breaks the window down by class and by agent, with the raw failure_reason strings behind a disclosure (unlocalised — an operator pastes them into a log search). Each agent row links to its Work tab, which lists the actual failed runs. - The 21 backend reasons fold into 7 display classes in @multica/core/dashboard. Unknown reasons — including ones from a backend newer than the client — land in "other" instead of being dropped, so the class totals always reconcile with the failure count. The Tasks KPI tile is deliberately left alone: its value counts started tasks only, so quoting the failure rollup's larger count there would put two denominators in one tile. The Errors card states its rate with the denominator spelled out instead. Migration 225 adds a partial index on agent_task_queue(completed_at) for terminal statuses. The table had no completed_at index at all, so the two pre-existing run-time rollups were already scanning it; these two new queries would have doubled that. Closes #4429 (MUL-5352) Co-authored-by: multica-agent <github@multica.ai> * fix(usage): correct the Errors drill-down, window and agent exposure Review findings on PR #5991. 1. The drill-down pointed at the wrong page. `?view=work` renders ActorIssuesPanel — the issues assigned to the agent — while its runs live in the Overview pane's ActivityTab. Link to Overview. That page also could not show why a run failed: `failureReasonLabel` was a `Record<TaskFailureReason, string>` indexed with a cast to the old 6-value coarse enum, so every refined reason the backend has written since MUL-1949 resolved to `undefined`. It is now a function over the full 21-value taxonomy plus the legacy coarse values, falling back to the raw wire string for anything newer than the client. Fixes the issue execution log too, which had the same cast. 2. The Errors card covered one more calendar day than the chart above it. `parseSinceParamInTZ` returns N+1 days of headroom on purpose and the dashboard trims the surplus client-side — but only a series carrying a date can be trimmed that way. Totals / classes / reasons now derive from the date-bucketed rollup after that trim, and the per-agent rollup (which has no date to trim on) closes its window server-side via a new `parseExactSinceParamInTZ`. At days=1 the card previously reported yesterday's failures beside a chart showing none. 3. The top-offenders list leaked agents the viewer cannot see. The failure rollups are workspace-scoped and deliberately skip per-agent visibility, but the agent list they are joined against does not — members only see a private agent when they own it or are owner/admin. `name ?? row.agentId` therefore rendered a bare UUID along with that agent's failure count, rate and dominant error class. Unresolvable agents now fold into one anonymous row, and the renderer never falls back to an id. Stricter than `bucketUnknownAgentRows` while the agent list loads: a transient flash of UUIDs is the leak, not a cosmetic glitch. Also from the review: the Errors tooltip echoed the raw Recharts dataKey ("rate_limit") instead of the translated label the legend already carries. Not changed — the schema's `failure_reason` default stays `""`. Defaulting a missing field to a failure bucket guards against a deflated rate, but the realistic drift is `omitempty` on the Go struct tag, which would strip the field from exactly the SUCCESS rows and read as a 100% error rate. Added TestDashboardFailureWireContractKeepsEmptyReason to pin that the server always emits the field, which is the assumption the default rests on. Co-authored-by: multica-agent <github@multica.ai> * fix(usage): renumber migration and fix the anonymous bucket's failure class Review findings on PR #5991, round 2. 1. Migration prefix 225 collided with `225_chat_message_channel_media_pending`, which landed on main while this branch was open — backend CI failed on TestMigrationNumericPrefixesStayUniqueAfterLegacySet. Merged main and renumbered to 231; main now carries 225 through 230, so 226 is taken too. 2. The anonymous "Other agents" bucket could announce the wrong failure class. It merged rows that had ALREADY collapsed to one dominant class per agent, then credited each agent's entire failure count to that class. An agent failing auth 6 / timeout 5 contributed 11 to auth and 0 to timeout, so a bucket whose real composition was timeout 15 / auth 6 rendered as Auth. Fixed by anonymizing the raw per-(agent, reason) rows instead: the sentinel becomes just another agent_id and `aggregateAgentFailures` computes its classes from real counts. That also deletes the parallel bucketing pass — one identity rewrite replaces it. `knownAgentIds` moves up to where both consumers can see it. Also from the review: - The wire-contract test decoded both payloads into one map. json.Unmarshal merges into a non-nil map rather than resetting it, so a residual failure_reason from the first case could have masked an omitempty regression in the second — exactly what the test is meant to catch. Now table-driven with a fresh map per case. - A test comment still described the drill-down as pointing at the Work tab. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
7cc763bbfb |
fix(runtimes): show runtime aliases across user-facing surfaces (MUL-5248) (#5881)
Several UIs rendered the raw daemon name (runtime.name) and dropped the user's custom_name alias: the Skills "Copy from runtime" selector, the Skills list/detail source, the agent creation runtime chip, the agent runtime filter, the agent overview, the skills/MCP runtime hints, the runtime delete confirmations, the desktop runtime window title, the onboarding runtime cards/hints, and the task transcript chip. Route every user-visible runtime label through the shared display contract: - runtimeDisplayLabel (alias + provider) for standalone labels - runtimeDisplayName (alias only) where a provider icon/text sits beside it - machine.title + runtimeRowLabel(runtime, machine.title) inside machine groups The Skills "Copy from runtime" selector is now machine-grouped via the shared buildRuntimeMachines/runtimeRowLabel helpers instead of a flat raw-name list; runtime.name stays the source of identity for hostname parsing, grouping, search, and protocol payloads only. Add a conventions rule (en + zh) forbidding raw runtime.name in user-visible JSX/i18n/Select labels/document titles. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
e17e04b312 |
MUL-5215: fix(agents): keep creation errors visible (#5746)
* fix(agents): keep creation errors visible * fix(agents): localize duplicate-name errors |
||
|
|
6992c58de3 |
MUL-5185: add Codex Fast mode (#5821)
* feat(agents): add Codex fast mode (MUL-5185) Co-authored-by: multica-agent <github@multica.ai> * fix(agents): make Codex Fast override authoritative Co-authored-by: multica-agent <github@multica.ai> * fix(agents): remove Codex Fast config conflicts Co-authored-by: multica-agent <github@multica.ai> * chore: refresh checks after conflict resolution Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
168620fc15 |
MUL-5163 fix(agents): rebind the Agent Builder carrier when the runtime is switched (#5780)
Switching the runtime mid-conversation in Build with AI only updated local React state, so the picker could show runtime B while every subsequent message still executed on the runtime frozen at session create time.
- Add PATCH /api/agent-builder/sessions/{id}/runtime to rebind the hidden builder carrier (runtime_id/runtime_mode, model cleared since model ids are per-runtime). Creator-only, builder carriers only, target must be in-workspace, usable by the member, and online; a reply in flight returns 409.
- Serialise rebind against send: both take LockChatSessionForRuntimeBind on the chat_session row and SendDirectChatMessage re-reads the agent inside that transaction, so a send blocked behind a rebind cannot resume and stamp its task with the runtime the switch moved away from.
- Leave chat_session.runtime_id stale on purpose so the daemon starts a fresh provider session on the new runtime while Multica-side history and the draft survive.
- Frontend updates the draft only after the server reports the bound runtime, blocks sending during a rebind, disables the Mine/All filter alongside the trigger, and explains why the picker is locked during a pending reply.
Closes #5773
|
||
|
|
e0e9383efe |
Improve UI animation transitions (MUL-5172) (#5718)
* Improve UI animation transitions * Remove implementation plan documents Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
5d9295ac65 |
feat(agents): add per-agent runtime skill controls (#5686)
* feat(agents): add per-agent runtime skill controls Co-authored-by: multica-agent <github@multica.ai> * fix(agents): renumber runtime-skill migration and broadcast agent:status on toggle Address the MUL-5101 review blockers on PR #5686: - Rebase onto main and renumber the runtime-skill-disable migration 202 -> 203. main added 202_runtime_profile_add_qwen, so the pair collided on prefix 202 and migrations_lint_test would reject the duplicate. 203 is the next free prefix. - Publish an "agent:status" event after persisting a disabled_runtime_skills override, mirroring the workspace-skill toggle in writeUpdatedAgentSkills. The realtime layer keys off this event to invalidate workspaceKeys.agents, so other open web/desktop/mobile clients now drop their stale toggle state instead of only the initiating tab refreshing. Reload junction-table skills before the broadcast so it doesn't signal cleared skills (#3459). - Add a handler regression test proving the broadcast fires on both disable and enable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: Walt <walt@multica.ai> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
4ad75977eb |
fix(agents): reset model and thinking level on runtime change (MUL-4963) (#5615)
Closes #5614 |
||
|
|
1483ce0825 |
fix(agents): always enable AI creation (MUL-4998) (#5660)
Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
ce15300493 |
MUL-4884: feat(issues): anchor the working chip on agents + real colour tiers (#5540)
* fix(issues): anchor the working chip on issues the filter actually shows (MUL-4884)
The header chip showed three units at once: the number counted distinct
issues, the avatar stack counted agents (with a rival "+N"), and the hover
card counted tasks — under a label with no noun at all. Each figure was
self-consistent; together they read as a miscount.
|
||
|
|
b31132edf3 |
feat(agents): add access-scope column, filter, and bulk edit to agents list (#5393)
Adds a three-state access column/filter and a bulk "Set access scope" action to the agents list, plus a fix for a state-update loop in the bulk access dialog (parent/child callback + effect cleanup were re-triggering each other). Co-authored-by: chouti <chouti@users.noreply.github.com> |
||
|
|
bf64bb9214 |
MUL-4781: keep machine CLI status visible
Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
56f453fed6 |
feat(agents): add loading skeleton for Recent work list (#5438)
The Recent work section on the agent Activity tab reads a lazily-loaded per-agent task list. On first paint the query is still pending, so the section rendered its 'nothing finished yet' empty state — a wrong answer that flashes before real data arrives. Render a skeleton (bordered, divided placeholder rows matching the real TaskList shell) while the first fetch is in flight, keyed off the query's isLoading. Once the cache is hydrated the tab opens straight into data with no skeleton flash. Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
45c0b38a94 |
fix(attribution): put run avatar on the meta-line middot rhythm (MUL-4767) (#5420)
The accountable-member avatar in the agent Activity 'Recent work' rows was appended to the middot-delimited meta line with only a 2px nudge (ml-0.5), so it read as glued to the duration while every other item (status / time / duration) is spaced by a '·' separator. Give the avatar a leading separator so it shares the same rhythm, and gate both on task.attribution?.initiator — the exact condition the avatar badge itself renders on — so no dangling separator is left for an unattributed run. Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
4fac8d772f |
feat(attribution): Human Attribution Phase 1 (MUL-4302) (#5150)
* feat(attribution): Phase 1 foundation — provenance schema + resolver (MUL-4302)
Human Attribution, Phase 1 (地基) first increment. Every agent run must be
traceable to exactly one accountable human AND record at which waterfall level
that human was resolved, so a NULL originator can be told apart from a genuine
'no human in the chain'.
- migration 149: add originator_source (waterfall label) + delegation/retry/
rerun/rule-version lineage + kind-tagged trigger evidence to agent_task_queue.
No FK, no cascade, no CHECK on the source enum (MUL-4302 §7); nullable ADD
COLUMNs = fast metadata-only change on the hot queue table.
- internal/attribution: the accountable-human vocabulary (Source, EvidenceKind,
TriggerKind) + pure, unit-tested classification rules (ClassifyComment/
ClassifyDirect). No DB, no authorization — provenance labeling only.
- service: attributionFor{IssueTask,TriggerComment} gather facts and delegate to
the pure classifier; the legacy originator resolvers now delegate here so
there is one source of truth. originator_user_id's VALUE is unchanged, so the
Composio-overlay and canInvokeAgent A2A authorization boundaries are
byte-for-byte preserved (MUL-4302 §1.3).
- enqueueIssueTask / enqueueMentionTask stamp originator_source + evidence;
CreateRetryTask carries the parent attribution forward and records
retry_of_task_id so retry and manual rerun stay separable (MUL-4302 §5).
Verified: go build ./..., go vet, gofmt clean; new attribution unit tests +
enqueue stamping integration test green; existing resolve_originator tests
unchanged.
Co-authored-by: multica-agent <github@multica.ai>
* feat(attribution): split accountable_user_id from originator, close enqueue bypasses (MUL-4302)
Phase 1, per Bohan's decision on the MUL-4302 thread: audit and authorization
answer different questions and get different columns.
- Migration 151 adds agent_task_queue.accountable_user_id (no FK, no cascade).
Authorization keeps reading ONLY originator_user_id (canInvokeAgent A2A gate,
Composio overlay); audit/UI/usage read accountable_user_id + source + evidence.
- Invariant (finalizeAttribution, single chokepoint + §11 tests): originator
non-null ⟹ accountable equals it. The two diverge only when originator is null
(autopilot / degraded fallback), which is the deferred rule_owner/owner_fallback
increment; this lands the column + mirror-write so that split has a home.
- Close the NULL-source enqueue bypasses Elon flagged: chat, quick-create,
deferred-fallback and run_only-autopilot now stamp originator_source + evidence
(+ accountable where a human exists). Autopilot stays unattributed until the
rule-version snapshot table lands, but is no longer a silent NULL-source row.
Retry inherits accountable_user_id like the rest of the attribution lineage.
- Fix assign/promote attribution (§4): a member who assigns/promotes an existing
issue is now the accountable human (and, by the invariant, originator) ahead of
the issue creator. Threaded as an OPTIONAL actor override, so comment/rerun/
autopilot paths keep today's resolution and create-with-assignee (creator ==
actor) is unchanged. The squad leader gate already judged the same member.
Also merges origin/main: renumbers the attribution migration 149→150 (main took
149 for issue_origin_agent_create) and folds agent_create into ClassifyDirect's
origin inheritance.
go build/vet/gofmt clean; attribution unit tests + service stamp/actor tests +
handler suite pass on a fresh DB migrated through 151.
Co-authored-by: multica-agent <github@multica.ai>
* docs(attribution): fix accountable NULL semantics + close chat/quick-create evidence boundary (MUL-4302)
Addresses Elon's 2nd-round review on PR #5150 (pre-merge doc/evidence items):
- Migration 151 no longer overclaims NULL. accountable_user_id is NULL not only
on pre-migration rows but on NEW rows whose audit source resolved no human yet
(run_only autopilot writes originator_source='unattributed' with NULL
accountable until rule_owner lands). Header + COMMENT ON COLUMN reworded so a
schema reader does not misjudge the invariant.
- Chat now uses the UNIFORM evidence pair (kind=chat, ref=chat_session_id), like
autopilot_run/issue_assignment, instead of relying only on the dedicated
chat_session_id column — new EvidenceChat kind. Added a service test asserting
chat stamps direct_human + chat evidence.
- Quick-create is documented as the ONE intentional no-antecedent-row path: no
comment/issue/session/run exists at enqueue time (the run creates the issue), so
trigger_evidence_kind/ref stay NULL while the human rides originator/accountable
and source is direct_human — not a NULL-source bypass.
No authorization behavior change. attribution + service + handler suites pass on a
DB migrated through 151.
Co-authored-by: multica-agent <github@multica.ai>
* chore(attribution): renumber migrations 150/151 → 157/158 after merging main (MUL-4302)
main's #5162 ("unblock release migrations") renumbered the chat migrations and
took 150 (agent_task_coalesced_comments) and 151 (chat_read_cursor), colliding
with this branch's attribution migrations. Renumber them above main's new highest
(156) so TestMigrationNumericPrefixesStayUniqueAfterLegacySet passes:
- 150_agent_task_attribution → 157_agent_task_attribution
- 151_agent_task_accountable_user → 158_agent_task_accountable_user
Fixed the internal "migration 150" references in 158's header to 157. Migrations
apply cleanly through 158 on a fresh DB; migration lint green.
Co-authored-by: multica-agent <github@multica.ai>
* feat(attribution): autopilot rule_owner — accountable = rule version publisher (MUL-4302)
Implements rule_owner (MUL-4302 §3.4), the first attribution source where the
accountable human diverges from the (NULL) authorization originator.
- Migration 159 adds the append-only autopilot_rule_version snapshot table (no FK,
no cascade); migration 160 adds its CONCURRENTLY lookup index.
- Write-on-publish: CreateAutopilot appends v1 (publisher = creator); UpdateAutopilot
appends a new version when a SUBSTANTIVE autopilot-row field changes (assignee /
status / execution_mode) — cosmetic edits (title/description/template) write none.
Both run inside the existing handler tx (atomic with the autopilot write).
- Dispatch resolution: both autopilot execution modes now resolve the active rule
version and stamp originator_source='rule_owner', accountable_user_id=publisher,
rule_version_id=<snapshot>, with originator_user_id left NULL (authorization
unchanged). run_only stamps CreateAutopilotTask directly; create_issue resolves in
attributionForIssueTask so both modes attribute identically. A missing version /
non-member publisher degrades to unattributed — never fabricates a human.
- finalizeAttribution now enforces the invariant ONE-WAY: it mirrors originator onto
accountable only when originator is valid, leaving an explicitly-set accountable
(rule_owner / future owner_fallback) intact when originator is NULL. Added
rule_version_id to CreateAgentTask so the create_issue path persists it too.
Also merges origin/main and renumbers this branch's attribution migrations
150/151 → 157/158 (main's #5162 took 150/151); rule_version table is 159/160.
Tests: attribution unit RuleOwner + one-way invariant table; service integration
tests proving an autopilot-origin issue stamps rule_owner + rule_version_id (and
degrades to unattributed with no version). Full service/attribution/handler/
migration suites pass on a DB migrated through 160; build/vet/gofmt clean.
Deferred (same PR): trigger-table republish (cron/webhook/event_filters) and
system-pause/archive versioning; owner_fallback + fail-closed; manual rerun.
Co-authored-by: multica-agent <github@multica.ai>
* fix(attribution): manual autopilot trigger → direct_human to the triggering member (MUL-4302)
Elon's blocking finding: a member manually triggering an autopilot was attributed
rule_owner (accountable = rule publisher, originator NULL) like a schedule/webhook
run, so member B triggering member A's autopilot landed accountable=A and carried
no originator authorization context for the run. Per MUL-4302 §4 a manual "run now"
is a direct human action and must attribute direct_human to the triggering member.
- Thread the triggering member from TriggerAutopilot into dispatch: new
DispatchAutopilotManual carries actorUserID (resolved via resolveActor +
memberActorUserID, so only a member actor is a human; an A2A agent actor falls
back to rule_owner). DispatchAutopilot / DispatchAutopilotForPlan keep their
public signatures (pass an invalid actor); only the internal dispatchAutopilot /
dispatchCreateIssue / dispatchRunOnly gained the param, so the many existing
callers are untouched.
- run_only: dispatchRunOnly stamps direct_human (originator == accountable ==
actor, no rule_version) for a manual actor, else rule_owner. CreateAutopilotTask
gains an originator_user_id param for the manual case.
- create_issue: dispatchCreateIssue enqueues a manual trigger via the actor-carrying
*WithHandoff entry points; attributionForIssueTask's autopilot-origin rule_owner
branch is now guarded on !actorUserID.Valid, so a valid actor falls through to the
direct_human override. Both execution modes attribute identically.
- schedule / webhook keep rule_owner (no actor). Trigger-table + system-pause/archive
versioning remain the pre-merge follow-ups.
Tests: the run_only row assertion Elon asked for (schedule → rule_owner row on
CreateAutopilotTask), plus manual direct_human on BOTH modes (run_only and
create_issue), including a manual actor distinct from the rule publisher. Full
service/attribution/handler/migration/scheduler/cmd suites pass on a DB migrated
through 160; build/vet/gofmt clean.
Co-authored-by: multica-agent <github@multica.ai>
* feat(attribution): owner_fallback + fail-closed policy, and manual-rerun direct_human (MUL-4302)
Two of the three remaining Phase 1 items (trigger-table / system-pause versioning
is deferred — see PR description).
owner_fallback + fail-closed (§1/§3.5) — the never-null accountable guarantee:
- attribution.OwnerFallback degrades an UNATTRIBUTED result to owner_fallback:
accountable = agent owner, originator stays NULL (audit-only, authz untouched),
Source.Precise()==false. finalizeAttribution's one-way invariant already allows
accountable-set / originator-NULL divergence, so nothing else changes.
- Migration 161 adds workspace.attribution_fail_closed (default FALSE) + a lean
GetWorkspaceAttributionFailClosed read. (Also added the column to ListWorkspaces'
explicit column list so its row type stays db.Workspace.)
- applyAttributionFallback is applied at every enqueue boundary (issue, mention,
chat, quick-create, deferred-fallback, autopilot run_only): unattributed →
owner_fallback (agent owner) by default, or ErrAttributionFailClosed when the
workspace is fail-closed, which the caller surfaces to refuse the enqueue (the
run does not start). So no run is left without an accountable human, and a
compliance workspace can block unattributable runs instead.
manual rerun (§5) — a rerun is a NEW direct_human trigger to the rerunning member:
- RerunIssue threads the acting member (resolved in the handler via resolveActor)
down to enqueueRerunTask, and attributionForIssueTask is now actor-first so the
actor wins over an INHERITED trigger comment (a rerun keeps the comment for the
daemon's prompt context but must attribute to whoever clicked rerun, not the
original comment's human).
- rerun_of_task_id lineage is recorded via a targeted SetAgentTaskRerunOf update on
the rerun path only (keeping the shared CreateAgentTask insert untouched), so
system retry (retry_of_task_id) and human rerun stay separable in reporting.
Tests: OwnerFallback unit test; owner_fallback + fail-closed-refusal + manual-rerun
(direct_human + rerun_of_task_id) service tests; the prior "degrades to unattributed"
test updated to owner_fallback. Full service/attribution/handler/migration/scheduler/
cmd suites pass on a DB migrated through 161; build/vet/gofmt clean.
Co-authored-by: multica-agent <github@multica.ai>
* fix(attribution): close fail-open holes + move rerun_of_task_id into creation snapshot (MUL-4302)
Addresses Elon's two must-fixes on PR #5150.
1. accountable-never-null / fail-closed had fail-open holes. applyAttributionFallback
now, for an UNATTRIBUTED run, refuses the enqueue (ErrAttributionFailClosed) in
THREE cases instead of silently degrading to a runnable NULL-accountable task:
- workspace policy read fails (or no workspace) → fail closed; we cannot confirm
fallback is permitted, so we don't run an unattributable task on a DB hiccup.
(Only the rare unattributed path pays this; precise runs never read the policy.)
- workspace is fail-closed → refuse (unchanged).
- owner_fallback has no valid agent owner → refuse rather than enqueue a task with
a NULL accountable_user_id.
ErrAttributionFailClosed's doc now covers all three "cannot guarantee an
accountable human" refusals. Added missing-owner / policy-read-failure /
precise-passthrough tests.
2. manual rerun rerun_of_task_id was a post-notify UPDATE (race: the queued event /
daemon claim could see rerun_of_task_id = NULL, and a failed update degraded the
run to a plain direct_human). It now rides the CreateAgentTask insert — threaded
through enqueueIssueTask / enqueueMentionTask as a creation param (like
retry_of_task_id) so it is written in the same statement before the daemon is
notified. Removed the SetAgentTaskRerunOf follow-up query.
Also merges origin/main (unrelated CLI fix #5167, no conflict). Full service /
attribution / handler / migration / scheduler / cmd suites pass on a DB migrated
through 161; build / vet / gofmt clean.
Co-authored-by: multica-agent <github@multica.ai>
* feat(attribution): rule_owner versioning on trigger edits + system-pause/archive (MUL-4302)
The final remaining Phase 1 item: substantive publishes beyond the autopilot row now
republish the rule version, so a run's rule_owner accountable follows whoever last
changed what the rule does.
- Extracted the config-summary + insert into service.RecordAutopilotRuleVersion so
the handler and the (different-package) failure monitor share one writer; the
handler's recordAutopilotRuleVersion is now a thin wrapper.
- Trigger edits: UpdateAutopilotTrigger and DeleteAutopilotTrigger republish the rule
version with the acting member as publisher, ATOMICALLY (tx-wrapped mutation +
version write, mirroring CreateAutopilot/UpdateAutopilot). CreateAutopilotTrigger
republishes best-effort — the webhook path mints its token with a retry loop that
cannot share one tx, and a create is usually initial setup already covered by v1;
a failed write there is benign (active version stays the current publisher, the new
trigger fires under it, no immediate daemon claim rides it).
- Archive (DeleteAutopilot) republishes (member, status=archived), tx-wrapped.
- System auto-pause (failure monitor) republishes with a 'system' publisher,
best-effort — a background sweep to a non-dispatching state (a paused autopilot
never dispatches; a later member resume supersedes).
- RotateWebhookToken / SetSigningSecret deliberately do NOT version: they rotate
credentials, not the rule's behavior (not §3.4 substantive).
Semantics: a system-published (no-member) active version degrades dispatch to
unattributed → owner_fallback, never fabricating a human.
Tests: republish-reattributes (member A → member B supersedes → dispatch resolves to
B; system publisher → unattributed). Full service/attribution/handler/migration/
scheduler/cmd suites pass on a DB migrated through 161; build/vet/gofmt clean.
Also merges origin/main (unrelated frontend feature #5074).
Co-authored-by: multica-agent <github@multica.ai>
* fix(attribution): make trigger-create rule-version republish atomic (MUL-4302)
Addresses Elon's final Phase 1 blocking finding: CreateAutopilotTrigger recorded the
rule-version republish best-effort AFTER the trigger insert. If member B added a
schedule/webhook trigger to member A's autopilot and the version write failed, future
schedule/webhook dispatches would keep attributing to A — violating the rule_owner
invariant that the last member to substantively change the rule owns future runs
("no immediate daemon claim" doesn't save it, since the miss surfaces at the LATER
trigger firing).
Both create paths now write the version in the SAME tx as the trigger INSERT:
- schedule create: wrap CreateAutopilotTrigger + recordAutopilotRuleVersion in one tx.
- webhook create: each mint-with-retry attempt runs in its own tx (insert + version
commit together; a token collision rolls that attempt back and retries with a fresh
token; a version-write failure rolls the trigger back). Passes ap + the acting
member id into the helper.
- removed the best-effort recordTriggerRuleVersionBestEffort helper (and the now-unused
slog import).
Test: TestCreateTrigger_RepublishesRuleVersionAtomically drives both create paths
through the handler and asserts a rule version is published by the acting member.
Existing webhook/trigger/archive handler tests still pass. Also merges origin/main
(unrelated avatar feature #5074). Full service/attribution/handler/migration/
scheduler/cmd suites pass on a DB migrated through 161; build/vet/gofmt clean.
Co-authored-by: multica-agent <github@multica.ai>
* feat(attribution): Phase 2.1 — surface run attribution on the task API (MUL-4302 §9)
First Phase 2 (visibility) increment: the agent-task API now returns the resolved
accountable-human provenance so the UI can render an "on behalf of" badge.
- AgentTaskResponse gains an `attribution` object: source label (never blank —
pre-migration NULL renders "unattributed") + `precise` flag (false for the degraded
owner_fallback / backfill / unattributed sources), the initiator (accountable) and
originator (authorization) user refs, the evidence {kind, ref_id} pointer, and the
rule_version / delegated / retry / rerun lineage ids.
- The label + evidence + raw ids are built in the PURE taskToResponse (no DB), so
every task response carries them. Names are hydrated separately, only on the
user-facing surfaces (ListAgentTasks, ListWorkspaceAgentTaskSnapshot, RerunIssue,
CancelTaskByUser) — daemon-claim paths stay lean.
- Hydration resolves initiator/originator from the GLOBAL user table (departed-member
safe) via a new batch GetUsersByIDs query (no N+1); best-effort, so a lookup hiccup
leaves the raw ids intact.
Tests: pure taskAttributionBase (direct_human / rule_owner NULL-originator /
owner_fallback degraded / pre-migration→unattributed) + DB hydration (fills known
ref, leaves unknown id un-filled, skips nil). Full handler/service/attribution/
migration/scheduler/cmd suites pass on a DB migrated through 161; build/vet/gofmt
clean. The field is additive — the frontend's parseWithFallback ignores unknown keys,
so nothing breaks until the UI increment consumes it.
Also merges origin/main (unrelated editor feature #5090).
Remaining Phase 2 (next increments, same PR): frontend zod schema + "on behalf of"
badge + evidence-chain jump; append-only correction events (write + display).
Co-authored-by: multica-agent <github@multica.ai>
* feat(attribution): Phase 2.2 — on-behalf-of badge in the execution log (MUL-4302 §9)
Surface the accountable human on every agent run row:
- AttributionBadge composes Badge + ActorAvatar, shows "on behalf of <member>"
with the resolution source as a tooltip; degraded (non-precise) attribution
gets a warning tone, and an unresolved initiator renders an explicit
"no responsible member" chip.
- Wire the badge into both active and past rows of the execution log.
- Mirror the attribution shape into AgentTaskResponseSchema (defensive, .loose())
so the cancel-task path carries it through zod; add parse tests.
- Export TaskAttribution/AttributionUser/TaskEvidence from @multica/core/types
and add the attribution block to all four issues.json locales.
Co-authored-by: multica-agent <github@multica.ai>
* fix(attribution): hydrate initiator names on issue-facing task endpoints + bound the badge (MUL-4302 §9)
Address Elon's PR #5150 review:
- ListTasksByIssue (the execution-log data source), GetActiveTaskForIssue and the
issue-scoped CancelTask now call hydrateTaskAttributions, so the "on behalf of
<member>" badge shows the real member name on issue detail instead of falling
back to "someone". Mirrors the existing ListAgentTasks / snapshot behavior.
- AttributionBadge: cap width (max-w-40, min-w-0) and truncate the name span so a
long name / narrow right column can't squeeze out trigger/status/actions; keep
the avatar shrink-0.
- Add a handler test asserting the issue task list returns a hydrated
attribution.initiator.name.
Co-authored-by: multica-agent <github@multica.ai>
* fix(attribution): use semantic AvatarSize 'xs' for the badge avatar
main refactored ActorAvatar.size from a raw pixel number to the semantic
AvatarSize union (packages/ui/lib/avatar-size). Switch the on-behalf-of badge
avatar from size={14} to size="xs" (16px) after merging main.
Co-authored-by: multica-agent <github@multica.ai>
* fix(attribution): stage-cascade falls back to parent-issue provenance, not agent owner (MUL-4302)
When closing the last sub-issue in a Stage wakes the parent's assignee agent, the
run was enqueued via a system-authored child-done comment with no actor, which the
resolver classified as unattributed and then degraded to owner_fallback (the agent's
own owner). That is the wrong accountable human: the woken run should be accountable
to whoever caused the parent issue to exist.
attributionForIssueTask now detects a system-authored trigger comment and falls
through to the parent issue's own provenance — the same creator / agent_create-origin
/ autopilot-origin chain a direct enqueue resolves (so an agent-decomposed parent
attributes via delegation to the human who drove it; a member-created parent to that
member; an autopilot parent to the rule publisher). owner_fallback is now only the
last resort when the parent provenance itself has no human.
- Extract attributionFromComment so attributionForIssueTask can inspect author_type
without a second GetComment; authorization resolution stays byte-identical.
- Add a DB-backed test asserting a system child-done comment resolves to the parent
issue's origin human (delegation), not owner_fallback.
Co-authored-by: multica-agent <github@multica.ai>
* feat(attribution): autopilot runs attribute to the firing trigger's creator (MUL-4302)
Per Bohan: an autopilot schedule/webhook run should be accountable to the human
who created the SPECIFIC trigger that fired it, not the rule publisher. (Manual
triggers already attribute to the invoking member via direct_human — unchanged.)
- Migration 162: add autopilot_trigger.created_by_type/created_by_id (nullable, no
FK/cascade). Capture the creating member at both trigger-create sites (schedule +
webhook).
- New precise source trigger_owner: originator stays NULL (an autonomous fire
carries no human authorization — same authz-safe divergence as rule_owner),
accountable = the trigger's member creator.
- triggerOwnerAttribution resolves run.trigger_id → creator; wired into run_only
dispatch and the create_issue path (bridging issue → active run → trigger_id).
Legacy triggers with no recorded creator, and agent-created triggers, degrade to
rule_owner then owner_fallback — nothing regresses.
- Frontend: trigger_owner source label in all four locales + badge switch case.
- Tests: attribution TriggerOwner unit + Precise/invariant; DB-backed resolver
tests (member creator → trigger_owner; creatorless → rule_owner fallback).
Co-authored-by: multica-agent <github@multica.ai>
* chore(attribution): re-trigger CI (dropped synchronize event on
|
||
|
|
9eddcaff10 |
fix(chat): defer cancellation-time finalization until the task transcript is stable (#5246)
A quick Stop before the agent's first token no longer races a late reply. Started-but-empty cancellations defer the empty/non-empty judgment until the daemon acks its transcript flush (or a grace-period sweeper fires), then settle to a single outcome. Empty outcomes persist a durable, creator-authorized draft restore (fetched/consumed via a dedicated endpoint, reconnect-safe and at-most-once) instead of broadcasting the prompt over the workspace bus. Closes #5219 |
||
|
|
e565a4559a |
fix(agents): show runtime alias + provider consistently (#5260) (#5340)
* fix(agents): show runtime alias + provider consistently (#5260) The daemon bakes the provider into runtime.name ('Codex (host)') while a custom alias is stored separately in custom_name. runtimeDisplayName() returned the bare alias and dropped the provider, and the agent list and profile card rendered raw name, ignoring the alias entirely. Add runtimeDisplayLabel(): with an alias it renders 'alias (Provider)', otherwise it returns the daemon name unchanged (no duplicated provider). Route the agent personal page, list Runtime column, and profile card through it. Fixes #5260 * fix(agents): use provider display-name map for aliased runtime label Address review on #5340: a title-cased slug mislabels providers whose display name differs from the slug (traecli -> 'Traecli' instead of 'Trae') and flattens mixed-case families (CodeBuddy / OpenCode / OpenClaw). Add a provider display-name map mirroring the ProviderLogo switch, with a title-case fallback for unknown slugs. * fix(agents): align provider display map with daemon contract Follow-up on #5340 review: the previous map canonicalized codebuddy / opencode / openclaw as CodeBuddy / OpenCode / OpenClaw, but the daemon's runtimeDisplayNameOverrides only special-cases traecli and first-letter- capitalizes the rest. That recreated alias/no-alias drift for those providers ('Openclaw (host)' vs 'box (OpenClaw)'). Shrink the frontend map to mirror the daemon exactly (traecli -> Trae, first-letter fallback otherwise) and point the comment at the daemon map as the source of truth. Tests updated to lock the alignment. |
||
|
|
6c6143e8fc |
fix(agents): always enable skill toggles (MUL-4520) (#5381)
Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
dc0c8afb98 |
fix(agents): gate agents list first paint on sort/filter deps (MUL-4511) (#5377)
The Agents list gated its first paint only on the main agent-list query (prefetched, so usually warm) while the sort/filter columns arrive in separate activity / run-count / presence queries. On entry the default lastActive sort ran on placeholder values (lastActiveDays null→Infinity, runCount 0), painting a degenerate name-ordered list that visibly re-ordered once each auxiliary query resolved (1–2 jumps). Add a need-based `listReady` render gate: wait for exactly the auxiliary queries the active sort field / filter depends on — nothing for name/created, run-counts for runs, activity + run-counts for the default lastActive, plus presence when an availability filter is active. Queries still run in parallel, so this only defers the first paint by at most one round-trip (shown as skeleton); an empty workspace skips the gate so the empty state is never blocked. Scope is the Agents page only. Adds agents-page.test.tsx covering the five gate scenarios. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
411a160b99 |
fix(release): harden v0.3.44 migrations (#5345)
Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
e1c65b0162 |
feat(agents): remove template entry from agent creation ModeChooser (MUL-4481) (#5343)
Drop the "使用模板" card and its onTemplate wiring from ModeChooser so the agent creation studio only offers blank and AI modes. Rebalance the mode grid to two columns. TemplateChooser and the template creation path are left in place but no longer reachable from the UI (known follow-up debt). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
3e03d8f97b |
fix(views): keep in-card side panels on the content surface color (#5329)
The settings nav and agent overview aside tinted themselves with bg-app-shell/70, pulling the window-chrome tone inside the content card. Since the desktop's active tab merges into the card top (MUL-4439), a tinted panel under the first tabs visibly broke the tab/content seam. Zone separation stays with the hairline divider, row hover/selected states, and the narrow column — matching the inbox panel's existing pattern. Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
eacc842280 | fix(agents): constrain builder models to runtime (#5323) | ||
|
|
abeee82cd0 | feat: animate numeric metrics with NumberFlow (#5317) | ||
|
|
1427e8abd3 | feat(agents): add conversational creation studio (#5296) | ||
|
|
3bd69bdaa2 |
MUL-4432: add DM button on agent detail page (#5289)
* feat(agents): add DM button on agent detail page The header gains a DM action next to Assign work. It shares the MUL-3963 invocation gate with assignment: allowed users land on the Chat tab with a fresh compose bound to the agent via a new one-shot ?agent=<id> deep link (consumed once the permission-filtered agent list resolves, then stripped); denied users get an explanatory toast instead of a hidden affordance. Closes MUL-4432 Co-authored-by: multica-agent <github@multica.ai> * fix(chat): harden the ?agent= deep link against races and undetermined permissions Review follow-up for the DM button (PR #5289): - An explicit user action (thread select, archive, manual new chat) now supersedes a still-pending ?agent= intent, so an intent deferred by slow agent/member queries can no longer fire late and clobber that choice. - The composingNew reset reads the live store value; under StrictMode's effect replay the render-captured snapshot re-closed the compose pane the intent had just opened (one-shot guard rightly refuses to re-fire). - A settled miss (revoked access, archived agent, bad id) now toasts and strips the param instead of silently keeping a re-fireable intent; still- loading queries keep the intent pending. Query errors never settle. - useAgentPermissions exposes isLoading: the detail page disables DM while membership resolves instead of toasting a false not_member deny at a legitimate member. The chat store test mock is now reactive (useSyncExternalStore) — with a plain mutable ref React bails out of committing post-effect re-renders and the StrictMode regression cannot reproduce. Both new regression tests fail against the pre-fix code. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
4ad060ec54 |
fix(settings): unify form control widths with SettingsRow size tiers (#5291)
Replace per-row controlClassName widths with a size enum (text / select-wide / select / code / none) on SettingsRow. Text-entry fields (name, slug, description, context, bio) now share the text tier so their control edges align within a card; deliberately-short fields (issue prefix) keep a clearly smaller tier. MUL-4428 Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
05d9298582 |
fix(agents): let workspace members view runtime capabilities (MUL-4427) (#5281)
* fix(agents): let workspace members view runtime capabilities (MUL-4427) The Agent capabilities redesign (#5277) reused the runtime local-skills discovery endpoint on Agent detail surfaces, but the endpoint kept the owner-only gate from the original import flow. Viewing an agent bound to someone else's runtime returned 403, which the Skills / MCP tabs rendered as 'try again when the runtime is online' even though the runtime was online. - Discovery (list + poll) now requires workspace membership only; the payload is the deliberately redacted inventory built for this display. - Import (init + poll) stays owner-only: it copies skill file contents off the owner's machine. - The failed notice no longer blames runtime connectivity, and a 403 (new client against an older backend) gets an honest permission message. * test(settings): stub Intl.supportedValuesOf in timezone picker tests The preferences-tab timezone tests drove a ~600-option Base UI Select through userEvent in jsdom; on slow CI runners the clear-preference case exceeded even its extended 20s per-test timeout (PR #5281 frontend job). Stub the IANA enumeration down to the curated COMMON_TIMEZONES fallback — everything the tests pick lives there too — and drop the now-unneeded 20s overrides. File test time drops from ~35s to under 1s. --------- Co-authored-by: Lambda <lambda@multica.ai> |
||
|
|
c377d7fb4f |
feat(labels): add scoped label management (#5279)
* feat(labels): add scoped label management * fix(labels): address review feedback * fix(migrations): use unique label migration prefix |
||
|
|
8a05af0342 |
fix(agents): pin capability/settings sub-nav while content scrolls (MUL-4426) (#5280)
The agent detail page wrapped both the sidebar rail and the content pane in one scroll container, so scrolling any Capabilities/Settings tab dragged the Instructions/Skills/MCP rail out of view. Split scrolling on md+ like settings-page.tsx: the outer container stops scrolling and each pane scrolls itself. Below md the page still scrolls as one, and the Overview/Work views keep whole-page scrolling. Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
3a382d92b3 | Refine agent access scopes in create flow and inspector (#5269) | ||
|
|
2affa34f76 |
feat(agents): two-level runtime picker on agent settings — machine, then runtime (MUL-4421) (#5276)
A machine-level rename stamps the same custom_name on every runtime of a daemon (MUL-4217), so the settings-page runtime dropdown rendered N indistinguishable machine-name rows. Split selection into two levels: pick the machine first, then a runtime on it, labelled by the runtime itself. Opening lands inside the selected runtime's machine, and the trigger now reads 'Claude · <machine>' instead of the bare machine name. Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
a14098288b | feat: redesign agent Skills and MCP capabilities (#5277) | ||
|
|
5541819f98 |
refactor(ui): unify collection page patterns (#5258)
* refactor(ui): unify collection page patterns * refactor(agents): redesign agent detail workbench (#5263) * refactor(agents): redesign agent detail workbench * refactor(agents): refine capability and settings surfaces * refactor(agents): rebuild general settings form * refactor(agents): refine overview and settings * refactor(agents): redesign custom args editor * docs(ui): record consistency audit |
||
|
|
8e0fbecab5 |
fix(models): codex empty-model effort validation + exact 5.6 aliases (MUL-4347) (#5196)
Follow-up to #5188 addressing the second-round review. - ValidateThinkingLevel now fails an empty codex model closed instead of borrowing the flagged Default (gpt-5.6-sol). An empty model follows config.toml, which can resolve to any installed model; Sol alone advertises `ultra`, so the old borrow green-lit levels Luna / gpt-5.5 don't support and Codex doesn't reject. Checked before ListModels so a discovery error can't fail it open. Frontend pickModelEntry mirrors this (no per-model effort preview for an empty codex model); the persisted-orphan clear path stays. - parseCodexDebugModels drops efforts without a known label so the picker never advertises a level the Create/Update enum gate would 400 on save; the contract test now drives the real parser with an unknown effort instead of comparing two hand-written maps. - gpt-5.6 price aliases anchor to a literal dot (not the [.-] class), so dashed variants like gpt-5-6-luna surface as unmapped on both backend and frontend rather than silently borrowing a tier. Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
f4de0948a2 |
refactor(ui): unify ActorAvatar size tiers + round all avatars & cropper (MUL-4277, MUL-4184) (#5133)
* refactor(ui): converge ActorAvatar size to semantic tiers (MUL-4277) Replace the free-form numeric `size` on ActorAvatar with a constrained `AvatarSize` union (xs/sm/md/lg/xl/2xl) so avatar dimensions are chosen by role instead of ad-hoc pixels. This eliminates the magic-number drift where the same role rendered at different sizes across pages. - Add `@multica/ui/lib/avatar-size` (AvatarSize union + AVATAR_SIZE_PX map + default tier). - Base `ActorAvatar` (packages/ui) and business `ActorAvatar`/`AgentStatusDot` (packages/views) now take `AvatarSize`; internal font/icon math and the presence-dot threshold read px from the map. - Migrate all web/desktop call sites (packages/ui + packages/views) from numeric sizes to tiers using the role table (12,14->xs 16,18,20->sm 22,24,28->md 30,32,34->lg 40,44->xl 56,64->2xl). - Token-ise the derived consumers `AgentAvatarStack` and `IssueAgentActivityIndicator` (px looked up internally for overlap/+N math). Out of scope (per plan): ui/avatar.tsx primitive, account-tab/AvatarPicker, mobile, and component-name disambiguation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(ui): unify all avatars and the upload cropper to round (MUL-4277, MUL-4184) main's avatar-shape decision rendered non-human actors (agent, squad, system) and the workspace logo as rounded squares, and the upload cropper mirrored that with a square crop window. Per the updated decision (avatars_and_cropper_round_required), every avatar and the crop UI are now circular; the square path is removed rather than left as dead config. - Base ActorAvatar: always rounded-full (drop the isHuman/rounded-md split). - avatar-crop-dialog: remove the AvatarCropShape/square path; crop window is always cropShape="round". - avatar-upload-control: drop VARIANT_SHAPE; the control is always round and no longer threads a shape to the dialog (variant still drives the fallback). - Strip rounded-md/rounded-none square overrides from agent/squad/member ActorAvatar call sites; round the read-only agent/squad static wrappers. - WorkspaceAvatar: round the org logo so it matches the (now round) workspace upload/crop and the shared avatar shape. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(ui): make round avatar shape a hard invariant (MUL-4277) Close the two remaining square squad-avatar paths flagged in review and prevent call sites from re-squaring the avatar: - base ActorAvatar: keep `rounded-full` as the last class in cn() so a call-site `className` can no longer override the circle. - SquadHeaderAvatar: drop `className="rounded"` (was overriding the base circle into a small rounded square). - SquadsPage no-avatar fallback: route through the shared ActorAvatarBase (`isSquad size="lg"`) instead of a hand-written rounded-md tile, so the fallback matches the image path — one shape source of truth. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(views): round the agent/squad avatar loading skeletons (MUL-4277) The avatar placeholder skeletons on the agent/squad list, detail, and profile-card loading states were still rounded squares (rounded-md/lg) from the pre-round era, so the avatar visibly popped from square to circle on load — inconsistent with the round avatars and with the member/inbox/issue skeletons that already use rounded-full. Round all five: agents-page, agent-detail-page, squads-page, squad-detail-page, squad-profile-card. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
c6783efd88 |
feat(views): unify avatar upload with crop editing (#5074)
* feat(views): unify avatar upload with crop editing across web/desktop Add a shared AvatarUploadControl + AvatarCropDialog used by the user, workspace, agent, and squad avatar entry points. Cropping (pan/zoom, fixed 1:1) and compression run client-side on canvas; the existing /api/upload-file + avatar_url chain is reused unchanged (no backend/API/DB changes). This collapses four hand-rolled upload buttons into one control and removes AvatarPicker. Also make the shared display avatar treat all non-human actors (agent, squad, system) as rounded squares — completing the "circles are for humans" convention the editors already assumed, so display and editors agree. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(views): rebuild avatar cropper to the "Edit avatar" reference form Replace the hand-rolled canvas cropper with react-easy-crop to match the requested design: full-bleed image with a dimmed overlay outside a bright crop window, a rotate control, a zoom slider flanked by −/+, and a Reset / Cancel / Save footer. Round window for people, rounded-square for non-human actors; output stays a 512px square (webp, jpeg fallback) through the same upload/avatar_url chain. avatar-crop.ts keeps the encode pipeline and gains rotation-aware getCroppedAvatarBlob; the interactive geometry now lives in react-easy-crop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(views): round square avatar crop frame --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
f8c4c88129 |
feat(agents): show runtime model + effort on agent hover card [MUL-4280] (#5117)
The agent profile hover card now surfaces the runtime-native model id (mono, e.g. `claude-opus-4-8`) with the reasoning/effort token as a badge, so a quick hover answers "which model is this agent running?" without opening the detail page. Empty model renders a "Runtime default" placeholder. Also fixes the Skills row: chips wrap to multiple lines, so the vertically centered label drifted to the middle chip — it now pins to the first chip row (items-start + pt), and each chip truncates so a long skill name can't blow out the card width. The effort badge is gated on `effort` alone (not `hasModel`): an agent with no pinned model can still persist a thinking_level override that applies at run time, and hiding it would misreport the agent's real config. Adds agent-profile-card.test.tsx covering the model/effort render states, including the `model:"" / thinking_level:"high"` regression. New i18n: profile_card.model_label / model_unset (en/zh-Hans/ja/ko). Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |