mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-09 01:46:46 +08:00
main
870
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ac131c55cb |
MUL-8416 fix(comments): keep thread owner when root folds into a queued task (#9735)
A member reply merged into a still-queued task moves trigger_comment_id to the reply and leaves the thread root only in coalesced_comment_ids. ListConversationRootOwners matched the root against trigger_comment_id alone, so the thread lost its owner and later un-mentioned replies under the root were silently dropped. Treat a root in coalesced_comment_ids as owned, the same coverage rule taskCoversReplyParent and CancelAgentTasksByTriggerComment already use. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
53c4bbe5cd |
MUL-8395 fix(lark): distinguish bound bots from verified message reception (#9709)
* fix(lark): verify message reception separately from binding * fix(lark): preserve verification evidence during rollout * fix(lark): remove stray space in Chinese verification hint Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Multica Eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
3191de1930 |
MUL-8385 feat(wecom): show the run's steps and reasoning in the bubble while it works (#9696)
* feat(wecom): show the run's steps and reasoning in the bubble while it works The bubble used to open, spin for the whole run and close with the answer. It now fills in while the run is going: each tool call as a sentence in the reader's language, the agent's own reasoning under it, and how long it has been. Only the bot's principal sees any of it, and only in their own 1:1 — a group or anyone else gets the plain spinner, decided again for every round. - Steps come from task:message (the transcript), plus the daemon's first task:progress milestone, which is said as "starting the agent" in the bubble's language rather than the daemon's English. Only runs asked over WeCom (a WeCom channel_task_delivery row) show steps. - Progress reaches the replica that holds the round, whichever replica served the daemon's request. The bus subscriber only classifies the event and queues it on a per-run lane, so the daemon's request does no database read and no socket write. The holder reads the run's new transcript rows past a per-run watermark (ListTaskProgressSince), redacts, throttles and writes the frame. A replica without the round publishes a progress relay frame that carries ids and a seq, no text; it takes no claim and never enters the dispatcher's queues, so it cannot delay or crowd out an answer. Duplicates, replays and out-of-order frames read nothing new, and do not count as a sign of life for the rotation guard. - Credentials are redacted from steps and reasoning: labelled values, known key prefixes, long high-entropy runs, and the same checks on each part of a URL or of a word glued to shell punctuation, plus webhook URLs whose path is the secret. Nothing the renderer strips afterwards can join two pieces into a key the redactor never saw. Reasoning arrives in 500ms increments, so redaction and the </think> defence run over the joined buffer, where a key and its value are seen together, and a run that may still be the start of a key is held back until it has finished. - A bubble near the server's ten-minute window is handed over to a fresh stream; a hand-over the server refuses stops rotation, and any stream the server has disowned takes no further frame — every closer falls back to a plain message instead of spending another refusal. Every frame a step writes, and both frames of a hand-over, take the reply write guard. - The identity lookup is wired on the real boot path, and its absence logs a warning, since without it no bubble ever shows a step. A relay with no typing indicator attached warns once when progress frames arrive. * fix(wecom): show a step the throttle held back without waiting for the next report A step that landed inside the 1.5s refresh window was only shown when the run reported again. The step most worth showing is often the last before a long silence (a file read, then a test suite that runs for minutes), so the bubble sat on the earlier step for the whole wait. A held step now schedules one trailing refresh per feed for when the window ends. It goes through the run's lane like any report, is not counted as a sign of life for the rotation guard, is never published to other replicas, and finds nothing once the round has ended. Co-authored-by: multica-agent <[email protected]> * fix(wecom): keep the reasoning buffer across a bubble hand-over Redaction runs over the joined reasoning buffer so a label and its value split across two increments are seen together. A rotation started that buffer over, so `password=` at the end of the old bubble and `hunter2` at the start of the new one were judged apart and the value went out in the clear. The new bubble now starts with no step lines and a fresh throttle but the same raw reasoning buffer. The old bubble is sealed with the hand-over line, so the reasoning is not shown twice. Also gofmt the two-replica test. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
77c17eb361 |
MUL-8410 fix(qoder): run chat turns on Qoder Cloud Agents (#9726)
* fix(qoder): run chat turns on Qoder Cloud Agents * fix(qoder): address review nits for Qoder chat (MUL-8410) - Tell the agent the chat audience (group/direct/unknown) like the local daemon's chat prompt, so an IM group reply is not treated as private. - Update the qoder-runtime README: chat turns and chat session continuation are supported. - Show conversation starters and the new-chat preview for Qoder agents, and keep starters when creating a Qoder agent. - PriorSessionID comment covers chats as well as issues. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: 安辰 <[email protected]> Co-authored-by: Multica Eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
908456c601 |
feat(wecom): tell the sender when the media budget gives up on an attachment, and count every failure (#9695)
When the Router's media budget ran out, the resolver had either never started or been cut off, so it saw no failure and the notice it owns never fired. The sender's photo was dropped in silence and the agent answered the words beside it as if there were no photo. engine.MediaAbandonNotifier is an optional extra a MediaResolver can implement. On that path the Router calls it on a goroutine of its own, with its own bounded context, started before database finalization and not waited on by it: the bind, the pending-media marker and the task promotion keep their five seconds whatever the chat write costs. Drain still waits for it. The call carries what only the Router knows: why the budget ended, whether ResolveMedia ran, and the refs it dropped. The WeCom resolver says the same "did not arrive" line it says for an unreadable attachment, and drops that line from its own notice when the budget cut it off, so the sender does not read it twice. A "too large" verdict reached before the deadline still goes. Its own notice also goes out on a goroutine now, so ResolveMedia no longer waits on the chat; the Router's Drain still waits for it, through the optional engine.MediaNoticeDrainer it calls once every media job is done. Both notices run on their own context through the language lookup, the write and the revoked-installation guard (guardReply). The two notice lines move into the copy pack and are written in the reader's language. media_failures_total counts each attachment that never reached the agent, once, by reason: unreadable, too_large, blocked_address (the SSRF guard refused where the host resolved), budget_expired, or shutdown. ResolveMedia counts what it failed on, with a deadline it hit counted as budget_expired rather than unreadable; NotifyMediaAbandoned counts the rest, every attachment when ResolveMedia never ran and the dropped refs when it did. It is counted even when there is no socket to tell the sender on. |
||
|
|
354243b459 |
MUL-8386: feat(cli): add workflow and project workflow commands (#9714)
* feat(cli): add workflow and project workflow commands Wrap the existing /api/issue-workflows endpoints and the project workflow switch so agents and scripts can read and change workflows without the web UI. Permission checks stay on the server. Refs #9698 Co-authored-by: multica-agent <[email protected]> * docs(skill): teach the workflow CLI in the multica-platform skill Workflow edits are rare, so the commands live in the projects reference instead of the per-turn brief. Co-authored-by: multica-agent <[email protected]> * fix(cli): keep the mapping plan when a workflow write needs status mappings On a 409 workflow_status_mapping_required, project workflow set and workflow update now print the server's code and full plan to stdout (JSON, or a table with --output table) and the message to stderr, and still exit non-zero. The default formatter kept only the first message. Also fix the zh CLI reference wording for tasks vs runs. Co-authored-by: multica-agent <[email protected]> * fix(cli): keep error bodies up to 64 KiB so structured errors parse newHTTPError kept only 4 KiB of an error response. A workflow's 409 status mapping plan lists one row per status in use and passes 4 KiB at around 45 statuses; the cut body no longer parsed, so the CLI fell back to a generic message and dropped the plan. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
4157f5b26e |
fix(selfhost): pass MULTICA_WECOM_DEFAULT_LOCALE into the backend container (#9691)
router.go reads MULTICA_WECOM_DEFAULT_LOCALE to pick the language the bot writes its own copy in for readers it cannot look a language up for (a group chat, anyone not linked to a Multica account). Neither .env.example nor docker-compose.selfhost.yml named it, so a self-hosted operator had no way to find the knob, and setting it on the host did nothing: compose forwards only what its environment: block lists, and the deployment silently stayed on zh-Hans. Add it to both, and a test that parses router.go for every MULTICA_WECOM_* literal it reads and fails when either file is missing one. |
||
|
|
c066f7ea61 |
MUL-8382: fix(wecom): write the unreadable-message receipt in the sender's language (#9693)
* fix(wecom): write the unreadable-message receipt in the sender's language The one line the read loop sends back for a message it cannot read (a location card, an image callback with no url) was a zh-Hans literal in wecom_channel.go, so a sender whose Multica profile says English got every other notice in English and this one in Chinese. It was the last copy #8831 left as a literal; the lint's pending list counted it. It is now copyPack.UnsupportedMsgType, resolved the way the Replier resolves its notices: the sender's profile language in a 1:1, the deployment's in a room. The read loop gets the lookup through ChannelDeps.Languages, which router.go sets, and RegisterWecom warns when it is nil, the way NewOutboundReplier does, so the boot-path wiring test covers both. The lookup runs only for a message nothing here can read; readable messages take no extra query. * fix(wecom): resolve the receipt copy after the dedup claim A redelivery stopped at the claim no longer pays for the language lookup. Also hoist the logger fallback in RegisterWecom so both warnings share it. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Multica Eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
2083970b51 |
MUL-8374: fix: keep private chat content to the session owner (realtime + REST) (#9677)
* MUL-8374: fix(realtime): deliver private chat content only to the session owner Chat events and chat-task transcript (task:message) frames were fanned out to every connection in the workspace. Route every chat:* event, and task:message for chat tasks, to the chat session creator's user scope instead. Producers stamp the owner on the bus event (from the same chat_session row they already read); an event without an owner is dropped rather than widened. Chat-task lifecycle events keep workspace fanout for agent presence. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(server): keep chat-task content private on REST task reads The transcript endpoint now 404s chat tasks for anyone but the chat session's creator, and the agent run list and workspace task snapshot keep foreign chat-task rows but blank their result and error. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(realtime): scope private chat delivery to the owner's workspace connections User-scope delivery reached the chat creator's connections to every workspace. Those are authorized against their own workspace only, so a creator removed from the chat's workspace kept receiving its transcript and reply through a socket to another workspace. Chat content now goes to a workspace_member scope each connection joins at connect time; it is re-authorized against the chat's workspace on every write. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
f2a6398d6e |
MUL-7257: refactor(server): remove unused compatibility queries and capabilities (#9671)
* refactor(comments): drop redundant RFC3339 fallback parse RFC3339Nano parsing already accepts timestamps without fractional seconds, so the second time.Parse(time.RFC3339) attempt can never succeed where the first failed. Keep the existing 400 responses and add a test pinning second-precision since and the invalid since/before cases. Co-authored-by: multica-agent <[email protected]> * refactor(daemon): stop advertising unread plugin capabilities execution-manifest-v1, agent-skill-v1 and remote-mcp-v1 have had no server-side reader since the Plugin V1 gates were removed in #7144. Drop the constants and the daemon advertisement. Co-authored-by: multica-agent <[email protected]> * refactor(autopilot): remove legacy no-reservation terminal queries UpdateAutopilotRunCompleted, UpdateAutopilotRunFailed and UpdateAutopilotRunSkippedWithResult have no production callers; terminal transitions go through UpdateAutopilotRunTerminalWithQuota. Their generated Params structs were still used as service argument types, so replace them with service-local structs. The reconciler test now seeds its crash window with an explicit fixture UPDATE. Co-authored-by: multica-agent <[email protected]> * refactor(tasks): remove unused StartAgentTask query Production start paths use StartAgentTaskWithSupplement. Move the seven test fixtures to the same query with both capability flags off, which performs the same running transition. The empty-body start handler branch is unchanged. Co-authored-by: multica-agent <[email protected]> * refactor(channels): remove unused pending_fresh queries MarkChannelChatSessionPendingFresh, LockChannelChatSessionPendingFresh and ClearChannelChatSessionPendingFresh have no callers. The context generation path and ClearChannelChatSessionPendingFreshForRevision remain. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
bb3747186e |
feat: let agents steer running turns they could start (MUL-8285) (#9630)
* feat(server): let agents steer running turns they could start (MUL-8285) An agent comment can now be added to another agent's running turn, the way a member steers from the composer: exactly when the comment could start that agent (canInvokeAgent, judged on the human at the top of the chain), never its own turn. - steer_task_ids now work for agent authors; steer: true asks the server to add the comment to the current running turn of every recipient without a named turn. - Agent-authored bindings are capped per turn (10) and per authoring agent (3). The task row is locked in its own statement before the bind, so the cap count sees what an earlier lock holder committed. - An agent comment that steered a turn but was never read is replayed on completion like the mention it replaced. Member receipts keep today's behaviour (the member sees the failure and can retry or resend). - The supplement claim returns author_type and the agent's name. A daemon that does not send task-supplement-author-v1 gets the attribution in the content, so an agent message never reads as a human's. - can_supplement on task-runs is computed for an agent caller by the same rule. Co-authored-by: Tim Smart <[email protected]> Co-authored-by: WitMiao <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(daemon): present an agent's added message as an agent's (MUL-8285) Claims send the task-supplement-author-v1 capability and render agent messages as peer input that cannot replace the objective or override humans. Co-authored-by: Tim Smart <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(cli): add --steer and --steer-active to issue comment add (MUL-8285) Docs: CLI help, CLI_AND_DAEMON.md, the CLI docs in every locale, the squad leader briefing, and a steering section in the multica-platform mentions reference. Co-authored-by: Tim Smart <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix: close the review gaps in agent steering (MUL-8285) - Replay an unread agent steer even when it reached the turn through an implicit route (a worker's reply to its leader): a comment bound to the completing run counts as planned input for the replay filter. - Drop the receipt of an agent steer once completion has replayed it, so no client (old or new) offers to resend what the follow-up already handles. - Never hand an agent's message to a daemon without task-supplement-author-v1: it would frame it as a human's. The message stays pending, settles when the turn ends, and is replayed as a mention. - Tell a squad leader about --steer only when its daemon advertises cli-comment-steer-v1, since an older CLI rejects the flag; the skill reference says to drop the flag on 'unknown flag'. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Tim Smart <[email protected]> Co-authored-by: WitMiao <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
b20cea33ad |
fix(desktop): wait for daemon shutdown before quitting (MUL-8290) (#9590)
* fix(desktop): wait for daemon shutdown before quitting * fix(desktop): clarify daemon quit progress and dialogs * fix(desktop): preserve stopped daemon after cancelling quit * fix(desktop): suppress deferred restart after cancelled quit * fix(daemon): require explicit cleanup confirmation before quit Record a profile-scoped, PID-matched shutdown result before releasing the daemon lock. Keep cleanup timeouts and provider cleanup_unconfirmed results sticky, and reject absent, malformed, stale or negative results in the forced stop path. Co-authored-by: multica-agent <[email protected]> * fix(daemon): keep unconfirmed cleanup sticky across repeated stops When a stopped daemon left no matching shutdown result (for example a daemon started by an older CLI, or a stale record from an earlier daemon), the first forced stop correctly failed, but a repeated stop saw an idle profile with no result file and reported success. Record the unconfirmed outcome on failure so cancelling the quit dialog and quitting again still requires an explicit ignore. A profile that never ran a daemon still stops as an idle no-op, and the next daemon start replaces the record. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
a8e7561421 |
[MUL-8300] fix(lark): resolve outbound references to workspace links (#9604)
* fix(lark): resolve outbound references to workspace links * test(wecom): use normal budget for no-holder state assertion * fix(channel): preserve Markdown reference source boundaries * fix(lark): format references with one workspace lookup * fix(deps): update x/net for HTML parser advisory --------- Co-authored-by: Frezc <[email protected]> |
||
|
|
d7d1831fcf |
feat(cli): add run-targeted retries with a fresh option (#9592)
Co-authored-by: multica-agent <[email protected]> |
||
|
|
188971168d |
fix(runtimes): spell out Qoder Cloud Agents instead of QCA (MUL-8282) (#9581)
* 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]> |
||
|
|
bcf40040e0 |
MUL-8233: fix Pi cancellation and guard unconfirmed cleanup (#9557)
* fix(agent): join Pi cancellation cleanup and guard continuation Co-authored-by: multica-agent <[email protected]> * fix(agent): preserve Pi completion and negotiate cleanup Co-authored-by: multica-agent <[email protected]> * perf(agent): avoid Pi exit delay and index cleanup guards Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Multica Eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
7c50af7b66 |
MUL-8094: feat(wakeups): accept blocked in --until-issue-state (#9553)
* feat(wakeups): accept blocked in --until-issue-state Lets a coordinator wait for a specific child to reach blocked, alongside the existing in_review handoff, without waiting for the child to close. Exact status key match, same as in_review. Refs #9344 Co-authored-by: multica-agent <[email protected]> * fix(i18n): keep blocked state key in lowercase English in zh wakeup copy Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
f4250e4298 |
MUL-8215: fix(runtimes): show Qoder Cloud Agents without encryption setup
Merged via Multica issue MUL-8215 approval. |
||
|
|
8a95f0d3b9 |
MUL-8205 feat(lark): deliver reply images and files (#9477)
* feat(lark): deliver reply images and files Based on #8332 by @zprial, addressing its review of bounded delivery, resource uploads, uncertain sends, file fallback and HTTP boundary tests. Render bound images with the reply text in one card, pair video/audio uploads with their message types, preserve pending-send uncertainty and normalize Markdown destinations before matching bound attachments. Keep runtime artifact ingestion and deployment-specific configuration outside this change. Add transport, ownership and rendering regressions and document the behavior in all supported languages. * refactor(lark): scope attachment transfer slots to the patcher The concurrency semaphore was a package-level global while the admission and pending tiers lived on each Patcher. Move it onto the Patcher so all three limits share one scope and tests no longer mutate global state. MUL-8205 Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
b5477490e4 |
MUL-7376: fix(openclaw): preserve agent identity with persistent model overrides (#9223)
* 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]> |
||
|
|
b51f42c54b |
MUL-8200: fix(autopilot): don't fail create_issue runs on issue blocked (#9472)
* fix(autopilot): settle create_issue runs by first task outcome, not issue blocked (MUL-8200) Co-authored-by: multica-agent <[email protected]> * fix(autopilot): settle create_issue runs only from their own dispatch chain (MUL-8200 review) Co-authored-by: multica-agent <[email protected]> * fix(autopilot): root create_issue dispatch chain at the recorded task and let its outcome win (MUL-8200 re-review) Co-authored-by: multica-agent <[email protected]> * fix(autopilot): resolve unlinked create_issue dispatch by agent and wait for pending enqueue (MUL-8200 review) Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
72d9058cc6 |
feat(telemetry): add visiting and delegating member counts (MUL-8161) (#9435)
* feat(telemetry): report visiting and delegating member counts in self-host snapshot v2 (MUL-8161) Co-authored-by: multica-agent <[email protected]> * fix(telemetry): count delegating members from a human-action ledger, drop v1 fallback (MUL-8161) - member_delegation_day records (member, UTC day) at the human action itself, so retries, system wakeups and creator-fallback runs never count, and a comment folded into a queued run keeps both humans and their own days. - Activity read failures now fail collection and use the existing retry instead of silently sending a v1 snapshot. Co-authored-by: multica-agent <[email protected]> * fix(telemetry): record create-with-assignee, honor DO_NOT_TRACK, build ledger PK concurrently (MUL-8161) - A member creating an issue assigned to an agent or squad now records a delegation day, on both the immediate and deferred create paths. - TaskService.RecordMemberActivity gates the ledger and is set from the startup telemetry config, so DO_NOT_TRACK stops collection too. - member_delegation_day's primary key is built with CREATE UNIQUE INDEX CONCURRENTLY + PRIMARY KEY USING INDEX (589-591), with cleanup registered. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
e9b8868e81 |
MUL-8138: fix(auth): reject task-token requests targeting another workspace (#9422)
* fix(auth): reject task-token requests that target a different workspace A mat_ task token is bound to one workspace. Auth overwrote X-Workspace-ID with that binding and both workspace resolvers short-circuit on task_token, so a request that explicitly named workspace B (header, query, or slug) silently ran against the token's workspace A. Before the overwrite, Auth now checks every value of X-Workspace-ID, X-Workspace-Slug, ?workspace_id and ?workspace_slug: a different workspace returns 403, a malformed ID 400, an unknown slug 404, a slug lookup failure 500. No-selector and same-workspace requests, JWT/PAT behaviour and the existing URL-param binding check are unchanged. Fixes #9403 Co-authored-by: multica-agent <[email protected]> * fix(auth): reject task-token requests with unparseable query strings r.URL.Query() silently drops pairs it cannot decode, so a selector like workspace_slug=ws-b%zz or workspace_id=<B>;x=1 was read as absent and the request fell through to the token's workspace. Parse RawQuery strictly with url.ParseQuery and return 400 before any handler runs. Addresses review on #9422. Co-authored-by: multica-agent <[email protected]> * test(server): stop sending a conflicting workspace in source-context guard test The test spoofed X-Workspace-ID to prove Auth overwrites it. Auth now rejects a conflicting selector with 403 (#9403), which made the sibling route return 403 instead of the downstream 404 the test asserts. Drop the spoofed workspace so the 403s come only from the human-only route guard. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
ac61e5ed32 |
fix(runtimes): rename to Qoder Cloud Agents and use Qoder's current logo (MUL-8153) (#9418)
* 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]> |
||
|
|
da278c7d42 |
fix(auth): block machine credentials from minting long-lived tokens (MUL-8154) (#9421)
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]> |
||
|
|
0fc6794821 |
MUL-7035 feat(cli): add comparison operators to issue list --property (#8001)
* feat(cli): add comparison operators to issue list --property `issue list --property` now accepts the comparison spellings #7664 reserved: > >= < <= on number, > (after) and < (before) on date, and ~= (contains) on text and url. Each comparison is sent as the {op, value} member the server has accepted since v0.4.38 (#7765); "Name=Value", __none__ and repeated-flag OR keep their meaning. A comparison the property's type cannot honour is rejected with the type named, because the server checks the value shape but not the op against the definition, and an empty page would read like a real answer. != is rejected because the server has no not-equal filter, and >= or <= on a date is rejected with the strict form to use instead. Closes #7999 * fix(cli): treat multi_text and multi_url as equality-only in property filters main added the free-form list types (#8559) after this branch was cut, and the web UI's PROPERTY_FILTER_OPS_BY_TYPE gives them no operators. Without an entry here a comparison on them reported the type as unknown and told the user to upgrade an already-current CLI. They now get the same 'only support =' rejection as the other equality-only types. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Nick Webster <[email protected]> Co-authored-by: Multica Eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
7ebaf00e7c |
MUL-8136: feat(server): restrict workspace creation to listed emails (self-host) (#9411)
* 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]> |
||
|
|
c9b6de8567 |
MUL-8142: docs(runtimes): step-by-step Qoder Cloud Agent setup and correct entry path (#9408)
Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
0fe2ebf02e |
MUL-8045: feat(runtimes): Qoder Cloud Agent as a managed cloud runtime (#9402)
* feat(runtimes): add Qoder Cloud Agent managed remote execution * fix(qoder): satisfy migration and localization CI contracts * fix(qoder): bound execution and scope managed credentials * fix(qoder): avoid upstream migration number collisions * fix(qoder): gate issue creation and correlate tool results * fix(qoder): synchronize guarded issue lifecycle transitions * test(agent): publish Hermes holder identity atomically * feat(agents): runtime-first create flow and cloud runtimes section for Qoder Cloud Agent - Three-step create-agent flow (runtime -> configure -> done) with local, remote and cloud sections; Qoder connect form inline in the runtime step - Runtimes page regrouped into remote and cloud sections with Qoder card - Generalize Qoder storage to cloud_agent_connection (migrations 586/587) - Locales for en, zh-Hans, ja, ko, fr; desktop and web routes for done step - Fix status-picker fixture owner_id and diagnostics route coverage after main merge Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(runtimes): unify runtimes page into machines and cloud services - One Add menu replaces Add remote runtime, the duplicated Add a computer and the web-only Cloud Runtime header button - Group by machines / cloud services instead of local / remote / cloud, so web and desktop share one structure; this device is pinned with a badge - Cloud services use the machine row anatomy in a single ListGrid table; unconnected Qoder is a row with Connect, coming-soon providers are rows - Empty groups show the next step inline; search and online/offline filter - Rename computer to machine across runtimes copy in all locales Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * test(runtimes): cover unified runtimes page; pin status and action tracks Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * refactor(runtimes): trim redundant descriptive copy on runtimes page Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(runtimes): keep row actions visible at narrow widths Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * refactor(runtimes): align Qoder add-menu item with other cloud services Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(runtimes): close the Qoder dialog after a first-time connection Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(agents): pick the runtime from one grouped list The runtime step showed four kinds of cards, a comparison panel and per-section descriptions. It is now one list in the runtimes-page layout: machines (this one open, others folded to a row) and cloud services. An unconnected Qoder opens the runtimes-page connect dialog in place and is selected once it comes online. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(agents): show owners as 'you' and bare version numbers in the runtime list Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(qoder): disconnect action and dedicated Qoder runtime page Disconnect removes the encrypted connection and the managed runtime, unbinding its agents after the admin confirms them. The Qoder row now opens its own page (status, agents, usage, configuration, pause/resume, disconnect) instead of the machine layout; generic runtime delete and visibility refuse a managed runtime. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(agents): land on the done page after creating from step 2 on desktop Desktop's location is the tab URL, which moves to /agents/new/done/<id> while the configure page is still mounted. Its missing ?runtime= tripped the skipped-step-1 redirect and replaced the tab back to /agents/new, so Create looked like it did nothing. Only redirect on the manual route. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(agents): show only settings a Qoder agent can run with The Qoder bridge fails every task whose agent carries a model, thinking or speed override, env, custom args, custom skills, MCP config or connected apps, and it runs issues only. The agent detail page still offered all of those editors, so one save could break every later run. For a qoder_cloud agent the page now keeps Instructions under Capabilities, General / Access / QCA Agent under Settings, drops the model rows and the header model chip, shows the QCA agent in the summary, and locks the runtime. Other agents no longer see the Qoder runtime in their picker, since moving onto it needs a QCA agent the server would otherwise reject. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(agents): hide conversation starters for Qoder agents Starters only appear in a chat, and the Qoder bridge runs issues only. The create flow already leaves them out for this runtime. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(runtimes): show the Qoder connection as cards with per-section editing The Qoder runtime page rendered the whole connection as one always-editable form. It now summarizes the connection, repositories and limits as read-only cards; each section opens its own dialog and saves only that section. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(qoder): reuse a follow-up session only with the same repository and credential Resolve repository credentials before deciding to continue a prior session, and stamp each session with a fingerprint of its repositories, branches and tokens. A follow-up continues only when the fingerprint still matches, so a changed repository, branch or rotated/revoked token never rides into an old session. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(qoder): keep a paused connection paused when its settings are saved Saving repositories, limits or the connection no longer re-enables a paused runtime. Resume goes through a new POST /qoder/resume. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(agents): drop settings a Qoder agent cannot run when creating one A draft started on another runtime could carry hidden skills, model overrides and starters into a Qoder agent that then failed every task. The create request now drops them for Qoder, and the API rejects skills and execution overrides on Qoder agents at save time. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * chore(db): regenerate sqlc models for cloud_agent_connection Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(runtimes): never list the Qoder runtime under Machines When the server could not run Qoder (no MULTICA_VCS_SECRET_KEY / MULTICA_QODER_STATE_DIR), the managed Qoder runtime fell back into the Machines group as if it were a computer. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * refactor(qoder): drop MULTICA_QODER_STATE_DIR The managed bridge keeps its run journal encrypted in cloud_agent_connection and a database advisory lock already elects one supervisor, so the state directory only held a redundant file lock and acted as a feature switch. Qoder is now available whenever MULTICA_VCS_SECRET_KEY is set. The standalone qoder-runtime command still uses its on-disk state_dir. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(qoder): keep the branch in the session fingerprint after a journal restore Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(agents): validate Qoder agents' effective settings on every write Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: 安辰 <[email protected]> Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
e720fe2219 |
MUL-8135: feat(wakeups): wake on linked PR merge conflicts (--until-pr conflicts) (#9406)
* MUL-8135: feat(wakeups): wake on linked PR merge conflicts (--until-pr conflicts) Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * MUL-8135: fix(wakeups): conflict wakes per PR identity, rotate the watch sweep - A linked-PR condition only fires for a PR key the rule has not seen; a PR leaving the satisfying set no longer re-wakes the agent for the others. - Conflict keys use the PR id, so same-numbered fork PRs on one head stay distinct. - The conflict-watch sweep rotates an address cursor so failing fetches cannot starve the rest. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * MUL-8135: fix(wakeups): count repeated linked-PR keys when deduping Checks and merged keys carry only the PR number, so same-numbered PRs from two repositories share a key. Compare occurrence counts so the second one satisfying still wakes the agent. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
0beaeddc0e |
MUL-8120 feat(workflows): owner-based workflow edit/delete permissions (#9388)
* MUL-8120 feat(workflows): record workflow owner and let owners edit their workflows Any workspace member may now create a workflow and becomes its owner. Editing, deleting and changing handoffs is open to the owner and to workspace owners/admins. Default has no owner and stays admin-only. Copying a workflow for one project now also requires managing that project. Migration 585 adds issue_workflow.owner_id and backfills existing named workflows with the workspace's earliest owner. Co-authored-by: multica-agent <[email protected]> * MUL-8120 feat(workflows): mirror owner-based workflow permissions in the UI Members can create and duplicate workflows; the editor, column handoff and flow panel are editable by the workflow's owner and admins. The save-for-this-project option only shows to admins and the project lead. Co-authored-by: multica-agent <[email protected]> * MUL-8120 fix(workflows): only admins add statuses through workflow writes Workflow create/update could add statuses to the shared workspace catalog without a role check. Non-admins now get 403 status_create_forbidden when a step names a status that does not exist; reusing a status by key or by name stays open. Co-authored-by: multica-agent <[email protected]> * MUL-8120 fix(workflows): hide new-status for members in the workflow editor Members pick from existing statuses only. A template that needs statuses the workspace lacks shows which ones and keeps Save disabled until they are removed or an admin adds them. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
1e01173fb4 |
MUL-8060 fix(daemon): trust the backend's SSL_CERT_DIR CAs when syncing repos (#9312)
* 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]> |
||
|
|
d908e4882d |
MUL-6710: fix(runtimes): revoke access when making runtime private (#7580)
* fix(runtimes): revoke access when making runtime private * fix(runtimes): regenerate queries for workflow step --------- Co-authored-by: m2keloop <[email protected]> |
||
|
|
467550728c |
fix: preserve targetless quick actions on rollback (#9333)
Co-authored-by: multica-agent <[email protected]> |
||
|
|
dfa8bdaa53 |
MUL-8070: feat(cursor): support live-run steering via Cursor hooks (#9322)
* MUL-8070: feat(cursor): support live-run steering via Cursor hooks Cursor's headless --print mode has no mid-turn input channel, but its postToolUse/postToolUseFailure hooks can return additional_context that reaches the model in the same turn. A steerable Cursor run now writes a managed .cursor/hooks.json whose command is the multica binary in a hidden relay mode; the relay forwards the hook event to a token-protected loopback endpoint owned by the run, which hands back pending guidance tagged with a per-run nonce. The prompt declares that tag so the model treats the context as genuine user guidance instead of a prompt injection. Steering is offered only for daemon-managed workdirs on Unix with cursor-agent 2026.10.14+, and the server allowlists cursor for capability negotiation, supplement creation and composer binding. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * MUL-8070: fix(cursor): confirm steering only on Cursor's receipt and gate on installable hooks - Count hook-delivered guidance as delivered only when the tool call's completed stream event echoes it in hookAdditionalContexts. Guidance Cursor dropped for the same call is retried at the next tool boundary; guidance without a receipt fails when the turn ends. - Offer steering only when the workdir can carry the managed hooks.json, and fail supplements immediately if installation still fails after negotiation. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
da1671ca20 |
MUL-7420: Project workflows — columns and handoffs on the workflow (#8814)
* feat(workflows): workspace workflows with step handoffs (MUL-7420)
A workflow is a named, workspace-level selection of statuses from the
shared status catalog, in board order, with an optional handoff on each
step. Projects opt in through project.workflow_id; a project without one
keeps the whole catalog (the implicit Default workflow), so existing
workspaces see no change.
- issue_workflow table + project.workflow_id, CRUD API under
/api/issue-workflows, POST /api/projects/{id}/workflow with a dry-run
status mapping plan. Only issues on statuses the target workflow does
not list are remapped, and remapping never hands issues off.
- Issue writes keep the status inside the project's workflow. Entering a
step with a handler through a status change assigns the issue to that
handler and starts its run with the step's brief as the handoff note;
an explicit assignee in the same write wins. Creating an issue starts
at the workflow's starting step.
- Task claims carry the issue's project workflow, and the brief renders
a Project Workflow section so agents move along this project's
statuses instead of the built-in ones.
- Statuses a workflow lists cannot be archived.
- Adoption is gated by the project_workflows_v1 flag; existing
workflows keep working and projects can always switch back to Default.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): workflow settings, project picker and workflow-aware boards (MUL-7420)
- Settings → Workflows (behind project_workflows_v1): the Default
workflow plus named workflows, each shown as its step chain with
handlers and the projects using it; an editor to pick statuses from the
library, order them, mark the starting step, and set each step's
handoff, instructions and next/back statuses.
- Project Workflow property: switching dry-runs first and asks where
issues on unlisted statuses go; nothing else moves and nothing is
handed off.
- A project board's columns are its workflow's steps, and each column
head names who entering it hands the issue to.
- Status pickers offer only the issue's workflow steps and say who each
hands off to; boards refuse drops onto statuses the dragged card's
workflow does not list.
- New issues in a workflow project start on its starting step, with a
hint naming who they will be handed to.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): name built-in steps once in the brief; roomier picker hints (MUL-7420)
Built-in statuses have no stored name, so the handoff brief printed
`todo` todo. Print the key alone when the name adds nothing.
The status picker widens for workflow projects and shows the handoff as
an arrow and name, so step names no longer truncate to one letter.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): handoff previews and a single timeline entry per handoff (MUL-7420)
- GET /api/issues/{id}/workflow-handoff?status= says what moving an issue
would do: who it goes to, which runs of its current agent are still
active, and the brief the handler receives. Backs the confirmation.
- POST /api/issue-workflows/preview-brief renders a step's brief from an
unsaved workflow, for the editor's preview.
- Mapping plans list the statuses that stay, so the switch dialog can say
which issues are untouched.
- A handoff is recorded on the status_changed activity (workflow, handler,
whether a run started, the brief) instead of a separate assignee row.
- The brief now reads like the one shown in the design: the workflow, the
current step, its instructions, the statuses, and the commands to hand
off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): rebuild the workflow UI to the reviewed design (MUL-7420)
The first cut used generic controls where the design had purpose-built
ones. This brings every surface up to the mockup:
- Settings → Workflows is one page: the workflows (step chain with
handler avatars, handoff count, projects using it; Default first) and,
below, the shared status library with each status's workflow usage.
- The workflow editor is a full page instead of a dialog: draggable step
cards summarising each handoff, and a panel for the selected step —
swap its status, pick the handler with a segmented control and a
searchable target, handoff instructions, next/back statuses, a preview
of the brief the handler receives, duplicate and delete.
- The project Workflow picker lists each workflow's statuses, handoffs and
projects, links to settings, and always confirms the switch with what
moves and what stays.
- Moving an issue into a handoff step (status picker, table, board drop)
asks first: who it goes from and to, a default-on option to stop the
previous agent's run, and the brief.
- Status pickers name the workflow and project, show each step's handler
with an avatar or that the assignee stays, and explain themselves.
- Boards: the handler chip in project column heads, hatched columns a
dragged card cannot enter with the reason, and a drop hint naming who
the card will go to.
- The timeline records a handoff under its status change, with the brief
the run received; the issue sidebar shows the workflow it follows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): word a queued previous run as queued in the handoff confirm
The confirm dialog said the previous agent was "still running (for N
minutes)" even when its task had only been queued. Queued runs now read
as queued, and the checkbox cancels them instead of stopping them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): refuse a run's status change once the issue left its step
A run decides where the issue goes next from the step it was queued for.
When someone else moved the issue while the run worked, applying that
decision undid their move: in the live Grok run, a PM parked NW-1 as
blocked and the still-running agent moved it straight back to code
review.
Tasks now record the issue's status when they are queued
(agent_task_queue.workflow_step, written by the task INSERTs; automatic
retries keep their parent's step), and a run's own status changes move
it along. In a workflow project, an agent status change from a run whose
step no longer matches the issue is refused with 409
workflow_step_moved, naming where the issue is now and who moved it, so
the agent stops instead of retrying.
A handoff away from a squad now also stops the runs made on the squad's
behalf, and stopped runs record who stopped them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): keep every status entry point inside the issue's workflow
The issue context menu listed every catalog status and applied handoff
steps without the confirmation the status picker and board show, so a
right-click could pick a status the workflow refuses or start an agent
unasked. It now offers the workflow's steps and opens the handoff
confirmation through a shared modal.
One-click status actions no longer assume built-in statuses a workflow
may not list: "mark done" lands on the workflow's done step (hidden when
it has none), un-marking a duplicate returns to the workflow's start,
and "mark duplicate" is offered only when the workflow lists cancelled.
The batch toolbar offers a single project's workflow statuses when the
selection sits in one project.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): keep issue wakeups consistent with workflow steps
Three places where project workflows and issue wakeups met:
- A wakeup run is queued when it fires, so it took the issue's status at
that moment as its workflow step. A wakeup an agent registered then
bypassed the stale-step refusal: parked by a PM, the issue could be
moved on by the agent's "check CI, then hand to review" wakeup. It now
keeps the registering run's step; a member's wakeup still starts from
the issue's status.
- Retries carried handoff_note only for wakeup runs, so a retried
workflow handoff lost its brief. Rows with a workflow step keep it.
- Switching or editing a workflow remaps statuses in bulk without the
close rule, so an issue remapped into a done status kept its wakeups
and pending wakeup runs. The remap now ends them like any status write.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(wakeups): don't also wake the agent a write hands the issue to
A workflow handoff or an assignment already starts the receiving agent's
run. If that agent also subscribed an event wakeup to the issue's status
or assignee, the same write queued a second run for one fact, and
workflows made such writes frequent.
The write now names the agent it hands the issue to in the
transaction-local multica.handed_to_agent (the new agent assignee, a
squad's leader, or a step's handler even when it already owned the
issue), and wakeup capture skips that agent's subscriptions for the
write's issue.* events. Other agents' wakeups, and task, comment and
reaction events, are captured as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): tell agents not to watch workflow issues with wakeups
The handoff brief and the runtime's project workflow section now say
that each step's handler gets a run when the issue enters the step, so
wakeups on a workflow issue are for waiting on time or on something
outside it, not on its status, assignee or runs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): refresh project usage after project changes
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): MUL-7420 split columns from per-project handoffs
Workflows now only define a board's columns between a fixed head
(backlog, todo) and tail (blocked, done, cancelled). Handoffs move to
project.workflow_handoffs, so projects can share columns and still hand
off to different people. Default becomes a real, editable row that every
project without a named workflow follows. Adds project handoff, add-column
(project fork or shared) and project brief preview endpoints; same-agent
adjacent columns re-run the agent.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(core): MUL-7420 project handoffs and Default workflow client model
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 column-header handoffs and project workflow menu
Board column headers set the project's handoff for that column; the
project toolbar gets a workflow menu (full flow, switch, templates) and an
add-column action. Views outside a project show Default's columns, with
other statuses reachable through a status filter. Adds the issue flow card,
create-project workflow choice, a columns-only workflow editor and a
lighter status picker.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(views): MUL-7420 wrap create-project workflow label in a menu group
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): MUL-7420 keep workflow structure in settings; no squad handoffs
Projects no longer add columns or create workflows: the add-column
endpoint is removed and creating a workflow is admin-only. Handoffs go
to one agent, a member or the project lead; squads route work themselves,
so a handoff to one is rejected, as is the issue-creator handler.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(core): MUL-7420 drop the project add-column client
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 simpler handoff picker and workflow editor form
The handoff form is one searchable "Hand to" picker (no handoff, the
project lead, an agent or a member); the next status defaults to the next
column and next/back overrides sit under "More options". The workflow
editor gets labelled name and description fields and shows the fixed
statuses as one start and one end row. Templates move from the project
menu to Settings → Workflows → New, and the project board loses its
add-column action.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(server): MUL-7420 keep handoffs on the workflow, make backlog and blocked optional
Handoffs move back onto workflow steps: a workflow is the whole pipeline,
and a project only picks one. The project lead stays a relative handler, so
projects can still share a workflow. Handoff edits are admin-only, through
the workflow write or PUT /api/issue-workflows/{id}/handoffs/{status}; the
brief preview moves to the workflow too. Drops the unreleased
project.workflow_handoffs migration.
Todo, done and cancelled are required; backlog and blocked are optional and
keep their fixed places. Without blocked, the brief points agents at the
step's back status.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Cursor <[email protected]>
* feat(views): MUL-7420 edit handoffs on the workflow, optional backlog and blocked
The workflow editor sets each column's handoff in its side panel, and
Backlog and Blocked switch on and off. The board's column popover and the
flow panel edit the workflow's handoff, say how many projects it reaches,
and are read-only for non-admins. Drops the per-project handoff client
code.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(wakeups): MUL-7420 keep platform rules on writes that hand off nobody
Migration 579 skips an issue.* event for the agent a write hands the issue
to. Its filter compared agent_id with the transaction's handed_to_agent
setting using IS DISTINCT FROM, which is false when both are NULL. Main's
platform rules (child_done) have no agent_id, so every issue.* event that
handed the issue to nobody was dropped for them: a parent leaving backlog
never woke its agent for sub-issues that closed meanwhile
(TestChildDoneHeldWhileParentInBacklog).
Rules without an agent are never the handoff target, so they always
capture. Extends TestHandoffDoesNotAlsoWakeTheHandler with a platform rule
on a handoff and on a plain status change; the latter fails on the old
filter.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 three-way handoff choice; quieter move confirm
The handoff form picks one of three directions, as in the mockup: no
handoff, hand to an agent (starts a run), or hand to a member, project lead
included (assignee only). What the step does, where it goes next and the
back status only reach an agent's brief, so they show and save for agent
handoffs alone.
The board's move confirmation keeps the agent's brief collapsed. Create
project shows the workflow picker even when Default is the only workflow,
with a note on where to make more. The hidden-columns panel says a board's
columns come from its workflow, with a link to manage workflows for admins.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): confirm handoff on project move, per-project handoff copy, two-pane editor (MUL-7420)
- A single issue moved into a workflow project from issue detail confirms
the handoff of the column it lands on; the write opts in with
enter_workflow_step. Batch, API and CLI moves stay administrative.
- The handoff preview takes project_id to preview such a move.
- Saving a shared workflow's handoff from a board can keep the change to
that project: the server copies the workflow for it and switches the
project in one transaction (copy_for_project_id).
- The column popover says which workflow it edits and who else uses it.
- The workflow editor is a step list with a detail panel for the
selected step.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* style: gofmt workflow tests
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 show next/back always; edit workflow name and description in place
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 trim copy on the workflows settings page
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix: MUL-7420 keep pre-workflow behavior for workspaces that never adopt workflows
The run step guard now applies only to workflows that hand off, and the
standalone Statuses settings page keeps its original header.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(views): MUL-7420 use a solid placeholder tone on the workflow header
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): keep the runtime brief cache-stable; fix project board and batch status gaps (MUL-7420)
- Move the project workflow out of the runtime brief into the per-turn
prompt so CLAUDE.md stays byte-identical across steps and issues.
- An explicit status filter overrides the project workflow's columns; the
hidden-columns panel lists statuses outside the workflow.
- Batch status offers only statuses every selected workflow allows; a
partial batch rolls back, refetches and warns.
- setProjectWorkflow returns void instead of an unparsed body.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Lambda <[email protected]>
Co-authored-by: Cursor <[email protected]>
|
||
|
|
86020b88a4 |
MUL-8047: chore(server): remove the pre-v3 desktop onboarding shim (#9291)
Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
231dedb552 |
MUL-8034: chore: clean historical comments across the codebase (#9274)
* chore: clean historical comments (MUL-8034) Co-authored-by: multica-agent <[email protected]> * docs: preserve pending Composio expiry contract Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
d3c1b44d75 |
MUL-8014: fix(realtime): revoke workspace WebSocket access (#9249)
* fix(realtime): revoke workspace access on membership removal Close removed members' sockets, revalidate registration, and gate captured write batches with fresh coalesced membership queries. Cover relay failure, replay, queue races, and transactional removal/rollback (MUL-8014). Co-authored-by: multica-agent <[email protected]> * fix(realtime): keep registration free of database waits Co-authored-by: multica-agent <[email protected]> * perf(realtime): narrow removal event prefilter Co-authored-by: multica-agent <[email protected]> * test(wakeup): avoid global backfill in workspace default test Co-authored-by: multica-agent <[email protected]> * fix(realtime): bound concurrent authorization and defer relay revocation Co-authored-by: multica-agent <[email protected]> * test(realtime): check caller budget without waiting for expiry Co-authored-by: multica-agent <[email protected]> * fix(realtime): decouple member-list invalidation from revocation Co-authored-by: multica-agent <[email protected]> * perf(realtime): skip removal decoding for member actor metadata Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
52b26900c5 |
MUL-8012: fix(inbox): restrict notifications to current workspace recipients (#9242)
* fix(inbox): restrict notifications to current workspace recipients Removing a member or leaving a workspace deletes their subscriptions, but several notification sources keep addressing the user by a retained ID: reactions to their issues and comments, retained assignments, explicit mentions, squad mentions through surviving squad-member rows, quick-create outcomes and children_done for a retained parent assignee. CreateInboxItem inserted those rows unconditionally and the inbox:new event was then sent to the user's own connections, so a former member could keep receiving new issue titles and comment content. Guard the shared insert instead of each source: a member recipient must still be a member of the item's workspace and an agent recipient must belong to it. An ineligible recipient yields no row (pgx.ErrNoRows), which every caller now treats as a quiet skip without publishing inbox:new or failing the underlying operation. The check runs in the statement's snapshot and takes no row locks; historical inbox rows are left untouched. Existing delivery tests created recipients without membership rows; their fixtures now add the membership the production path requires. * test(inbox): assert children_done settles receipts when the recipient left * refactor(inbox): centralize eligible notification writes Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: 千乘妍 (Xiaoyaner) <[email protected]> Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
191c782d60 |
MUL-7988: recover daemon when profile health port is unavailable (#9217)
* fix(daemon): recover unavailable profile health ports Co-authored-by: multica-agent <[email protected]> * fix(daemon): wait for profile shutdown before restart Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Yushen <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
c6b451c9c1 |
fix(views): route plain attachment download links through the re-sign path (MUL-7991) (#9211)
* 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]> |
||
|
|
53b1baba0a |
feat(cli): add opt-in daemon autostart (MUL-7731) (#8864)
* fix(test): isolate cmd/multica suite home resolution on Windows os.UserHomeDir reads USERPROFILE, not HOME, so every test that redirected only HOME kept resolving ~/.multica against the real home: mkProfiles stopped isolating at all, and a full-suite run wrote SaveCLIConfig fixtures over a real default-profile config.json. Redirect HOME and USERPROFILE process-wide in TestMain, and make mkProfiles redirect both so per-test isolation holds on Windows too. On unix nothing changes: USERPROFILE is unused there. Co-authored-by: multica-agent <[email protected]> * feat(cli): register daemon boot autostart on every platform multica daemon start now registers the profile's daemon to come back at login/boot so a reboot no longer takes the runtime offline: an HKCU Run key on Windows, a launchd LaunchAgent on macOS, and a systemd user unit (or XDG autostart fallback without systemd) on Linux. New 'daemon autostart enable|disable|status' manages the registration; --no-autostart opts a single start out, and daemons spawned by a manager (MULTICA_LAUNCHED_BY, i.e. the Desktop app's own start-at-login pref) are never registered. The registered command runs 'daemon start --foreground' per profile and snapshots the shell's PATH so login sessions keep finding agent CLIs. EnsureHiddenConsole now hides a console that exists only for the daemon itself — a Run-key launch would otherwise park a visible console window for the daemon's whole lifetime. Co-authored-by: multica-agent <[email protected]> * refactor(cli): make boot autostart registration opt-in per review Registration now happens only through 'multica daemon autostart enable': daemon start (and setup / restart / --foreground) merely print the enable hint when nothing is registered and silently refresh an entry Multica owns — marked by a comment line in a unit/.desktop file or a ManagedBy key in a plist (the Run value name doubles as the marker on Windows). Refresh skips anything without the marker and skips entirely under an external systemd unit (INVOCATION_ID with a cgroup that is not ours), so a hand-written unit like the one in #7091 is never overwritten. --no-autostart is gone (there is nothing to opt out of), loginctl enable-linger is no longer run automatically (enable prints the command instead), and 'autostart enable' now spells out that only PATH carries into the login session while shell-exported variables do not. Also fixes the binary-update restart handoff under our own systemd unit: spawning a successor and exiting 0 lets systemd's cgroup cleanup kill it while Restart=on-failure never fires, leaving the runtime offline after the first auto-update. The daemon now exits with daemonSystemdHandoffExitStatus (42) instead; the generated unit carries RestartForceExitStatus for it, and linux tests cover the unit-ownership detection behind both the handoff and the refresh gate. Review items also addressed: gofmt applied to cmd_daemon_test.go and proc_windows.go, the Desktop wording corrected to its app-start daemon preference (it has no OS login item), and CLI_AND_DAEMON.md plus the daemon start / autostart help text follow the opt-in flow. Co-authored-by: multica-agent <[email protected]> * fix(test): redirect USERPROFILE alongside HOME in cmd/multica tests Every t.Setenv("HOME", ...) in this package now goes through redirectTestHome, which sets both home environment variables. Production resolves ~/.multica via os.UserHomeDir — USERPROFILE on Windows — so a HOME-only redirect split the fixture's write path from the code's read path: on Windows the fixture landed where the code never looked (or, before TestMain's scratch home, in the real ~/.multica). CI on ubuntu is unaffected; the Windows host suite stops depending on whatever happens to be in the real profile directory. Co-authored-by: multica-agent <[email protected]> * fix(cli): record autostart with the stable brew path, shared with the restart target The exit-42 systemd handoff restarts whatever ExecStart recorded, and autostartSpecFor recorded os.Executable() verbatim — on brew installs (Homebrew on Linux too, reached through install.sh) that is the versioned keg path, which `brew upgrade` deletes when it cleans the old keg: systemd then fails ExecStart with 203/EXEC and gives up after StartLimitBurst, leaving the runtime offline until someone starts it by hand; a LaunchAgent would point at a missing file at the next login. Fix per review: both consumers now resolve through one shared helper, cli.StableExecutablePath, with identical detection order — the path's own known-Cellar shape first, then `brew --prefix` — so the two cannot drift. The daemon's restartTargetBinary keeps its seam-based per-process cache but delegates the mapping; autostartSpecFor runs cli.StableSelfExecutable before recording. cli.IsBrewInstall shares the same detection core. Tests pin the keg -> <prefix>/bin/multica mapping at the helper level for all three known prefixes, and end to end through autostartSpecFor feeding a brew keg path, as requested in the review. Co-authored-by: multica-agent <[email protected]> * fix(cli): key autostart ownership on file presence, not enablement Review round 3: on Linux Enabled only means "linked in default.target.wants", so the enable/disable guard (Enabled && !Managed) missed a hand-written unit at our path that was unlinked — or linked under another target such as graphical-session.target — and disable deleted it; separately, the XDG sweeper deleted an unmarked multica-daemon.desktop outright from enable, disable, and the silent daemon start refresh. - autostartState gains Present ("a file/value exists at our path"); refuseUnmanagedAutostart and the status note key off Present, while the enabled/disabled verdict still keys off Enabled. - platformRemoveAutostart re-checks the marker on the unit BEFORE systemctl disable (refusing must not unlink the user's unit as a side effect) and refuses to delete an unmarked file; removeXdgAutostartFile removes only marked files, which covers all three of its call sites (enable's stale sweep, the daemon start refresh, disable). - sync no longer hints enable at a foreign unlinked file, and never heals an owned-but-unlinked entry — refresh must not re-enable what the user manually disabled — it hints instead. - linux tests cover unlinked / other-target-linked / default-linked unmarked units on the read side, the removal backstop, a managed unit coexisting with an unmarked .desktop, and the mark-checked XDG sweeper; shared guard/status/sync tests move to the presence contract. Co-authored-by: multica-agent <[email protected]> * fix(cli): keep the running service's restart policy when disabling autostart Review round 4 (P2): on Linux, `autostart disable` deleted the managed unit file after unlinking it. The running daemon kept going, but systemd lost the unit — LoadState=not-found, Restart=no — so the next binary-update handoff (exit 42) exited into a service that could no longer restart it and the runtime stayed offline until a manual start. Disable now removes the enablement only: unlink every wants entry (systemctl --user disable, with a glob fallback for a missing user bus) and KEEP the marked unit file, so the supervised process retains its restart policy while nothing starts at the next login. Re-enabling re-links the same file. A second disable is a no-op again (change is keyed off links actually present, in any target). The macOS equivalent is fixed in the same spirit: the plist is deleted WITHOUT launchctl bootout, which used to terminate the daemon launchd was supervising — disable means "don't start at the next login", not "kill this session". enable/disable now read the state once and share it with the ownership guard; enable's wording keys off the pre-state, because re-linking a kept unit often changes no file content and "already enabled" would describe the state the user just left. The daemon-start hint now fires only when nothing is registered at all — never at a disabled-or-foreign file the user chose or owns — and the refresh still never re-links. Regression coverage for running service → disable → handoff: TestDisableKeepsRunningServiceRecoverable asserts the unit file survives with RestartForceExitStatus intact, no wants link remains, the handoff still detects our own unit (exit-42 path), a subsequent sync does not re-enable, and a second disable reports no change. Docs note the keep-the-file semantics on both Linux and macOS. Co-authored-by: multica-agent <[email protected]> * test(cli): update the coexistence test to the keep-the-unit disable contract CI caught the round-4 test asserting the pre-round-5 behavior: it wrote a marked unit WITHOUT a wants link and expected platformRemoveAutostart to delete it. Disable now keeps the marked unit (unlinking its enablement) so a running service retains its restart policy — so the test links the unit (giving disable real work, changed=true), asserts the unit survives unlinked, and keeps its original point: the hand-written .desktop beside it is never touched. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: ZhangRui <[email protected]> Co-authored-by: multica-agent <[email protected]> Co-authored-by: Bohan-J <[email protected]> |
||
|
|
d6956ede15 |
feat(daemon): report full CLI build commit in local identity (MUL-7951) (#9164)
Co-authored-by: jikuya <[email protected]> |
||
|
|
c8d6a1fad0 |
MUL-7923: fix(test): isolate test home on Windows so tests stop overwriting ~/.multica/config.json (#9128)
* fix(test): isolate test home on Windows across config-writing packages
Tests that only redirect HOME still resolve ~/.multica against the real
home on Windows: os.UserHomeDir reads USERPROFILE, not HOME. SaveCLIConfig
then writes test fixtures over a real default-profile config.json.
Reproduced on a Windows host by running `go test ./internal/cli ./cmd/multica`
on an unmodified tree: the real ~/.multica/config.json was replaced with the
test fixture {"token": "mul_owner_pat"}, dropping server_url, app_url and
workspace_id. The running daemon keeps its in-memory config, so the damage
only surfaces on the next restart as an auth failure.
Redirect both home variables process-wide in TestMain for the packages that
resolve ~/.multica (cmd/multica, internal/cli, internal/daemon), and route
per-test home redirects through a redirectTestHome helper that sets HOME and
USERPROFILE together. Per-test t.Setenv overrides still win.
Comparison on the same host, same packages:
without this change: 6 internal/cli + 10 cmd/multica failures, real config overwritten
with this change: 1 + 5 failures (all pre-existing Windows cases), config intact
Co-authored-by: multica-agent <[email protected]>
* fix(test): fail closed when isolating test homes
Co-authored-by: multica-agent <[email protected]>
* test(ghsnapshot): isolate cursor fixture from other packages
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: ZhangRui <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Yushen <[email protected]>
|
||
|
|
3bacc41f84 |
MUL-7573 fix(lark): clear the Typing reaction without trusting in-process state (#8656)
* fix(lark): persist and reconcile typing cleanup across replicas - preserve terminal trigger targets and per-input cleanup ownership - reconcile late Adds and source deletion without delaying replies - recover interrupted ledger indexes and cover lifecycle boundaries Refs: #8655 * fix(lark): restrict typing cleanup to the current application - match both app operator type and the current App ID - preserve foreign-app, human and unknown-owner reactions - cover ownership filtering in fast and durable cleanup paths Refs: #8655 * fix(lark): bound typing recovery and credential retention - recover taskless indicators after lost debounce timers - cap workspace ledger admission and redact terminal credentials - isolate maintenance budgets and test retention and quota boundaries Refs: #8655 * docs(lark): document typing cleanup recovery and rollout limits - explain ownership, retention, quota and observable terminal outcomes - document migration upgrades and safe application rollback Refs: #8655 * fix(deps): require patched Go 1.26.9 and x/net 0.60 - Require the patched Go standard library to clear reachable vulnerabilities. - Upgrade x/net to the matching HTTP/2 security release. - Align local development prerequisites with the minimum Go version. |
||
|
|
13520a0d13 |
fix(cli): keep member names off free-form property lists (#9093)
multi_text and multi_url entries were resolved through the member directory, so a stored string that matched a member reference displayed as that person's name. Co-authored-by: Cursor <[email protected]> |
||
|
|
bb14e8763c |
feat(issues): add multi_text and multi_url custom property types (MUL-7496) (#8559)
* feat(issues): add multi_text and multi_url custom property types Workspaces could only hold list-shaped values as preset option lists (multi_select) or member references (multi_actor). There was no way to keep a list of free-form strings or links on an issue, so teams with "related docs / spec links / aliases" needs had to either cram them into one text field (no structure, nothing clickable) or burn one select option per link. This adds two property types: - multi_text: an ordered list of free-form text entries - multi_url: an ordered list of http(s) URLs Each entry is validated exactly like a single text / url value (length caps, http(s)-only for URLs), duplicates are dropped, insertion order is preserved, and values are capped at 20 entries to respect the 16KB properties-bag budget. Values ride the existing jsonb bag, so the jsonb_path_ops GIN index, the @> containment filter, and the client value schema all keep working unchanged. Filtering matches any single list element exactly (equality) plus "No value" — the containment form the filter compiler already emits for array elements, so no new server predicate is needed. List types deliberately get no sort order and cannot group, like the other multi-value types. On the web, list values render as chips; multi_url chips are clickable and open in a new tab, and the editor popover offers a per-entry open button, so several related documents or links stay directly reachable from the issue. The filter menu reuses the scalar input (equality-only plus "No value"). CLI: comma-separated --value form for both types, element-equality --property filtering, and display_values rows. Server-side allowlist widened by migration 500 (check constraint re-added NOT VALID then validated, same pattern as migration 341). * fix(properties): address list property review nits Co-authored-by: multica-agent <[email protected]> * fix(properties): scope list drafts to the edited issue Co-authored-by: multica-agent <[email protected]> * test(agent): avoid fork races when writing zeroclaw fixtures Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
43b0571f99 |
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 <[email protected]> * 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 <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
fa5d470ae4 |
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 <[email protected]>
* 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 <[email protected]>
* 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 <[email protected]>
* 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 <[email protected]>
---------
Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
|