A merged PR moved its issue to the workspace's pr_merge_status even when the
issue's project workflow does not list that status, bypassing the check a
manual status change goes through. The decision now loads the project
workflow and returns not_in_workflow instead; the issue page renders nothing
for it, so the issue silently stays where it is. Default workflows and issues
outside any project are unchanged.
Co-authored-by: Multica Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(runtimes): spell out Qoder Cloud Agents instead of QCA
The UI, docs, landing copy and server messages called the product "QCA"
in labels such as "QCA Agent", "QCA runtime" and API errors like "QCA
token is required". Use the full name Qoder Cloud Agents everywhere a
person reads it, and add it to the brand list in the naming conventions
so the abbreviation does not come back.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(runtimes): refine Qoder Cloud Agents copy (MUL-8282)
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: 安辰 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
UpdateProjectResource derives every column it writes back from the row
read at the top of the request, but the read and the write ran outside
any transaction. Two concurrent partial updates could interleave as
read-read-write-write, and the second writer's stale snapshot silently
reverted the first writer's change: a label-only PUT reverted a racing
ref update to an old Git ref, and a label-only PUT on a local_directory
restored a stale execution mode (reported in #9238 with a deterministic
interleaving that fails 15/15 under -race -count=3).
Serialize the flow with SELECT ... FOR UPDATE inside a short transaction
(GetProjectResourceInWorkspaceForUpdate + Begin/WithTx, same shape as
CreateAgent). Competing updates block on the row until the write lands,
then re-read fresh. This keeps the local_directory legacy label-mirror
writeback intact, which a presence-aware column rewrite alone would have
broken for mirror-triggered ref updates.
Review follow-ups:
- The regression test no longer leaves the race to the scheduler. Before
resuming the paused label request, it waits until the ref request has
either completed outright (the only exit on the pre-fix code, which
never blocks) or is blocked waiting for the label request's row lock
(the only exit with the fix, seen as wait_event_type='Lock' on the
backend running the FOR UPDATE query, checked via pg_stat_activity).
The two signals are mutually exclusive, so the test fails
deterministically without the lock and passes with it.
- findLocalDirectoryConflict and requireWorktreeCapableDaemon now read
through caller-provided queries instead of the handler's pool: on the
update path they run inside the transaction, so a locked request no
longer borrows a second pooled connection per request.
- Added a local_directory concurrency case: a label-only update racing
an execution_mode update through the label-mirror ref rewrite.
The regression tests fail on the pre-fix code ("stored ref = main, want
the concurrent ref update (release)" / "stored execution_mode = worktree,
want the concurrent mode update (cleared = in_place)") and pass with the
lock, 20/20 runs each way.
Fixes#9238
Generated-by: GLM-5.3-Flash (ZCode)
* feat(i18n): add zh-Hant locale to web and desktop
Add Traditional Chinese (zh-Hant) as a supported locale with a full
translation of the shared views bundles. Browser tags zh-TW/zh-HK/
zh-Hant now resolve to zh-Hant instead of collapsing to zh-Hans; the
document lang is zh-TW, and docs/use-case links reuse the /zh pages.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit a6b3f6ba53a605c3e06fd6c4d30fd9df6fca8645)
* feat(mobile): add zh-Hant locale
Ship Traditional Chinese bundles for mobile and resolve zh-Hant/TW/HK/MO
device locales to them instead of falling back to English.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 04563c22662bdb735a9a6c04be784abd9f87da06)
* feat(ui-lab): add zh-Hant locale
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 1351ea572ed5d885d8a29034d6c0317de58641ea)
* feat(server): accept zh-Hant as user language
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 50dfe77a4c45452fc877c62497aba226d5aaa426)
* fix(i18n): sync zh-Hant with upstream copy changes
Upstream added conversation-starter, instruction-preview and board column
strings, removed restated descriptions (MUL-8162) and renamed Qoder Cloud
Agents. Mirror those key additions, removals and rewordings in zh-Hant so
the locale parity test passes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 3a97dfc7e0bbf7568925f735c00fc69b6d94313d)
* fix(i18n): add zh-Hant copy for member-scoped runtime visibility
Mirror the runtime visibility "members" choice and the reworded revoke
dialog in zh-Hant so the locale parity test keeps passing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(i18n): replace Mainland terms in zh-Hant copy with Taiwan usage
Several zh-Hant strings were converted character by character from
zh-Hans and kept Mainland vocabulary, which reads wrong to Traditional
Chinese users: 郵箱 → 信箱, 賬單/賬號 → 帳單/帳號, and similar. Values
only; no keys added or removed.
* fix(i18n): polish zh-Hant copy from review
- 分割槽 → 分區 and 許可權 → 權限 (OpenCC phrase mis-conversions)
- drop stray blank lines before zh-Hant workspace names
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Multica Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(daemon): defer issue status rules to the project workflow (MUL-8145)
When the claim carries a project workflow, the runtime brief no longer
tells the agent to set in_progress first and in_review on delivery. Those
statuses may be outside the workflow (the server rejects them), and
entering a handoff step reassigns the issue. The brief now points at the
per-turn [Project workflow] block, and the workflow brief carries the
entry/exit checks in workflow terms. Briefs without a workflow are
byte-identical to before.
Co-authored-by: multica-agent <[email protected]>
* fix(squad): word the owning leader's status moves by the project workflow (MUL-8145)
The squad protocol (Agent Identity, which outranks the runtime brief) still
told an owning leader to move the parent to in_progress and then in_review,
which a workflow may not list and which can hand the parent to another
step's handler. When the claim carries a project workflow, responsibility 6
now keeps the ownership grant but moves the parent only through the
[Project workflow] commands. The claim resolves the workflow before the
briefing to choose the wording. Guest leaders and issues without a
workflow are unchanged.
Co-authored-by: multica-agent <[email protected]>
* refactor(daemon): list no status keys in the brief for a workflow issue (MUL-8145)
With a project workflow, [Project workflow] already lists the issue's
statuses and the commands that move it, and the built-in keys may not be
accepted there. The status command line now keeps only the command and
points at that block; other issues learn valid statuses from the error.
Co-authored-by: multica-agent <[email protected]>
* refactor(workflow): stop forbidding status moves nothing asks for (MUL-8145)
Under a project workflow the current runtime brief no longer states the
built-in in_progress / in_review moves, so the lines that forbade them
(in [Project workflow], the runtime brief and the squad protocol) only
named statuses the agent had no reason to use. Drop them, and the exit
check the runtime brief already states.
Older daemons still state the built-in rules, so the daemon now
advertises workflow-status-rules-v1; without it the claim keeps the
override lines in [Project workflow] and the squad protocol. The
multica-platform skill notes that [Project workflow] supersedes its
built-in status arc.
Co-authored-by: multica-agent <[email protected]>
* refactor(workflow): shorten the step's moves in the workflow brief (MUL-8145)
Replace the handoff preamble with a plain "Your moves:" heading, name
who each move hands off to on its own line, and drop the "this
workflow has no blocked status" explanations from Can't proceed.
Co-authored-by: multica-agent <[email protected]>
* refactor(workflow): state each step outcome as the status to set (MUL-8145)
The moves block read like raw CLI ('Step done → multica issue status MUL-7 x').
Each outcome now says which status to set and that the server assigns the
next handler; the CLI form is given once.
Co-authored-by: multica-agent <[email protected]>
* Revert "refactor(workflow): state each step outcome as the status to set (MUL-8145)"
This reverts commit 2d82d2d342a99afe88cc40c48516b7ea8e860d1e.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Preserve workflow-selected assignees when description updates refresh untouched nullable fields under the issue row lock. Apply the same behavior to single and batch updates while retaining explicit-assignee precedence.
Add regression coverage for single and batch handoffs, unchanged-status retries, and explicit member assignments. All applicable PR CI checks passed on 3c5f8d4.
Co-authored-by: Multica Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Use one slash-delimited path contract for skill authoring endpoints and daemon bundle validation. Cover canonical path handling and all public file-write routes with regression tests.
* fix(openclaw): carry per-run model override on the run input (INFRA-71)
Reimplement MUL-7376 (#8419) on this fork and address the review points.
Resolve runtime_config.model_override once, at claim time, and carry it on the run input (model_override); the daemon no longer decodes it from mutable agent config.
OpenClaw keeps agent.model as the registered agent id (--agent) and the override as the underlying model (--model); arbitrary --model from custom_args stays blocked. Usage attribution falls back runtime model -> per-run override -> unknown, never the agent id.
Round-trip model_override through the runtime-config schema, serializer, equality check and the runtime-config tab (i18n in all locales). Docs updated.
Co-authored-by: multica-agent <[email protected]>
* docs(openclaw): clarify persistent agent model overrides
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Multica Eve <[email protected]>
* fix(daemon): tell resumed runs when their trigger comment was edited (MUL-8218)
A comment edit cancels the affected run and retriggers from the edited
comment, but the follow-up resumes the cancelled run's session, whose
history still quotes the pre-edit text (GH #9492).
The claim now sets trigger_comment_edited_at / trigger_comment_revision
when the trigger was created before the resumed run started and edited
after it. The daemon renders an [EDITED COMMENT] header plus a note that
the quoted text is the current version, while work already done in the
session still happened.
Co-authored-by: multica-agent <[email protected]>
* fix(daemon): keep edit flag after resolve and for coalesced comments (MUL-8218)
Review of #9526 found two paths that resumed a session holding the old
text without flagging the edit:
- A resolve after the edit moved updated_at to resolved_at and the
resolve exclusion hid the edit. Only a single resolve on a first-
revision row (revision 2, resolved_at == updated_at) is now treated
as resolve-only; anything else errs toward the note.
- An edited comment folded behind a newer, unchanged trigger lost its
flag because only the trigger was checked. Every delivered coalesced
comment now carries edited_at / revision, and the prompt marks it
EDITED with the shared current-version guidance.
Adds the reviewer's two regressions.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* fix(skills): preserve complete bundles when source fetching fails
* fix(skills): skip submodules in the contents-crawl fallback
The contents API lists submodules inside a directory as type "file" with
a null download_url. The crawl fallback rejected them as a missing
download URL, so a skill containing a submodule failed with 503 whenever
the repository tree was truncated, while the tree path skips them. Detect
submodules via their git_url tree pointer and skip them; a regular file
without a download URL still fails the import.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Multica Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(kiro): fail fast when the runtime panics mid-turn (MUL-8188)
Kiro can panic inside its agent loop while the ACP process stays alive, so
session/prompt never answered and the task hung until the 2h idle watchdog
or a manual cancel. Watch stderr live for a panic header, abort the prompt,
and surface the panic text. A resumed session that panics before its first
message signals ResumeRejected so the daemon retries once on a fresh session.
Co-authored-by: multica-agent <[email protected]>
* fix(resume): stop re-resuming sessions the runtime cannot drive (MUL-8188)
- taskfailure.UnresumableHistory matches "invalid conversation history",
so the daemon's fresh-session retry, classifyPoisonedError and
ResumeUnsafeFailure all treat Kiro's panic as resume-unsafe; the
GetLastTaskSession / GetLastChatTaskSession text guards mirror it for
rows written by older daemons.
- A resumed run cancelled after >=5 minutes with zero agent output now
retires its session: the daemon sends retired_session_id on cancel-ack
and the server records it on the cancelled row (status CAS, never
overwrites) and clears a chat pointer still naming it.
Co-authored-by: multica-agent <[email protected]>
* fix(resume): address review on retired sessions (MUL-8188)
- Manual retry no longer revives a retired session: the exact-source rerun
claim path now checks IsTaskSessionRetired (fails closed), still reuses
the source workdir, and sets PriorSessionResumeUnavailable.
- Silent-resume retirement measures awakeclock time, so host suspend
(Windows monotonic clock counts through sleep) cannot retire a healthy
session; adds a suspend regression test.
- Handler tests use dbfx.Issue/Task and testutil.Call per AGENTS.md.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* feat: share runtimes with selected workspace members
Co-authored-by: multica-agent <[email protected]>
* refactor(runtimes): one-click private/public and popover member picker
Private and Public apply immediately again (restrictions still go through
the reviewed revocation dialog). Specific members opens a draft popover
with search, Cancel and Save; selected recipients are summarized under
the scope control with an Edit entry point. The editor moves to its own
runtime-access-editor component.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* feat(telemetry): report visiting and delegating member counts in self-host snapshot v2 (MUL-8161)
Co-authored-by: multica-agent <[email protected]>
* fix(telemetry): count delegating members from a human-action ledger, drop v1 fallback (MUL-8161)
- member_delegation_day records (member, UTC day) at the human action itself,
so retries, system wakeups and creator-fallback runs never count, and a
comment folded into a queued run keeps both humans and their own days.
- Activity read failures now fail collection and use the existing retry
instead of silently sending a v1 snapshot.
Co-authored-by: multica-agent <[email protected]>
* fix(telemetry): record create-with-assignee, honor DO_NOT_TRACK, build ledger PK concurrently (MUL-8161)
- A member creating an issue assigned to an agent or squad now records a
delegation day, on both the immediate and deferred create paths.
- TaskService.RecordMemberActivity gates the ledger and is set from the
startup telemetry config, so DO_NOT_TRACK stops collection too.
- member_delegation_day's primary key is built with CREATE UNIQUE INDEX
CONCURRENTLY + PRIMARY KEY USING INDEX (589-591), with cleanup registered.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* fix(daemon): enforce runtime ownership on daemon routes (MUL-8154)
/api/daemon/* accepts any workspace member's PAT/JWT, and authorization only
checked workspace membership. Any member could claim another member's tasks
(receiving the agent's custom_env, MCP config and an owner-scoped task token),
write task results/messages, and take a runtime over by re-registering its
daemon_id.
- Runtime-level check on every runtime/task daemon route, HTTP heartbeat,
deregister, batch claim (and so the WS claim RPC) and the WS handshake:
user credentials must own the runtime (NULL owner fails closed); daemon
tokens must match a non-empty runtime daemon_id.
- Register: pre-check that every existing row on the daemon_id belongs to the
caller, plus a required_owner_id fence in both upserts (incl. the
failed-profile path) so a concurrent registration cannot win the row.
Daemon-token registration keeps the existing owner.
- Legacy merge: only for user-credential registrations, and only when the
caller owns both rows, verified on the FOR UPDATE-locked rows.
- Tasks without a runtime are not writable through daemon routes; status
stays readable.
- GetTaskStatus / WS lease queries carry runtime owner/daemon so the hot
paths keep one round trip.
Co-authored-by: multica-agent <[email protected]>
* test(daemon): authenticate fixtures as the runtime owner (MUL-8154)
Most handler tests drove daemon routes with a daemon token whose daemon_id
did not match the fixture runtime, relying on the workspace-wide token scope
this fix removes. They now authenticate the way real daemons do, with the
runtime owner's user credential (newOwnerDaemonRequest). Tests about daemon
token scoping keep mdt_ and get fixtures with a matching daemon_id; the
ownerless-runtime tests also assert that a user credential is refused.
Co-authored-by: multica-agent <[email protected]>
* fix(daemon): serialize registration per daemon and keep Qoder WS claims working (MUL-8154)
Review of #9423:
- P1: the owner pre-check and the per-provider/profile upserts hit different
conflict keys, so two members registering different providers of the same
daemon could both pass the pre-check and both insert, leaving one daemon
with two owners (and a daemon token for either runtime reaching the
other's). Registration now runs the pre-check and every runtime write in one
transaction under a pg_advisory_xact_lock on (workspace_id, daemon_id), on a
single connection so queued registrations cannot starve the holder. Legacy
merges, analytics and the register event run after commit; failed-profile
records use savepoints so they stay best-effort.
- P2: the Qoder revocation check read the Authorization header, which the WS
RPC bridge's in-process request does not carry, so valid Qoder WS claims
silently returned nothing. DaemonAuth now records the mdt_ token hash in the
request context, the WS identity keeps it, the RPC bridge re-attaches it,
and the check validates that hash on every call (revocation still applies
to an open connection).
- Regression tests for cross-provider and provider+profile concurrent
registration, and for valid/revoked Qoder credentials over WS RPC.
Co-authored-by: multica-agent <[email protected]>
* fix(daemon): bind daemon tokens to the runtime owner they were issued for (MUL-8154)
Second review of #9423: a daemon_id can be re-registered by another member
after its runtimes are deleted, and a daemon token was bound only to
(workspace, daemon_id), so the previous owner's Remote MCP token reached the
new owner's runtime (heartbeat, claim, owner-scoped task token).
- daemon_token.owner_id (migration 588). Remote MCP tokens are bound to the
locked runtime owner inside FinalizeTaskClaim; live tokens are backfilled
only where their daemon has exactly one owner.
- DaemonAuth carries the owner binding (DB and cache; cache key bumped to v2
so pre-upgrade entries are never read), the WS identity keeps it and the
RPC bridge replays it.
- Daemon-token authorization now also requires token owner == runtime owner.
Qoder-managed tokens stay unbound (governed by cloud_agent_connection), and
user credentials can no longer register the reserved qoder-managed-* ids.
- Tests: the reuse lifecycle over HTTP, a new WS handshake and an already
open WS connection; the reserved daemon_id; the concurrency barrier now
hooks the registration transaction (TxStarter) so the interleaving is real.
Co-authored-by: multica-agent <[email protected]>
* fix(daemon): owner-bind daemon-token registration; drop unsafe token backfill (MUL-8154)
Third review of #9423:
- Migration 588 no longer backfills daemon_token.owner_id from the daemon's
current owner: that owner may be a later re-registrant, which re-bound the
previous owner's live token to them. Existing non-Qoder tokens stay NULL and
are refused (24h Remote MCP credentials, re-issued on the next claim).
- owner_id is a plain UUID column, no foreign key / cascade (AGENTS.md).
- Register via a non-Qoder daemon token now uses the token's owner for the
in-transaction pre-check and the SQL owner fence, and refuses unbound
tokens. Qoder keeps its connection-based check; daemon tokens still never
trigger legacy merges.
- Tests: an old owner's bound token and an unbound legacy token can neither
register over nor heartbeat the replacement owner's runtime.
Co-authored-by: multica-agent <[email protected]>
* fix(daemon): grant the Qoder registration exemption only with a live connection (MUL-8154)
Fourth review of #9423: register skipped the token owner binding for any
qoder-managed-* daemon token, but checked the Qoder connection only when the
body declared a qoder_cloud runtime. A pre-upgrade Remote MCP token under that
prefix (no owner, no connection) could register type=claude and rewrite
another member's runtime.
Register now verifies the token against a live cloud_agent_connection before
anything else whenever the token is in the Qoder namespace, regardless of the
declared runtimes, and restricts that namespace to the bridge's own shape: a
built-in qoder_cloud runtime, no custom profiles, no failed_profiles.
Test covers built-in, custom-profile, failed-profiles-only and qoder_cloud
bodies with an unconnected token, plus the connected token's allowed and
refused shapes.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(runtimes): rename Qoder Cloud Agent to Qoder Cloud Agents
Qoder's product is called Qoder Cloud Agents, matching how Cursor Cloud
Agents is already spelled in the runtime lists. Update the display name,
default runtime name, error messages, translations, landing copy, docs
and tests.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(runtimes): use Qoder's current logo
Replace the old green Qoder mark with the current monochrome mark from
qoder.com/favIcon.svg. The background is removed, the paths are
minified, and both paths use currentColor so the mark stays visible in
dark mode. It applies to the Qoder, Qoder CN and Qoder Cloud Agents
runtimes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: 安辰 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
An agent task token (mat_) authenticates as the runtime owner, and neither
POST /api/cli-token nor the /api/tokens group checked the actor source. A
prompt-injected agent could trade its 24h task token for a 30-day session
JWT or a non-expiring PAT, and could list or revoke the owner's PATs.
- Mount RequireHumanActor on /api/cli-token and the whole /api/tokens group
(also rejects mcn_ cloud-node PATs).
- Add a handler-level backstop in IssueCliToken and
CreatePersonalAccessToken so a future route refactor cannot drop the guard.
- Add router-level integration tests (real Auth -> router -> handler, with a
forged X-Actor-Source) and handler backstop tests.
Co-authored-by: multica-eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(server): allow workspace creation only for listed emails (self-host)
Add WORKSPACE_CREATION_ALLOWED_EMAILS. When set, POST /api/workspaces
returns 403 for callers whose email is not listed. Empty keeps the
current behavior; DISABLE_WORKSPACE_CREATION=true still blocks everyone.
No instance-admin role, no domain allowlist, no UI hiding.
Refs #9400, MUL-8136
Co-authored-by: multica-agent <[email protected]>
* docs(self-host): clarify which signup vars /api/config exposes
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(runtimes): add Qoder Cloud Agent managed remote execution
* fix(qoder): satisfy migration and localization CI contracts
* fix(qoder): bound execution and scope managed credentials
* fix(qoder): avoid upstream migration number collisions
* fix(qoder): gate issue creation and correlate tool results
* fix(qoder): synchronize guarded issue lifecycle transitions
* test(agent): publish Hermes holder identity atomically
* feat(agents): runtime-first create flow and cloud runtimes section for Qoder Cloud Agent
- Three-step create-agent flow (runtime -> configure -> done) with local,
remote and cloud sections; Qoder connect form inline in the runtime step
- Runtimes page regrouped into remote and cloud sections with Qoder card
- Generalize Qoder storage to cloud_agent_connection (migrations 586/587)
- Locales for en, zh-Hans, ja, ko, fr; desktop and web routes for done step
- Fix status-picker fixture owner_id and diagnostics route coverage after main merge
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(runtimes): unify runtimes page into machines and cloud services
- One Add menu replaces Add remote runtime, the duplicated Add a computer
and the web-only Cloud Runtime header button
- Group by machines / cloud services instead of local / remote / cloud, so
web and desktop share one structure; this device is pinned with a badge
- Cloud services use the machine row anatomy in a single ListGrid table;
unconnected Qoder is a row with Connect, coming-soon providers are rows
- Empty groups show the next step inline; search and online/offline filter
- Rename computer to machine across runtimes copy in all locales
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* test(runtimes): cover unified runtimes page; pin status and action tracks
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(runtimes): trim redundant descriptive copy on runtimes page
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(runtimes): keep row actions visible at narrow widths
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(runtimes): align Qoder add-menu item with other cloud services
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(runtimes): close the Qoder dialog after a first-time connection
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(agents): pick the runtime from one grouped list
The runtime step showed four kinds of cards, a comparison panel and
per-section descriptions. It is now one list in the runtimes-page layout:
machines (this one open, others folded to a row) and cloud services. An
unconnected Qoder opens the runtimes-page connect dialog in place and is
selected once it comes online.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agents): show owners as 'you' and bare version numbers in the runtime list
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(qoder): disconnect action and dedicated Qoder runtime page
Disconnect removes the encrypted connection and the managed runtime, unbinding
its agents after the admin confirms them. The Qoder row now opens its own page
(status, agents, usage, configuration, pause/resume, disconnect) instead of the
machine layout; generic runtime delete and visibility refuse a managed runtime.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agents): land on the done page after creating from step 2 on desktop
Desktop's location is the tab URL, which moves to /agents/new/done/<id>
while the configure page is still mounted. Its missing ?runtime= tripped
the skipped-step-1 redirect and replaced the tab back to /agents/new, so
Create looked like it did nothing. Only redirect on the manual route.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agents): show only settings a Qoder agent can run with
The Qoder bridge fails every task whose agent carries a model, thinking or
speed override, env, custom args, custom skills, MCP config or connected
apps, and it runs issues only. The agent detail page still offered all of
those editors, so one save could break every later run.
For a qoder_cloud agent the page now keeps Instructions under Capabilities,
General / Access / QCA Agent under Settings, drops the model rows and the
header model chip, shows the QCA agent in the summary, and locks the
runtime. Other agents no longer see the Qoder runtime in their picker,
since moving onto it needs a QCA agent the server would otherwise reject.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agents): hide conversation starters for Qoder agents
Starters only appear in a chat, and the Qoder bridge runs issues only.
The create flow already leaves them out for this runtime.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(runtimes): show the Qoder connection as cards with per-section editing
The Qoder runtime page rendered the whole connection as one always-editable
form. It now summarizes the connection, repositories and limits as read-only
cards; each section opens its own dialog and saves only that section.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(qoder): reuse a follow-up session only with the same repository and credential
Resolve repository credentials before deciding to continue a prior session,
and stamp each session with a fingerprint of its repositories, branches and
tokens. A follow-up continues only when the fingerprint still matches, so a
changed repository, branch or rotated/revoked token never rides into an old
session.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(qoder): keep a paused connection paused when its settings are saved
Saving repositories, limits or the connection no longer re-enables a paused
runtime. Resume goes through a new POST /qoder/resume.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agents): drop settings a Qoder agent cannot run when creating one
A draft started on another runtime could carry hidden skills, model
overrides and starters into a Qoder agent that then failed every task. The
create request now drops them for Qoder, and the API rejects skills and
execution overrides on Qoder agents at save time.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* chore(db): regenerate sqlc models for cloud_agent_connection
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(runtimes): never list the Qoder runtime under Machines
When the server could not run Qoder (no MULTICA_VCS_SECRET_KEY /
MULTICA_QODER_STATE_DIR), the managed Qoder runtime fell back into the
Machines group as if it were a computer.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(qoder): drop MULTICA_QODER_STATE_DIR
The managed bridge keeps its run journal encrypted in cloud_agent_connection
and a database advisory lock already elects one supervisor, so the state
directory only held a redundant file lock and acted as a feature switch.
Qoder is now available whenever MULTICA_VCS_SECRET_KEY is set. The
standalone qoder-runtime command still uses its on-disk state_dir.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(qoder): keep the branch in the session fingerprint after a journal restore
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agents): validate Qoder agents' effective settings on every write
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: 安辰 <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* MUL-8135: feat(wakeups): wake on linked PR merge conflicts (--until-pr conflicts)
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* MUL-8135: fix(wakeups): conflict wakes per PR identity, rotate the watch sweep
- A linked-PR condition only fires for a PR key the rule has not seen; a PR
leaving the satisfying set no longer re-wakes the agent for the others.
- Conflict keys use the PR id, so same-numbered fork PRs on one head stay
distinct.
- The conflict-watch sweep rotates an address cursor so failing fetches
cannot starve the rest.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* MUL-8135: fix(wakeups): count repeated linked-PR keys when deduping
Checks and merged keys carry only the PR number, so same-numbered PRs from
two repositories share a key. Compare occurrence counts so the second one
satisfying still wakes the agent.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(agent): support steering running Kiro turns via _session/steer
Kiro CLI 2.33.0 exposes an undocumented ACP extension, _session/steer,
that queues a message and injects it into the in-flight session/prompt at
the next tool or message boundary. Wire it up as the kiro backend's
Session.Supplement (active only while the prompt is in flight, and only
when the daemon negotiated task supplements), and gate kiro >= 2.33.0 in
SupportsTaskSupplement.
MUL-8126
Co-authored-by: multica-agent <[email protected]>
* fix(supplement): allow kiro in server-side steering whitelists
The capability handshake (StartAgentTaskWithSupplement), direct steering
(CreateTaskSupplement) and composer reply binding (BindCommentTaskSupplement)
each whitelist providers, and none included kiro, so a negotiated Kiro run
never got a capability row and steering stayed unreachable. Add kiro to all
three, regenerate sqlc, and cover kiro in the negotiation and both delivery
path tests. Also widen the kiro supplement test's readiness wait, which was
flaky under full-package parallel load.
MUL-8126
Co-authored-by: multica-agent <[email protected]>
* fix(supplement): allow grok in composer reply binding
BindCommentTaskSupplement was the only steering whitelist without grok, so
a reply from the normal composer to a running Grok turn fell back to the
next run even though direct steering worked. Add grok and cover it in the
composer binding test.
MUL-8126
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* MUL-8120 feat(workflows): record workflow owner and let owners edit their workflows
Any workspace member may now create a workflow and becomes its owner.
Editing, deleting and changing handoffs is open to the owner and to
workspace owners/admins. Default has no owner and stays admin-only.
Copying a workflow for one project now also requires managing that
project. Migration 585 adds issue_workflow.owner_id and backfills
existing named workflows with the workspace's earliest owner.
Co-authored-by: multica-agent <[email protected]>
* MUL-8120 feat(workflows): mirror owner-based workflow permissions in the UI
Members can create and duplicate workflows; the editor, column handoff
and flow panel are editable by the workflow's owner and admins. The
save-for-this-project option only shows to admins and the project lead.
Co-authored-by: multica-agent <[email protected]>
* MUL-8120 fix(workflows): only admins add statuses through workflow writes
Workflow create/update could add statuses to the shared workspace
catalog without a role check. Non-admins now get 403
status_create_forbidden when a step names a status that does not exist;
reusing a status by key or by name stays open.
Co-authored-by: multica-agent <[email protected]>
* MUL-8120 fix(workflows): hide new-status for members in the workflow editor
Members pick from existing statuses only. A template that needs statuses
the workspace lacks shows which ones and keeps Save disabled until they
are removed or an admin adds them.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* fix(wakeup): clear a condition rule's error once a check commits
A condition check that fails (for example on its 50ms lock wait) records the
error on the rule. Nothing cleared it afterwards: an unmet condition never
reaches AdvanceIssueWakeup, which is the only write that resets last_error, so
the rule kept reporting the old failure while every later check succeeded.
SetWakeupConditionState runs only inside a check that is about to commit, so it
now resets last_error. A "waiting for claimed run" note written later in the
same dispatch still lands, because AdvanceIssueWakeup runs after it.
* fix(wakeup): clear a system rule's error on every committed pass
dispatchSystem commits early when the assignee changed, when the parent is
closed or the rule is off, and while the parent sits in backlog. None of those
passes writes condition state, so a child_done rule kept an earlier pass's
error: until its parent left backlog, or for good on a closed parent, and the
workspace wakeups view kept flagging it.
The pass now clears last_error once it holds the rule's lock; an error later in
the pass rolls the clear back. Also corrects the SetWakeupConditionState
comment (it is written by baselines and system passes too, not only checks)
and trims the condition test to the assertion it exists for.
* fix(wakeup): preserve current diagnostics when checks recover
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Yushen <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(daemon): report the checkout's own repo sync error
ensureRepoReady read the sync failure from a per-workspace slot that any
concurrent background sync overwrote, so a later successful sync of another
repo erased the error and checkout reported only "repo is configured but
not synced". syncWorkspaceReposContext now returns its error to the caller
and the shared slot is removed.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* feat(server): share SSL_CERT_DIR CAs with daemons for git
Daemons clone with the system git, which never sees the backend's
SSL_CERT_DIR, so a repository on a host with an internal CA could not
sync even after the operator made the backend trust that CA.
The new gitca package mirrors crypto/x509's SSL_CERT_DIR loading, skips
the system directories, and keeps only certificates crypto/x509 trusts.
The register and workspace repos responses carry them as ca_certs. The
daemon writes its system CA bundle plus those certificates to a bundle
file, adds http.sslCAInfo to its git environment, and records it in the
cache and isolated checkout config so agents' fetch and push use it too.
An empty value removes the bundle; Windows daemons skip it.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* docs: document private CA trust for daemons
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* fix(daemon): keep operator CA trust and existing checkouts working
Review of the private CA support found two regressions for repositories
that already worked:
- Removing the last backend CA deleted the bundle while existing linked
worktrees and isolated checkouts still pointed at it, so their HTTPS
fetch and push failed. The bundle is now rewritten to the machine's own
trust instead, and each repository drops the reference the next time
the cache configures it.
- The bundle started from the system bundle and was injected through the
environment, overriding an operator's general http.sslCAInfo and any
repository-local value. It now starts from the file a general
http.sslCAInfo names (system bundle otherwise), the environment no
longer carries it, clone passes it with -c, and repository config is
only set where unset and only unset where it is ours. URL-scoped
settings, repository-local values, and GIT_SSL_CAINFO keep precedence.
Isolated checkouts record the setting before their first checkout, which
can lazily fetch from origin, and clones before the refspec migration
fetch.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---------
Co-authored-by: Iliya Anastasov <[email protected]>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
* MUL-8070: feat(cursor): support live-run steering via Cursor hooks
Cursor's headless --print mode has no mid-turn input channel, but its
postToolUse/postToolUseFailure hooks can return additional_context that
reaches the model in the same turn. A steerable Cursor run now writes a
managed .cursor/hooks.json whose command is the multica binary in a hidden
relay mode; the relay forwards the hook event to a token-protected loopback
endpoint owned by the run, which hands back pending guidance tagged with a
per-run nonce. The prompt declares that tag so the model treats the context
as genuine user guidance instead of a prompt injection.
Steering is offered only for daemon-managed workdirs on Unix with cursor-agent
2026.10.14+, and the server allowlists cursor for capability negotiation,
supplement creation and composer binding.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* MUL-8070: fix(cursor): confirm steering only on Cursor's receipt and gate on installable hooks
- Count hook-delivered guidance as delivered only when the tool call's
completed stream event echoes it in hookAdditionalContexts. Guidance
Cursor dropped for the same call is retried at the next tool boundary;
guidance without a receipt fails when the turn ends.
- Offer steering only when the workdir can carry the managed hooks.json,
and fail supplements immediately if installation still fails after
negotiation.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
- issueworkflow.Brief is the only renderer: the claim carries it into every
run's per-turn prompt; the editor preview and timeline show the same text.
- Workflow handoffs stop writing the brief into handoff_note, so a person's
note no longer drops the brief and the handoff framing fits again.
- Agents that are not the issue's assignee get an observer brief without
the step's commands.
- Clearer rule override and narrower wakeup guidance; user text is
stripped of control characters.
- multica-platform skill documents project workflows.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): workspace workflows with step handoffs (MUL-7420)
A workflow is a named, workspace-level selection of statuses from the
shared status catalog, in board order, with an optional handoff on each
step. Projects opt in through project.workflow_id; a project without one
keeps the whole catalog (the implicit Default workflow), so existing
workspaces see no change.
- issue_workflow table + project.workflow_id, CRUD API under
/api/issue-workflows, POST /api/projects/{id}/workflow with a dry-run
status mapping plan. Only issues on statuses the target workflow does
not list are remapped, and remapping never hands issues off.
- Issue writes keep the status inside the project's workflow. Entering a
step with a handler through a status change assigns the issue to that
handler and starts its run with the step's brief as the handoff note;
an explicit assignee in the same write wins. Creating an issue starts
at the workflow's starting step.
- Task claims carry the issue's project workflow, and the brief renders
a Project Workflow section so agents move along this project's
statuses instead of the built-in ones.
- Statuses a workflow lists cannot be archived.
- Adoption is gated by the project_workflows_v1 flag; existing
workflows keep working and projects can always switch back to Default.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): workflow settings, project picker and workflow-aware boards (MUL-7420)
- Settings → Workflows (behind project_workflows_v1): the Default
workflow plus named workflows, each shown as its step chain with
handlers and the projects using it; an editor to pick statuses from the
library, order them, mark the starting step, and set each step's
handoff, instructions and next/back statuses.
- Project Workflow property: switching dry-runs first and asks where
issues on unlisted statuses go; nothing else moves and nothing is
handed off.
- A project board's columns are its workflow's steps, and each column
head names who entering it hands the issue to.
- Status pickers offer only the issue's workflow steps and say who each
hands off to; boards refuse drops onto statuses the dragged card's
workflow does not list.
- New issues in a workflow project start on its starting step, with a
hint naming who they will be handed to.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): name built-in steps once in the brief; roomier picker hints (MUL-7420)
Built-in statuses have no stored name, so the handoff brief printed
`todo` todo. Print the key alone when the name adds nothing.
The status picker widens for workflow projects and shows the handoff as
an arrow and name, so step names no longer truncate to one letter.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): handoff previews and a single timeline entry per handoff (MUL-7420)
- GET /api/issues/{id}/workflow-handoff?status= says what moving an issue
would do: who it goes to, which runs of its current agent are still
active, and the brief the handler receives. Backs the confirmation.
- POST /api/issue-workflows/preview-brief renders a step's brief from an
unsaved workflow, for the editor's preview.
- Mapping plans list the statuses that stay, so the switch dialog can say
which issues are untouched.
- A handoff is recorded on the status_changed activity (workflow, handler,
whether a run started, the brief) instead of a separate assignee row.
- The brief now reads like the one shown in the design: the workflow, the
current step, its instructions, the statuses, and the commands to hand
off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): rebuild the workflow UI to the reviewed design (MUL-7420)
The first cut used generic controls where the design had purpose-built
ones. This brings every surface up to the mockup:
- Settings → Workflows is one page: the workflows (step chain with
handler avatars, handoff count, projects using it; Default first) and,
below, the shared status library with each status's workflow usage.
- The workflow editor is a full page instead of a dialog: draggable step
cards summarising each handoff, and a panel for the selected step —
swap its status, pick the handler with a segmented control and a
searchable target, handoff instructions, next/back statuses, a preview
of the brief the handler receives, duplicate and delete.
- The project Workflow picker lists each workflow's statuses, handoffs and
projects, links to settings, and always confirms the switch with what
moves and what stays.
- Moving an issue into a handoff step (status picker, table, board drop)
asks first: who it goes from and to, a default-on option to stop the
previous agent's run, and the brief.
- Status pickers name the workflow and project, show each step's handler
with an avatar or that the assignee stays, and explain themselves.
- Boards: the handler chip in project column heads, hatched columns a
dragged card cannot enter with the reason, and a drop hint naming who
the card will go to.
- The timeline records a handoff under its status change, with the brief
the run received; the issue sidebar shows the workflow it follows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): word a queued previous run as queued in the handoff confirm
The confirm dialog said the previous agent was "still running (for N
minutes)" even when its task had only been queued. Queued runs now read
as queued, and the checkbox cancels them instead of stopping them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): refuse a run's status change once the issue left its step
A run decides where the issue goes next from the step it was queued for.
When someone else moved the issue while the run worked, applying that
decision undid their move: in the live Grok run, a PM parked NW-1 as
blocked and the still-running agent moved it straight back to code
review.
Tasks now record the issue's status when they are queued
(agent_task_queue.workflow_step, written by the task INSERTs; automatic
retries keep their parent's step), and a run's own status changes move
it along. In a workflow project, an agent status change from a run whose
step no longer matches the issue is refused with 409
workflow_step_moved, naming where the issue is now and who moved it, so
the agent stops instead of retrying.
A handoff away from a squad now also stops the runs made on the squad's
behalf, and stopped runs record who stopped them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): keep every status entry point inside the issue's workflow
The issue context menu listed every catalog status and applied handoff
steps without the confirmation the status picker and board show, so a
right-click could pick a status the workflow refuses or start an agent
unasked. It now offers the workflow's steps and opens the handoff
confirmation through a shared modal.
One-click status actions no longer assume built-in statuses a workflow
may not list: "mark done" lands on the workflow's done step (hidden when
it has none), un-marking a duplicate returns to the workflow's start,
and "mark duplicate" is offered only when the workflow lists cancelled.
The batch toolbar offers a single project's workflow statuses when the
selection sits in one project.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): keep issue wakeups consistent with workflow steps
Three places where project workflows and issue wakeups met:
- A wakeup run is queued when it fires, so it took the issue's status at
that moment as its workflow step. A wakeup an agent registered then
bypassed the stale-step refusal: parked by a PM, the issue could be
moved on by the agent's "check CI, then hand to review" wakeup. It now
keeps the registering run's step; a member's wakeup still starts from
the issue's status.
- Retries carried handoff_note only for wakeup runs, so a retried
workflow handoff lost its brief. Rows with a workflow step keep it.
- Switching or editing a workflow remaps statuses in bulk without the
close rule, so an issue remapped into a done status kept its wakeups
and pending wakeup runs. The remap now ends them like any status write.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(wakeups): don't also wake the agent a write hands the issue to
A workflow handoff or an assignment already starts the receiving agent's
run. If that agent also subscribed an event wakeup to the issue's status
or assignee, the same write queued a second run for one fact, and
workflows made such writes frequent.
The write now names the agent it hands the issue to in the
transaction-local multica.handed_to_agent (the new agent assignee, a
squad's leader, or a step's handler even when it already owned the
issue), and wakeup capture skips that agent's subscriptions for the
write's issue.* events. Other agents' wakeups, and task, comment and
reaction events, are captured as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): tell agents not to watch workflow issues with wakeups
The handoff brief and the runtime's project workflow section now say
that each step's handler gets a run when the issue enters the step, so
wakeups on a workflow issue are for waiting on time or on something
outside it, not on its status, assignee or runs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): refresh project usage after project changes
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): MUL-7420 split columns from per-project handoffs
Workflows now only define a board's columns between a fixed head
(backlog, todo) and tail (blocked, done, cancelled). Handoffs move to
project.workflow_handoffs, so projects can share columns and still hand
off to different people. Default becomes a real, editable row that every
project without a named workflow follows. Adds project handoff, add-column
(project fork or shared) and project brief preview endpoints; same-agent
adjacent columns re-run the agent.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(core): MUL-7420 project handoffs and Default workflow client model
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 column-header handoffs and project workflow menu
Board column headers set the project's handoff for that column; the
project toolbar gets a workflow menu (full flow, switch, templates) and an
add-column action. Views outside a project show Default's columns, with
other statuses reachable through a status filter. Adds the issue flow card,
create-project workflow choice, a columns-only workflow editor and a
lighter status picker.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(views): MUL-7420 wrap create-project workflow label in a menu group
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): MUL-7420 keep workflow structure in settings; no squad handoffs
Projects no longer add columns or create workflows: the add-column
endpoint is removed and creating a workflow is admin-only. Handoffs go
to one agent, a member or the project lead; squads route work themselves,
so a handoff to one is rejected, as is the issue-creator handler.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(core): MUL-7420 drop the project add-column client
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 simpler handoff picker and workflow editor form
The handoff form is one searchable "Hand to" picker (no handoff, the
project lead, an agent or a member); the next status defaults to the next
column and next/back overrides sit under "More options". The workflow
editor gets labelled name and description fields and shows the fixed
statuses as one start and one end row. Templates move from the project
menu to Settings → Workflows → New, and the project board loses its
add-column action.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(server): MUL-7420 keep handoffs on the workflow, make backlog and blocked optional
Handoffs move back onto workflow steps: a workflow is the whole pipeline,
and a project only picks one. The project lead stays a relative handler, so
projects can still share a workflow. Handoff edits are admin-only, through
the workflow write or PUT /api/issue-workflows/{id}/handoffs/{status}; the
brief preview moves to the workflow too. Drops the unreleased
project.workflow_handoffs migration.
Todo, done and cancelled are required; backlog and blocked are optional and
keep their fixed places. Without blocked, the brief points agents at the
step's back status.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Cursor <[email protected]>
* feat(views): MUL-7420 edit handoffs on the workflow, optional backlog and blocked
The workflow editor sets each column's handoff in its side panel, and
Backlog and Blocked switch on and off. The board's column popover and the
flow panel edit the workflow's handoff, say how many projects it reaches,
and are read-only for non-admins. Drops the per-project handoff client
code.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(wakeups): MUL-7420 keep platform rules on writes that hand off nobody
Migration 579 skips an issue.* event for the agent a write hands the issue
to. Its filter compared agent_id with the transaction's handed_to_agent
setting using IS DISTINCT FROM, which is false when both are NULL. Main's
platform rules (child_done) have no agent_id, so every issue.* event that
handed the issue to nobody was dropped for them: a parent leaving backlog
never woke its agent for sub-issues that closed meanwhile
(TestChildDoneHeldWhileParentInBacklog).
Rules without an agent are never the handoff target, so they always
capture. Extends TestHandoffDoesNotAlsoWakeTheHandler with a platform rule
on a handoff and on a plain status change; the latter fails on the old
filter.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 three-way handoff choice; quieter move confirm
The handoff form picks one of three directions, as in the mockup: no
handoff, hand to an agent (starts a run), or hand to a member, project lead
included (assignee only). What the step does, where it goes next and the
back status only reach an agent's brief, so they show and save for agent
handoffs alone.
The board's move confirmation keeps the agent's brief collapsed. Create
project shows the workflow picker even when Default is the only workflow,
with a note on where to make more. The hidden-columns panel says a board's
columns come from its workflow, with a link to manage workflows for admins.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): confirm handoff on project move, per-project handoff copy, two-pane editor (MUL-7420)
- A single issue moved into a workflow project from issue detail confirms
the handoff of the column it lands on; the write opts in with
enter_workflow_step. Batch, API and CLI moves stay administrative.
- The handoff preview takes project_id to preview such a move.
- Saving a shared workflow's handoff from a board can keep the change to
that project: the server copies the workflow for it and switches the
project in one transaction (copy_for_project_id).
- The column popover says which workflow it edits and who else uses it.
- The workflow editor is a step list with a detail panel for the
selected step.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* style: gofmt workflow tests
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 show next/back always; edit workflow name and description in place
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 trim copy on the workflows settings page
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix: MUL-7420 keep pre-workflow behavior for workspaces that never adopt workflows
The run step guard now applies only to workflows that hand off, and the
standalone Statuses settings page keeps its original header.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(views): MUL-7420 use a solid placeholder tone on the workflow header
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): keep the runtime brief cache-stable; fix project board and batch status gaps (MUL-7420)
- Move the project workflow out of the runtime brief into the per-turn
prompt so CLAUDE.md stays byte-identical across steps and issues.
- An explicit status filter overrides the project workflow's columns; the
hidden-columns panel lists statuses outside the workflow.
- Batch status offers only statuses every selected workflow allows; a
partial batch rolls back, refetches and warns.
- setProjectWorkflow returns void instead of an unparsed body.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Lambda <[email protected]>
Co-authored-by: Cursor <[email protected]>
* fix(views): route plain attachment download links through the re-sign path (WK-11)
A plain markdown link to the API-host attachment endpoint
(MULTICA_PUBLIC_URL/api/attachments/<id>/download, persisted by
buildMarkdownURL in private-storage deployments) opened with a bare
navigation. The browser sent no credential there — web's session cookie is
host-locked to the API origin and SameSite=Strict, and Desktop/mobile carry a
bearer token no <a> can send — so the request returned 401
"missing authorization" and the user downloaded the error body, not the file.
File-card/image and the download button already route through
useDownloadAttachment, which refreshes authenticated metadata and navigates to
the credential-bearing URL the server mints per click (capability / presign /
CloudFront). Plain links missed that path. Intercept attachment download URLs
(UUID-guarded via attachmentIdFromDownloadURL) in RichLink and hand the
click to the same download hook, so all three attachment entry points behave
alike. Modified clicks still open natively; the raw URL remains the durable ref.
Add a routing test covering the UUID-link interception and the non-UUID
non-interception.
Fixes WK-11.
* fix(attachments): scope Markdown downloads to their deployment and workspace
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Dev (multica) <agent@multica>
Co-authored-by: Yushen <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Partially reverts #9046. The backend again ships the
multica-working-on-issues redirect stub to daemons that do not
advertise platform-skill-v1, on both inline and skill-ref claims and in
bundle resolution. Released daemons (e.g. v0.4.39) hardcode that skill
name in their brief, and a backend deploy cannot rewrite it.
The daemon-side cleanup from #9046 is kept: current daemons advertise
platform-skill-v1 and point only at multica-platform.
Co-authored-by: multica-agent <[email protected]>