Repository navigation
MUL-7816 fix(taskfailure): classify a 403 usage limit as provider quota, not auth - #8967
Conversation
Kimi Code reports an exhausted usage window as HTTP 403. The bare 403 auth rule ran before the quota rule, so the failure was filed as provider_auth_or_access. Match the usage-limit witness before the auth rule. The concurrent-request-limit case still comes first, and a plain 403 still classifies as auth. Fixes multica-ai#8965
|
@drakeo338 is attempting to deploy a commit to the IndexLabs Team on Vercel. A member of the Team first needs to authorize it. |
Bohan-J
left a comment
There was a problem hiding this comment.
Thanks for the focused fix. The diagnosis is right: on current main, API Error: 403 You've reached your 5-hour usage limit lands in agent_error.provider_auth_or_access because the bare 401/403 rule runs before the quota rule. Keeping this PR classification-only is the direction we want. Two things need to change before merge.
1. Older daemons keep reporting auth. Classification runs on the daemon, and the server only reclassifies when the reported reason is empty (FailTaskWithTransition). A daemon built before this PR sends a non-empty agent_error.provider_auth_or_access, which the server persists as-is, so users see no change until their daemon upgrades. Please add a matching rule to NormalizeDaemonReason, the way #8200 did for the concurrent-request-limit case.
2. The Failed to refresh access token. prefix still misclassifies. The existing concurrent-request-limit fixtures show Claude Code prefixing 403s with Failed to refresh access token.. For Failed to refresh access token. API Error: 403 You've reached your 5-hour usage limit, the text contains both "token" and "limit", so rule 1 (context overflow) claims it before the new case runs. On this branch it is classified as agent_error.context_overflow.
Suggested shape. I prototyped it on this branch and all existing pkg/taskfailure tests still pass:
var httpForbiddenCodeRe = regexp.MustCompile(`(^|[^0-9])403([^0-9]|$)`)
// isUsageLimit403 is shared by Classify and NormalizeDaemonReason so new and
// old daemons land on the same reason.
func isUsageLimit403(lower string) bool {
return httpForbiddenCodeRe.MatchString(lower) && strings.Contains(lower, "usage limit")
}- In
Classify, check it right after theconcurrentRequestLimitWitnesscase, before rule 1. - In
NormalizeDaemonReason, right after the concurrent-limit rule, upgrade the same legacy set (context_overflow,provider_auth_or_access,unknown,agent_error) toprovider_quota_limitwhen it matches.
Requiring the 403 keeps the change scoped to the conflict this PR is about. Other "usage limit" wordings stay where main puts them today; for example, a 429 ... tokens-per-minute usage limit message is unaffected.
Tests to add: the access-token-prefixed variant, old-daemon provider_auth_or_access → quota and context_overflow → quota upgrades, and a plain 403 that stays auth.
…ken rule Move the usage-limit check into a shared isUsageLimit403 helper that requires HTTP 403. Classify checks it right after the concurrent-request case, so an access-token-prefixed 403 usage limit no longer lands in context_overflow. NormalizeDaemonReason upgrades the same message from older daemons (context_overflow, provider_auth_or_access, unknown, agent_error) to provider_quota_limit.
|
Both points are done with the shared |
multica-eve
left a comment
There was a problem hiding this comment.
Thanks @drakeo338 for the focused fix and for addressing both review points. The shared 403 usage-limit check now handles the access-token prefix and corrects classifications reported by older daemons, while preserving plain 403 authentication errors and concurrent-request-limit precedence.
The updated code and regression coverage look good. Approved; CI has been authorized to run before merging.
Both requested changes are resolved in adccbc8; the updated implementation has been reviewed and approved.
* MUL-7755: feat(issues): configurable side peek in every issue view (#8886)
* feat(issues): add a peek variant to IssueDetail
A single-column layout for the board's side peek: the core properties
render as a row of pills under the title (the create dialog's PillButton
with the sidebar's pickers), the properties sidebar and its toggle are
dropped, and the host supplies the header's leading and trailing
controls. The peek keeps its own scroll key and always opens at the top.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(issues): open board cards in a side peek on Shift+Click (MUL-7755)
Shift+Click (or Space on a focused card) opens the issue in a floating
panel over the board, so it can be triaged without leaving the board.
Shift+Click another card switches the panel; on the same card, or Esc,
it closes. J / K step through the card's column, the peeked card keeps
a brand ring, and the board scrolls it clear of the panel. The page
header and toolbar stay usable, and the panel stops above the chat
launcher.
On web, Shift+Click on a board card no longer opens a browser window;
Cmd/Ctrl(+Shift)+Click still open tabs. Board and swimlane only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(issues): animate the side peek and peek the hovered card on Space
The panel now enters and exits through motion/react with the shared UI
easing: a 200ms fade and 16px slide in, a shorter 150ms fade and 8px slide
out, fade-only under reduced motion. The key is stable, so switching
issues (J / K, Shift+Click another card) swaps content without replaying
it, and the exiting panel stops taking clicks and keys at once. The
sideways board scroll that keeps the peeked card in view also respects
reduced motion.
Space now peeks the card under the pointer as well as a focused card,
unless a control has focus and owns Space itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(issues): side peek in every issue view, with H / L and arrow keys
Shift+Click (and Space on the hovered issue) now opens the side peek from
list rows, table rows, gantt rows and swimlane cards as well as the board.
The host wraps every view and its loading states, so an open peek survives
a view switch; each view publishes its own order:
- board: its columns; swimlane: one column per status, running down
through the expanded lanes; list, table, gantt: one column in display
order, skipping collapsed groups.
H / L step to the nearest non-empty column at the same row (clamped), J / K
within it. The arrow keys mirror them unless the reader last clicked into
the panel, where they scroll it as usual; tabs, radios and sliders keep
their own arrows.
Peeked rows get a brand tint and leading bar; table cells carry it because
pinned cells paint over the row. DataTable gains a generic getRowProps for
that. The swimlane scroller reserves room for the panel like the board's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(issues): keep the peek closable in every state, and follow moved cards
Review of #8886:
- The peek's close button lives in IssueDetail's trailing controls, which
the loading and not-found early returns dropped, leaving only Esc. Both
states now render the host's trailing controls, and the panel's error
boundary gets a fallback with its own close button.
- Keep-in-view keyed on the peeked card's neighbours, so a card alone in
its column that moved to an empty column never scrolled back into view.
It now acts whenever the element standing for the issue is a new one
(another issue, a column move, a view switch) and still ignores reorders
that leave it in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(issues): make default card opening configurable (MUL-7755)
Co-authored-by: multica-agent <github@multica.ai>
* feat(settings): move the issue-opening preference into the new Preferences page
Main folded the Preferences tabs into one page (MUL-7732), deleting the
issue tab the preference had been added to. It now lives there as its own
"Opening issues" section, in the page's own terms:
- device scope badge, like Appearance, since the choice is stored per device
- the either/or SegmentedToggle Theme uses, instead of a two-item dropdown
- no success toast: the new page announces only failures
- a settings-search entry, also matched by its option names
Clicks now mean "the other one" with Shift: a plain click opens the
preferred target, Shift+Click the other. In side-preview mode that makes
the full page one click away again (before, Shift also peeked, leaving only
the panel's button or a new tab); a link would otherwise hand Shift to the
browser, so the hook navigates in place itself. The row's hint says so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* MUL-7680: issue wakeup v2 — conditions, runaway protection, check-ins and visibility (#8807)
* feat(wakeups): add rule deadlines and expose the child-done system rule (MUL-7680)
Wakeups can now end: an absolute deadline (expires_at) or a relative wait
(expires_in_seconds) that restarts when the rule is re-enabled. When the
deadline passes first, the scheduler ends the rule; event rules with
on_timeout=wake run the target once with a wakeup.timeout fact. A timed-out
rule keeps disabled_at NULL so its timeout run stays claimable.
The implicit "wake the parent's assignee when a stage of sub-issues
finishes" behavior is now described by GET /api/issues/{id}/system-wakeups
and can be turned off or given a supplementary instruction per issue via
PUT /api/issues/{id}/system-wakeups/child_done. Rule reads fail open to the
previous behavior.
List responses add the creator (member or agent) and the new expiry fields.
The CLI gains --expires-in, --expires-at and --on-timeout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(issues): let members create wakeups and show the child-done system rule (MUL-7680)
The issue sidebar's Wakeups section is always available on open issues and
gains a New wakeup popover. Members pick a condition (a time, a recurring
check with an end date, someone's reply, an agent's run ending, or raw
events), the agent to wake, the instruction, how often it fires, how long
to wait and what happens on timeout. It posts the same configuration agents
create with the CLI.
The parent's child-done wake appears as a System rule with its stage,
remaining sub-issues and target, a per-issue toggle and a supplementary
instruction. Rows show when a rule ends, timed-out rules read as such, and
details name who created the rule. Rule titles wrap to two lines instead of
truncating.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* docs(wakeups): document deadlines, member-created rules and the system rule (MUL-7680)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(wakeups): platform conditions, runaway protection and check-ins (MUL-7680)
Conditions: a wakeup can now wait for a fact the platform checks itself
(a status, assignee, label or property value; sub-issues or one stage
finishing; a linked PR's checks finishing or merging; another issue's
status). Related events only prompt an early evaluation; the rule wakes
its agent when the predicate becomes true, and a repeating rule fires
again only after it turned false or its facts changed. Finished PR checks
from before registration are ignored.
Runaway protection: repeating event rules stop after max_fires runs
(default 20), a rule pauses when its causal chain passes through it a
third time without a person in between, or when it starts 12 runs in an
hour. The reason is stored and shown.
Also: silent check-ins for every/cron checks (the run then posts no
fallback comment), wake now, delete, a per-rule run history, paused-rule
lists, timeline entries for created/triggered/timed-out/paused/check-in,
and source, paused scope, 7-day runs and child-done system rows in the
workspace list. The CLI gains --until-* conditions, --max-fires and the
trigger/delete/checkin/runs commands; the brief and wakeup prompt state
the check-in exception.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(issues): record child-done transitions in the status write (MUL-7680)
The child-done system rule ran after the status write committed and gave
up on any error, so a crash, deploy or transient failure lost the
parent's wake. A trigger now records each child's move into a closed
status in the writing transaction, for every writer. The request that
wrote it processes the rows right away and a scheduler sweep retries
anything left over; claims make the two paths process a transition
once.
The rule also follows a workspace default (settings key
system_wakeup_child_done) until an issue sets its own, the per-issue
update accepts partial bodies, and each trigger adds a timeline entry
that names its system comment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(views): wakeup conditions, history, timeline and workspace list (MUL-7680)
Issue sidebar: members can create the platform-checked conditions (a field
reaching a value, sub-issues or a stage finishing, a linked PR's CI or merge,
another issue's status) and cap how often a repeating wait fires. Rule
details show the fire count, why the platform paused a rule, the latest runs
with silent-check notes, and offer wake now and delete.
The issue header says what the issue is waiting for ("Emacs 在等 Jiayuan 回复
+2") and opens the Wakeups section. Board and list cards use the same short
sentence, or flag a paused rule. Wakeup runs read as "由唤醒触发 · <condition>"
in the execution log, transcript and on the comments they post.
The timeline shows created, triggered, timed-out, paused and check-in entries
with the rule they came from; consecutive check-ins merge, and the child-done
entry stands in for its system comment, which it can reveal.
Automation → Issue wakeups adds a source column and filter, 7-day runs, a
paused scope with a banner, the child-done system rule on each waiting parent,
and "New wakeup" with an issue picker. Settings → Issue statuses sets the
system rule's workspace default. Copy in all five languages.
The server records the watched issue's identifier in other-issue conditions
and the rule's creator in timeline snapshots, and returns board summaries with
their condition.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* docs(wakeups): document conditions, runaway protection and check-ins (MUL-7680)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(wakeups): list system rules without a revision and localize statuses (MUL-7680)
System rule rows returned revision 0, which clients reject as a rule
revision; they now return null, and a list row with an invalid revision no
longer fails the whole page. Built-in statuses in conditions and the status
picker read in the viewer's language. Adds a browser test for a condition
created from the form, the header summary, the scheduler waking the agent
and the workspace list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(views): say why a rule paused without repeating "paused" (MUL-7680)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(i18n): use the product's task terms in wakeup copy (MUL-7680)
ja, ko and fr wakeup strings said イシュー / 이슈 / ticket; the product term
for an issue in those languages is タスク / 태스크 / tâche (and sub-tasks
accordingly), per the conventions page. French strings are rewritten for
the feminine noun.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* test(views): pin the shortcut platform in the wakeup form test (MUL-7680)
The send shortcut is primary+Enter, which is Ctrl on Linux CI runners, so
pressing Cmd+Enter only submitted on macOS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(views): simplify the workspace wakeup list
Put the filters on one row: scope as a segmented control on the left,
source/trigger/agent and a search-as-you-type box on the right. "New
wakeup" moves to the page header, which drops the separate Search button.
Rows are single-line: the identifier sits before the title, frequency and
timezone move into the trigger tooltip, and selection, prompt edit and
the last run's transcript show on row hover or focus. The issue column
takes the remaining width and truncates, so the table fits the page.
Also fix creating from the list: the issue picker closes itself right
after reporting the selection, and that close cancelled the flow before
the form could open.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* refactor(server): run the child_done rule as a system issue wakeup
The parent-assignee wake on closed sub-issue stages was its own code path:
a system comment, a mention-style run, an override table and endpoints.
It is now an ordinary issue_wakeup row with system_rule set, sharing the
condition, receipts, runaway protection, timeline entries and run model of
people's rules.
- One children_done evaluator: a stage wakes the assignee when it closes
while a later stage waits; the wrap-up waits for every sub-issue,
unstaged ones included. People's sub-issue conditions read the same set.
- issue_child_event records closing, reopening, re-parenting and restaging
for every writer; the request processes it right after commit and the
sweep retries. User sub-issue rules become immediate too.
- The target is resolved when it fires: an agent run, a squad leader run,
an inbox notification for a member, or only the timeline entry.
- A parked (backlog) parent catches up when it leaves backlog; a waiting
run of the same agent is joined instead of queuing a second one; the
hourly limit pauses a runaway rule. No system comment is posted.
- The run gets the issue's instruction, else the workspace's, else the
built-in one, plus each stage's counts and the next stage.
- Workspace defaults move to GET/PUT /api/system-wakeups (owners and
admins) and apply to every rule nobody customized; turning a rule on
records what already holds so old facts do not fire.
- Existing open parents get their rule from a one-time backfill.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(views): workspace wakeup settings and sub-issue notifications
- Settings → Wakeups holds the sub-issue rule's workspace default and
default instruction; the toggle leaves Issue statuses.
- The issue's system rule shows who it reaches (a member is notified),
a platform pause, and the instruction it will use.
- Timeline entries say whether the assignee was woken, notified or joined
a waiting run. The folding of the old system comment is gone.
- Inbox renders the new children_done notification on web, desktop and
mobile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(server): stop wakeups from repeating their agent's runs
Runs are serialized per issue and agent, so a wakeup never ran beside
another run of its agent, but it queued behind one and repeated it. Each
rule now checks two things before it starts a run:
- Acknowledged: every input came from the agent itself. Its own comments
and issue changes never wake it, and a condition its own unfinished run
on the issue satisfied does not wake it when the platform or the agent
set the rule up. This fixes #8849: a coordinator closing its own stage is
not woken again while that run is going. A person's condition rule still
runs afterwards, since the running agent lacks its instruction.
- Merged: when a run of the agent is already waiting to start on the issue
(assignment, comment, another rule), the rule's instruction and facts
join that run (context.wakeup_joined, sent to the daemon as
wakeup_joined and rendered for every prompt kind) instead of queuing
another. Turning the rule off or replacing it withdraws what it joined.
Sub-issue changes now record the run that made them, and hints and
condition.met inputs carry those runs so a satisfied condition knows
whether the agent caused it. A rule that names the agent itself as the
actor keeps waking it on its own changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(views): show merged and acknowledged wakeups, hint assignee comment rules
- Timeline entries say when a wakeup joined the agent's waiting run or was
the agent's own doing, for people's rules and the sub-issue rule.
- The create form tells a member that a reply or comment rule for the
issue's agent assignee joins the run a comment already starts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(server): hand waiting wakeups to a run when it is claimed
A rule that fired while its agent had a run waiting used to write its
instruction into that run right away and consume its inputs. That let a
member's rule run under another member's identity, kept deleted rules'
instructions in the run, lost the inputs when the run was cancelled or
claimed by an older daemon, and skipped the fire cap and loop check.
Now the rule only waits for a run that runs as the same person its own
run would, keeping its inputs. When a daemon advertising
joined-wakeups-v1 claims that run, JoinWaitingWakeups rechecks each rule
(on, same agent and person, creator still allowed, not the agent's own
input, no loop), consumes its inputs, counts the fire toward max_fires,
adds its chain and records the merge. A re-claim drops entries of rules
turned off or changed since. Anything else leaves the rule its inputs to
start its own run.
A child_done rule created by the backfill or by processing a change now
treats sub-issues with unprocessed close events as still open, so the
backfill no longer swallows a close waiting for the sweep.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(server): consume joined wakeup inputs only once the run starts
A claim used to consume the inputs of the rules that joined the run. If
that claim did not go through, the run went back to the queue carrying
inputs the rules no longer had: turning the sub-issue rule off on the
issue or for the workspace (which only changes enabled) did not remove
them from the next claim, and cancelling the run lost a once rule that
had already ended.
A claim now reserves the inputs for the run (receipt task_id, still
unprocessed). The rule's next dispatch settles them: a run that started
consumes them as a merged firing (once ends, max_fires counts and can
pause, timeline entry); a run that ended without starting gives them
back; one that has not started keeps them. Turning a rule off or
changing it discards its pending inputs, reserved ones included, and a
later claim of the same run rechecks every entry and drops the ones
without reserved inputs or no longer allowed to reach the run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* MUL-7754: feat: local search index for web and desktop (#8891)
* feat(server): add a change log and sync endpoints for local search (MUL-7754)
Web and Desktop will keep a per-workspace copy of issue, comment, and
project text for instant search. Triggers on issue/comment/project record
the xid of each entity's latest write in search_index_change, covering
every write path including hard and cascaded deletes. Clients bootstrap
with GET /api/search-index/manifest + /snapshot pages and catch up with
POST /api/search-index/changes, whose cursor is a pg_snapshot: a change is
returned once it is visible in the target snapshot but not in the one the
client holds, so late commits are never skipped and long transactions do
not stall catch-up.
Workspace teardown skips the triggers and clears the table, and an hourly
scheduler job prunes rows older than 30 days; a cursor older than the
prune mark gets 410 and rebuilds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(core): add a synced local search index for web and desktop (MUL-7754)
Each (user, workspace) gets an IndexedDB copy of its issue, comment, and
project text, kept in a worker (SharedWorker when available, otherwise a
dedicated worker per tab) and searched in memory. Matching, ranking, and
snippets port the server's issue/project search, so local rows have the
server's shape and order.
The worker bootstraps from /api/search-index/manifest + snapshot pages,
catches up through /changes after realtime events, reconnects, focus, and
every five minutes, and resumes an interrupted bootstrap. It has no
credentials of its own: tabs run its requests through their API client.
Local results are served only while the copy is complete and recently
confirmed; otherwise searchIssues/searchProjects fall back to the server.
Workspaces over 500 MB of text, lost access, and expired cursors are
handled by declining, wiping, and rebuilding respectively. Session cleanup
deletes every local index.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(views): search through the local index first (MUL-7754)
The command palette, @mention suggestions, the issue picker, and duplicate
suggestions now call the core local-first search. The palette drops its
300 ms debounce while the local index is serving, since no request leaves
the tab.
An E2E spec checks that palette results match server ranking without any
server search request, follow issues created and deleted through the API,
and that logging out deletes the local copy. .env.example documents the
FF_LOCAL_SEARCH_INDEX kill switch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(core): close local search gaps found in review (MUL-7754)
- Access loss: deleting, leaving, or being removed from a workspace now
destroys its local copies right away (worker and tab both delete the
database, since a full-page relocate may end the worker first). The sync
hook also prunes every copy outside the user's current workspace list,
which covers access lost while offline or on another device; copies a
tab is showing are kept in case the list is stale.
- Memory budget: the engine counts UTF-8 bytes the way the manifest does,
and the budget is checked on every load, snapshot page, and catch-up
batch, not only at bootstrap. An over-budget copy is dropped and search
falls back to the server until the workspace is measured again.
- Frozen tabs: a port the sweep dropped is restored with its workspace the
next time the tab sends anything, a page restored from the bfcache
attaches again, and a null local answer clears the tab's "serving" flag
so the palette goes back to debounced server search.
E2E now also covers being removed from a shared workspace.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(agent): pass Hermes custom args before the acp subcommand (MUL-7748) (#8882)
* fix(agent): pass Hermes custom args before the acp subcommand
Hermes only accepts its global flags (--provider, --yolo, -m, ...) ahead
of a subcommand, so the daemon's `hermes acp <custom args>` exited with
an argparse usage error before the ACP handshake and every run of a
Hermes agent with custom_args failed. Assemble `<prefix> <custom args>
acp` instead, keep the profile resolver and overlay stripping on the
same argv, and drop a trailing custom arg left without its value so it
cannot capture `acp` and hang the task in interactive chat.
Fixes #8878 (MUL-7748)
Co-authored-by: multica-agent <github@multica.ai>
* fix(agent): keep acp's own flags after the Hermes subcommand
Hermes' acp subparser declares --accept-hooks, --version, --check,
--setup, --setup-browser and --yes/-y, and argparse only accepts them
after the subcommand: moving them ahead of `acp` turned an agent with
custom_args ["--yes"], which launched before, into a usage error.
Lay custom args out around `acp` instead — global flags (with their
values) before it, acp's own flags after it — and map profile
stripping back through that layout so custom args keep their
configured order.
Co-authored-by: multica-agent <github@multica.ai>
* fix(agent): resolve Hermes flag abbreviations like argparse
Hermes keeps argparse's allow_abbrev, so custom args must be classified
the way its parsers resolve them, not by string equality. Behind `acp`
a `--y` is `--yes`; moved in front, the root parser reads it as
`--yolo` and turns off dangerous-command approval, while `--ye` went
from valid to a usage error. Keep every token the acp subparser would
take as its own option (exact, abbreviated or ambiguous) behind the
subcommand, and treat abbreviated value flags (`--prov`) as value flags
when pairing and when guarding against a bare flag capturing `acp`.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>
* MUL-7710 fix(channel): refuse an inbound turn from a member who may not run the agent (#8838)
* fix(channel): refuse an inbound turn from a member who may not run the agent
The web chat checks the agent's invoke permission before it opens a session: a
private agent runs only for its owner, with no admin bypass. The shared channel
Router checked the sender's binding and workspace membership and nothing else,
so any member who had bound a chat identity could run another member's private
agent through its bot — on the owner's runtime, with the owner's credentials. A
1:1 message, a group mention, /issue and /new all got through, on every channel
the engine serves.
The Router now asks service.CanMemberInvokeAgent right after identity
resolution and before anything is stored, so a refused turn reaches no Chat, no
/issue and no later run's context. It judges the SENDER, never the installer who
owns a group's route — judging the installer is what would let this through.
MemberMayInvokeAgent is that existing function keyed by agent id, since the
Router holds the installation's agent id rather than the loaded row. An agent
that no longer exists admits nobody; a lookup that FAILED is an error rather
than a denial, so the dedup claim is released and the platform's redelivery is
still the message's chance.
A refusal is audited as invocation_not_allowed. WeCom answers a 1:1 in place; a
group trigger is answered in the sender's own 1:1 and the room hears nothing,
since a line there would tell everyone present which member was refused and
that the agent is someone's private one. Other channels stay silent, as they do
for a non-member.
* fix(channel): a failed permission lookup is not a denial
The policy swallowed two of its three query errors. CanMemberInvokeAgent
returns a plain bool, so ListAgentInvocationTargets failing came back as false,
and any error from GetMemberByUserAndWorkspace — not only pgx.ErrNoRows — read
as 'not a workspace member'. Only GetAgent's failure reached the caller.
For the scheduled triggers that shipped with it, failing closed is right: an
autopilot or an issue wakeup can wait for the next tick. For a channel turn it
is not. The Router reads false as a verdict, marks the message processed so the
platform's redelivery is discarded, and WeCom tells the sender they may not run
an agent they are in fact allowed to run — all from one transient error.
So the policy is now memberMayInvokeAgent, returning (bool, error):
- (false, nil) stays a real verdict — no user id, not the owner of a private
agent, not a target of a public_to one, and a member row that is simply not
there, which is what pgx.ErrNoRows means here.
- (false, err) is 'we do not know', and every other query failure produces it.
CanMemberInvokeAgent is now a thin wrapper that fails closed, so autopilot and
issue wakeup behave exactly as before. TaskService.MemberMayInvokeAgent returns
the error, and the Router releases its dedup claim on it.
TestRouter_RealServiceLookupFailure_ReleasesInsteadOfDenying drives the REAL
TaskService against a real database with one query failed at the SQL boundary,
because the router tests stub this at the interface and can only prove what the
Router does with an error it is handed — not whether the policy ever produces
one. It asserts Release rather than Mark, no invocation_not_allowed audit, and
no denial notice, for each of the two queries.
Also moves postPrivate's doc comment back to postPrivate: sendInvokeDenied's
was inserted above it, leaving one function documented by the other's text.
* MUL-7758: lay an issue's runs out in time (#8890)
* fix(issues): render run trigger snapshots as plain text (MUL-7758)
A run's trigger snapshot is the comment's raw Markdown cut at ~200 runes,
and the execution log printed it verbatim: an escaped ampersand read
"&", and a comment that opens with a screenshot read
"![CleanShot 2026-…" because the cut lands inside the image URL.
Decode the entities in one pass, turn whole or cut images into a localized
"[Image]" label, and keep links' text, so every surface that lists runs
shows what the person actually wrote.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(issues): lay an issue's runs out in time (MUL-7758)
The usage breakdown was a nine-column table of raw token counters headed by
a token total that is 99% cache re-reads and a "cache savings" figure larger
than the spend itself, with no way to tell when a run happened or to open
it. The execution log showed tokens where the question is cost, and kept
every past run behind a "Show past runs" toggle.
Execution log (sidebar):
- the header reads "21 runs · $166" and opens the new Runs dialog; narrow
panels drop the count, never the cost's digits
- a spend strip draws one bar per past run, oldest to newest, height =
cost, with a baseline dot for runs that recorded no usage
- agent time and elapsed time sit under it, active runs stay on top, the
latest three past runs are listed with their cost, and "Open timeline"
hands the rest to the dialog
Runs dialog (replaces the usage breakdown):
- spent / agent time / elapsed / runs, with failed and cancelled counts
- a cumulative cost curve over per-agent run lanes on one time axis, the
run that moved the total most labelled, hover summaries on every bar
- runs grouped by day, newest first: who asked (quoted), which agent ran
it, how long, a cost bar split by what was billed with the exact split on
hover, and transcript / retry actions
- runs without usage are listed with "—" instead of being left to a
footnote, so the run count no longer disagrees with itself
The timeline arithmetic lives in issue-run-timeline.ts with its own tests;
retry moves to a shared RetryRunButton so both surfaces retry the same way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(issues): open a run's transcript from its row (MUL-7758)
A Runs row cut its trigger to one line and opened the transcript only from
a small icon at the row's end, so a long ask could not be read anywhere
and the obvious click did nothing.
The whole row now opens that run's transcript; the trigger text is the
row's real button for keyboard and screen readers, and retry stays its own
action. The transcript dialog gains a "Trigger" line under its header that
shows the ask whole and wrapped, for every surface that opens it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* revert(issues): open Runs rows' transcripts from the hover action again (MUL-7758)
Row-wide clicks were a response to the transcript icon looking permanent;
it only appears on hover, which reviewers found enough. Restore the
per-row icon and retry actions. The trigger keeps its full-text native
tooltip, and the transcript dialog keeps its Trigger line, so a truncated
ask is still readable in full.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* feat(issues): make the Runs chart answer the pointer anywhere (MUL-7758)
Only the lane bars carried hover summaries, and most are a few pixels
wide, so the chart read as static: hovering the curve did nothing.
The plot column now snaps the pointer to the nearest run and shows a
crosshair at its completion, the curve's reading there, and a card with
the ask, agent, time, duration, the run's cost and the total so far; the
other lane bars dim. Each run carries `costSoFar`, and
`nearestRunIndex` does the snapping, both with helper tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(issues): reach nested runs and open cost splits without hover (MUL-7758)
Addresses the two review findings on the Runs dialog:
- Overlapping runs: the chart snapped by time alone and broke distance ties
by start order, so a run fully inside a longer one could never be picked,
even with the pointer on its bar. The innermost bar now wins a tie, and a
pointer over a lane only considers that agent's runs.
- Cost split: the input / output / cache breakdown lived in a tooltip on a
non-focusable span, out of reach for keyboard and touch. The cost cell is
now a button that opens a popover on hover, click, tap or Enter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(issues): use radius tokens for the Runs chart marks (MUL-7758)
CI's radius check rejects fixed arbitrary radii outside its allowlist. The
lane bars, legend swatches and spend-strip bars used rounded-[2px]; they
now use rounded-xs (3px), the same step the transcript's run timeline
marks already sit on, rather than widening the allowlist.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(wakeup): scope test scheduler passes to their own workspaces (#8901)
CI runs every backend package concurrently against one database, and
IssueWakeupService.Tick dispatches every ready wakeup in it. The handler
child_done tests' runWakeupTick therefore dispatched rules owned by
concurrently running service tests, consuming their pending receipts
mid-assertion: TestIssueWakeupPendingEventsAreBoundedAndLegacyInputsDrain
failed on main with "unbounded pending receipts: 1" (reproduced locally
2/40 with the handler tests running alongside, 0/20 alone).
Give ListReadyWakeups an optional workspace filter and add
TickWorkspaces for tests; production Tick passes no filter and is
unchanged. Scope the handler helper, the busy-rule test and the expiry
candidate check to the workspaces they own.
Co-authored-by: J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
* fix(views): stack wakeup condition hints under their labels (MUL-7769) (#8899)
The condition menu put each label and its hint side by side in a
22rem popup. English and French strings are too long for that row, so
labels and hints both wrapped and misaligned. Stack the hint under the
label, top-align the icon with the first line, and narrow the menu to
w-72 so every locale lays out the same way.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(views): keep the PR row diff whole and spin the running-checks icon (MUL-7767) (#8900)
* fix(views): keep the PR row diff whole instead of cutting it mid-number
When the verdict pill needed the room, the diff after `repo#number` was
clipped by `overflow-hidden`, so `−486` showed as a bare `−` and `+312`
could read as `+31`. Everything after the dot is now one unit that wraps
onto a clipped second line when it doesn't fit, and the repo name is only
shown when the linked PRs span repos, so the diff fits at the narrowest
sidebar width in the common case. The tooltip keeps owner/repo#number.
MUL-7767
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(views): spin the running-checks icon in the PR verdict pill
The loader icon on a PR whose checks are running stood still. It now
spins (motion-safe), and stops when the snapshot is stale, since a stale
snapshot can't vouch that the checks are still running.
MUL-7767
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* MUL-7766: feat(editor): fit Mermaid previews to show the whole diagram, with inline zoom (#8898)
The inline Mermaid preview now opens fitted: the whole diagram within the
column's width and at most 448px tall (the frame's collapse threshold less
padding), never upscaled past natural size. Zoom out / percent / zoom in /
fit buttons step from there; a zoomed diagram scrolls inside the same box
and can be drag-panned both ways. Tap still opens the full viewer.
- New useInlineZoom hook: measures the column, keeps a fitted preview
fitted on resize, snaps through 100%, and zooms about the visible center.
- The SVG fills its iframe, so zoom resizes the iframe and redraws vectors
instead of scaling a bitmap.
- Framed blocks get a floating pill bottom-right; the standalone editor
preview puts the controls in its existing toolbar.
- The layout cache records the fitted height so the lazy shell reserves the
space the preview actually takes.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* MUL-7706 feat(wecom): every notice reads the language of whoever will read it (#8831)
* feat(wecom): every notice reads the language of whoever will read it
The adapter's user-visible strings were Chinese literals scattered across
replier.go, inbox_message.go and outbound_media.go, so an English-reading member
got Chinese notices whatever their profile said. copyPack already existed for
the bubble's five stream strings; this moves the rest into it and resolves the
locale from the destination rather than the deployment.
Who decides, per surface:
- a 1:1 reply, the binding prompt, the inbox card: the reader's own profile
- a group notice: the room, which reads the deployment's language
- an attachment-failure notice: resolved on the request path and carried on
attachmentTarget, because the failure happens on a detached goroutine with
no context left to read a profile with
TestOnlyStringsGoHoldsUserVisibleCopy walks the package's AST and fails on any
Han literal outside strings.go. Two files are listed as pending with an exact
count: wecom_channel.go's unsupported-kind receipt and media_ingest.go's two
media notices, each moved by its own follow-up. The count is asserted both ways,
so the allowance cannot outlive the follow-up that consumes it.
* feat(wecom): every notice reads the language of whoever will read it
The adapter's user-visible strings were Chinese literals scattered across
replier.go, inbox_message.go and outbound_media.go, so an English-reading member
got Chinese notices whatever their profile said. copyPack already existed for
the bubble's stream strings; this moves the rest into it and resolves the locale
from the destination.
Who decides, per surface:
- a 1:1 reply, the binding prompt, the inbox card: the reader's own profile
- a group notice: the room, which reads the deployment's language
- an attachment-failure notice: resolved on the request path and carried on
attachmentTarget, because the failure runs on a detached goroutine with no
context left to read a profile with
The wiring is the part that has to hold: router.go passes Languages, and
NewOutboundReplier warns when it is nil, because a missing lookup has no other
symptom — nothing errors, nothing is empty, every notice simply comes out in one
language. TestWecomReplierGetsItsLanguageLookupOnTheRealBootPath asserts it off
NewRouter itself, with the anti-vacuity check read as a delta between two
routers rather than a count, since chat:done has listeners of its own.
TestOnlyStringsGoHoldsUserVisibleCopy walks the package AST and fails on any Han
literal outside strings.go, naming the two files whose copy has not moved yet
with an exact count, asserted both ways.
* MUL-7706 docs(wecom): drop the duplicated stream-field docs, refresh the lint header
The restored StreamNoReply block carried its opening paragraph twice, and the
lint file's header still described two rules and a greeting after the second
rule was dropped.
Co-authored-by: multica-agent <github@multica.ai>
* MUL-7706 test(wecom): compare two routers in the replier wiring guard
A bare chat:done count is non-zero whenever Slack or DingTalk is configured, so
the guard passed with the WeCom block never entered. Build one router without
the WeCom key and one with it, as wecom_bubble_wiring_test.go does, and require
the difference.
Co-authored-by: multica-agent <github@multica.ai>
* fix(wecom): a relayed reply's attachment notice reads the reader's language too
deliverRelayed builds its own attachmentTarget and never set Locale, so
copyFor("") fell back to the deployment pack. On a multi-replica deployment
that is the common path, not the exception: chat:done lands wherever the run
finished, and only the lease holder can write to the socket — so an English
reader whose file fails to upload got the Chinese notice, while the same
failure on the direct path was already answered in English.
Nothing else differs when this is wrong. The file still fails, the notice still
goes out, and no existing test covered the relayed case.
Also two wording fixes against the product glossary: /issue creates an issue,
not a task, so IssueUsage now reads like Slack's; and the task_failed inbox
label is 'Run failed', matching what the web inbox shows for the same
notification.
* MUL-7706 test(wecom): keep each attachment-notice test under its own doc comment
The relayed test was inserted between the direct test's doc comment and its
function, so the direct test lost its comment to the relayed one. Put the
direct test back under its comment, follow it with the relayed test, and move
the util import out of the standard-library group.
Co-authored-by: multica-agent <github@multica.ai>
* MUL-7706 test(wecom): the invoke-denied notice reads its one reader's language
A 1:1 refusal joins the per-outcome locale table; a group trigger's refusal,
sent to the sender's own 1:1, must read the sender's profile rather than the
room's deployment default.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Bohan-J <bhjiang@outlook.com>
Co-authored-by: multica-agent <github@multica.ai>
* docs(changelog): add v0.6.0 release entry (2026-09-28) (MUL-7777) (#8909)
Co-authored-by: Bohan <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
* MUL-7780 feat(issues): run charts follow the pointer; a cost sparkline replaces the sidebar strip (#8912)
The Runs dialog's crosshair snapped to the end of whichever run was nearest,
so it moved in jumps: a pointer 57px further right could send it 250px across
the chart, and inside a long run it sat at the run's far end rather than under
the pointer. It now follows the pointer, clamped to the stretch that holds
runs. The run whose bar spans that moment (give or take 8px) answers; between
runs the card says there was no run, how long the quiet lasted, and what had
been spent by then. The card sits in the half of the plot its dot is not in.
Three smaller breaks in the hover are fixed too:
- it covered only the plot column, so drifting onto the lane labels or the
y scale dropped it; the whole chart row tracks now
- the gaps between lanes belonged to no lane and briefly handed the hover to
every agent's runs; lane rows now touch
- the peak label unmounted on enter and leave; it fades instead
The sidebar's spend strip drew one bar per run, 12px wide and left-aligned, so
an issue with three runs showed three bars in the corner of an empty strip. It
is replaced by the dialog's chart in miniature: the cumulative cost as a step
curve over a track of the runs. It spans the column whatever the run count,
appears from the first priced run, and hovers the same way the dialog does.
nearestRunIndex becomes runIndexAt (with a slop, -1 between runs), and the
timeline gains cumulativeCostAt, idleSpanAround, stepCurvePath and extent so
both charts share one hover model.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* docs(skills): document wakeup v2 conditions and check-ins in multica-platform (MUL-7791) (#8950)
Supersedes #8929.
Co-authored-by: Karryz <karry.viber@gmail.com>
* fix(chat): preserve workspace switches from open sessions (#8928)
Scope URL reconciliation and selection cleanup to the route and workspace that own the controller. Add regressions with the real workspace-aware chat store.
Co-authored-by: yangpengcheng.1 <yangpengcheng.1@bytedance.com>
* MUL-7801 fix(daemon): follow directory junctions when authorizing repo checkout workdirs (#8958)
* fix(daemon): follow directory junctions when authorizing repo checkout workdirs (#8946)
On Windows, a workspaces root moved to another drive and left behind as a
directory junction put a junction in the middle of every task path.
filepath.EvalSymlinks fails with ENOTDIR on such a path under the winsymlink
semantics Go 1.23+ selects, so the repo checkout authorization added in
v0.4.30 refused every checkout as "not owned by the active task", and the
workdir-reuse check silently declined every follow-up.
Add util.ResolveSymlinks, the strict counterpart of
ResolveSymlinksBestEffort: it follows symlinks and junctions and fails on
any missing component. On Unix it is filepath.EvalSymlinks; on Windows a
junction-free path keeps the spelling EvalSymlinks gives it. Use it in the
checkout authorization and both workdir-reuse checks, and log the real
reason for a 403 and include it in the response.
Co-authored-by: multica-agent <github@multica.ai>
* fix(util): walk extended-length paths in ResolveSymlinks, refuse device-namespace ones
evalPath returns a `\\?\` input unwalked and joins the rest of a path
lexically onto a volume-GUID link target. That is the fail-closed answer for
the best-effort resolver, but ResolveSymlinks feeds a containment check that
can compare two device-namespace spellings with each other: with the active
workdir spelled `\\?\C:\task\workdir`, `\\?\C:\task\workdir\escape\sub` read
as inside it although escape is a junction to another directory, and
canonicalSpelling's fallback kept that unwalked string.
The strict path now converts an extended-length path with a Win32 spelling
and walks it component by component, and returns ErrUnresolvablePath for any
input or result that stays in the device namespace.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>
* MUL-7821: feat(cli): mark duplicates with issue status/update --duplicate-of (#8979)
* feat(cli): mark duplicates from issue status and issue update (MUL-7821)
Add --duplicate-of to `multica issue status` and `multica issue update`.
It resolves an issue key or UUID and sends duplicate_of_issue_id, which
the server already accepts (MUL-7349). The CLI rejects a status other than
cancelled, and description or attachment changes, before any request or
upload. It fails when the response has no duplicate_of field, so a server
older than v0.5.2 cannot silently drop the mark.
Closes #8974
Co-authored-by: multica-agent <github@multica.ai>
* docs: teach agents to mark duplicates with --duplicate-of (MUL-7821)
Add the command to the agent runtime brief, the multica-platform issues
reference, CLI_AND_DAEMON.md and the CLI docs tables, so agents record the
mark instead of cancelling a duplicate and explaining it in a comment.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>
* fix(chat): release hidden floating composer focus (#8998)
* MUL-7816 fix(taskfailure): classify a 403 usage limit as provider quota, not auth (#8967)
* fix(taskfailure): classify a 403 usage limit as provider quota, not auth
Kimi Code reports an exhausted usage window as HTTP 403. The bare 403
auth rule ran before the quota rule, so the failure was filed as
provider_auth_or_access. Match the usage-limit witness before the auth
rule. The concurrent-request-limit case still comes first, and a plain
403 still classifies as auth.
Fixes #8965
* fix(taskfailure): upgrade old-daemon 403 usage limits and beat the token rule
Move the usage-limit check into a shared isUsageLimit403 helper that
requires HTTP 403. Classify checks it right after the concurrent-request
case, so an access-token-prefixed 403 usage limit no longer lands in
context_overflow. NormalizeDaemonReason upgrades the same message from
older daemons (context_overflow, provider_auth_or_access, unknown,
agent_error) to provider_quota_limit.
* fix(codex): add GPT-6.1 Sol fallback and current model pricing (MUL-7834) (#8996)
* perf(issues): scope working-agent facet tasks by visible agents (MUL-7838) (#9004)
* perf(issues): scope working-agent facet tasks by visible agents
Co-authored-by: multica-agent <github@multica.ai>
* perf(issues): join working facet agent candidates as a relation
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Yushen <yushen@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
* perf(issues): limit working-agent refresh queries (MUL-7838 PR2) (#9005)
* perf(issues): limit working-agent refresh queries (MUL-7838)
Co-authored-by: multica-agent <github@multica.ai>
* fix(issues): defer working filter until membership resolves
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Yushen <yushen@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
* docs(changelog): add v0.6.1 release entry (2026-10-01) (MUL-7840) (#9006)
Add the 0.6.1 entry to the four changelog locales and bump the web and desktop package versions to 0.6.1.
Co-authored-by: Yushen <yushen@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
* fix(codex): exclude historical turns from resume responses (#9020)
* test(shortcuts): wrap upstream composer-focus test in a QueryClient
The fork's GlobalShortcuts reads the query client for the Mod+Shift+O
new-chat shortcut. Upstream's new floating-composer test rendered it
without a provider, so pass the file's existing wrapper.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Jiayuan Zhang <forrestchang7@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: Bohan Jiang <52446949+Bohan-J@users.noreply.github.com>
Co-authored-by: Xichang(Seacen) Zhao <xichangzhao@outlook.com>
Co-authored-by: J <bohan@devv.ai>
Co-authored-by: Bohan-J <bhjiang@outlook.com>
Co-authored-by: Multica Eve <eve@devv.ai>
Co-authored-by: Karryz <karry.viber@gmail.com>
Co-authored-by: Frezc <504021398@qq.com>
Co-authored-by: yangpengcheng.1 <yangpengcheng.1@bytedance.com>
Co-authored-by: mrlonely <116348059+mameikagou@users.noreply.github.com>
Co-authored-by: Younes Beriane <paranoyouz@gmail.com>
Co-authored-by: Yushen <yushen@devv.ai>
What does this PR do?
Refs #8965. Covers the item where a 403 usage-limit failure is filed as an auth failure.
On "API Error: 403 You've reached your 5-hour usage limit" the bare 403 auth rule matched first, so members were told to fix valid credentials. Classify now checks "usage limit" before it and returns the quota reason; a plain 403 stays auth.
Related Issue
Refs #8965
Type of Change
Changes Made
How to Test
go test ./pkg/taskfailure/in server/: 198 passed, 3 failed before, 201 passed after.Checklist
apps/web/features/landing/i18n/) and relevant docs (apps/docs/content/docs/)apps/docs/content/docs/developers/conventions.zh.mdx(terminology, mixed-rule fortask/issue/skill)AI Disclosure
AI tool used: Claude Code
Prompt / approach: Reproduced with a failing test, then a minimal ordering fix.
Screenshots (optional)