mirror of
https://github.com/multica-ai/multica.git
synced 2026-09-28 13:23:48 +08:00
agent/lambda/850c485fe6bb
2198
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a319c2f482 |
feat(daemon): capture what each run changed and upload it before reporting done (MUL-7651)
- Local worktree mode: Finalize now reports the range it delivered (baseline..tip), so the run's change is exactly the agent's commits, with the user's replayed edits on the baseline side. The branch row runs from the newest baseline on the branch, the start of this line of work. - Repository checkouts: each checkout's HEAD is recorded when the run first sees it (already in a reused workdir, or made by `multica repo checkout`), and diffed against HEAD at the end. The branch row measures against the remote's default branch. - codechange diffs two commits with user diff config pinned off (external drivers, textconv, prefixes, colour), caps the patch at 1 MB and the file list at 3000, and stops git rather than draining a larger patch. - Collected on every exit path, failed and cancelled runs included, and uploaded before the terminal report. Best effort: an older server's 404 is logged and ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
3c75f03a76 |
feat(server): store each run's code change and serve it to the issue page (MUL-7651)
A run's diff arrives from the daemon as it ends and lands in task_code_change:
one row per run, scope and repository. "run" is the run's own change;
"branch" is the delivered branch against where its line of work started,
which backs the issue-wide view when no pull request can supply it. The
patch goes to object storage; a patch over 1 MB never arrives and the row
keeps only the file list.
- POST /api/daemon/tasks/{taskId}/code-changes stores them. First write
wins, so a retried upload changes nothing. Remote URLs lose any embedded
credentials; commits, statuses and sizes are validated and clamped.
- GET /api/issues/{id}/code-changes lists summaries, and
/code-changes/{changeId} returns one with its file list and patch.
- GET /api/issues/{id}/pull-requests/{prId}/diff reads a linked GitHub PR's
files through the GitHub App and assembles them into one patch.
- code_changes:created tells open issue pages to refetch.
- No foreign keys: issue delete and workspace delete remove the rows and
their stored patches.
- .patch and .diff join the text preview whitelist.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
6dc6a0b9a2 |
MUL-7650: attachment viewer — CSV tables, JSON/YAML tree, numbered code, HTML viewports, every kind in a new tab (#8868)
* feat(server): accept JSON Lines in the attachment text preview proxy The viewer now shows .jsonl / .ndjson files as a tree, so the text proxy's whitelist takes them too (extension and application/x-ndjson). The whitelist test gains the CSV / TSV / log / JSON Lines cases and says it is mirrored by the client-side table in packages/views/editor/utils/preview.test.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(views): table, tree, numbered-line and viewport views in the attachment viewer MUL-7650, step 3 of MUL-7642. Fills in how the full-window viewer shows the file types agents produce most. - CSV / TSV (`table` kind): parsed to a table on the shared DataTable — pinned header, virtualized rows, resizable columns, a frozen row-number column. Clicking a header sorts ascending / descending / file order; numbers sort numerically, empty cells stay last, all-number columns align right. `;`-separated exports are sniffed. DataTable headers now carry aria-sort when the table sorts through TanStack's own state. - JSON / JSON Lines / YAML (`structured` kind): a collapsible tree with inline previews of collapsed records and paged long lists; Tree / Raw in the top bar. A file that does not parse falls back to the source and says why. YAML alias expansion stays capped by the parser. - Code, text and logs: numbered lines (the number is a pseudo-element, so copying skips it) with a sticky gutter, and a wrap toggle. Logs and plain text wrap by default, code does not; the reader's choice carries across the sequence. - HTML: Fit / Desktop 1440 / Tablet 768 / Phone 390. A device width lays the document out at that width and scales it down when the stage is narrower, with the size and scale shown under it; switching never remounts the iframe. A source toggle shows the HTML with line numbers. - Open in new tab works for every previewable kind: /{slug}/attachments/{id} /preview loads the record by id and renders the viewer's own top bar and stage (AttachmentPreviewStandalone), keeping the desktop scroll restore for HTML. - Whitelist: .jsonl / .ndjson join the text types on both sides, and the client test mirrors the server's case table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
7dac88a6a8 |
MUL-7649: issue deliverables and dynamic blocks — sidebar section, overview grid, viewer info panel, html/mermaid block frame (#8779)
* feat(attachments): issue deliverables model and sequence block ids (MUL-7649) collectDeliverableFiles groups every comment upload into deliverables, merging same-name, same-type re-uploads into versions (newest version first). Description attachments are inputs and never enter the model. Sequence items now carry the id of the block they came from, so a viewer can say which comment (or the description) a file was posted in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(viewer): host details, overview and info panel slots (MUL-7649) PreviewSequenceProvider takes an optional describeItem (title accessory, info panel, locate action) and onOpenOverview. The viewer renders only the slots it is handed: a locate button, a grid button (G) and an info toggle (I) whose panel sits beside the stage. Letter keys stand down in fields, and any key pressed inside an open menu stays with the menu. A zoom canvas still at fit now follows a viewport resize (the info panel opening), while a zoom the reader chose is only clamped, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(issues): deliverables section, overview grid and info panel (MUL-7649) The sidebar's "Deliverables" section shows what the issue delivered as a whole: its pull requests and the files its comments uploaded, re-uploads merged into one entry marked v2. It lists the three newest images and four newest files, plus "View all N deliverables". The former Pull requests section becomes the code group, keeping its MUL-7429 actions (link a PR by URL, the auto-complete menu) beside the group label. While the workspace shows PRs, the section stays up even with nothing delivered, since that is where a PR gets linked by hand. The overview (from "view all" or G in the viewer) shows code first, then files grouped by posting comment, filterable by kind, each group one click from its comment. Its count always equals the sidebar's. In the viewer, deliverables get a version switcher, and every file gets an info panel (source excerpt, sibling files, uploader, time, size) and "Show in comments", which unfolds a resolved thread if needed and reuses the timeline's jump-and-flash highlight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(comments): standalone attachments as cards and an image row (MUL-7649) The files under a comment (and a chat message) no longer stack as one full-width row each. Other files become cards in a grid: type glyph, name, "TYPE · size", download / remove on hover, and the whole card opens the file. Several images sit in one row at a shared height; a lone image keeps its full size. HTML keeps its embedded preview (MUL-2330). On the issue page a re-uploaded file's card carries its version (v1, v2) from the deliverables model, through a small context. Standalone attachments are grouped images, then HTML, then files, and the preview sequence walks them in that same order, so paging follows the screen. The file-type glyph moves to editor/utils/file-icon so comment cards and the deliverables surfaces share one mapping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * refactor(issues): pull requests keep their own section (MUL-7649) Since MUL-7429 the PR block is issue workflow — linking a PR by hand, the auto-complete switch, "won't auto-complete: missing Closes" — rather than output, so it goes back to main's own Pull requests section, unchanged, directly above Deliverables. Deliverables is now the delivered files only: hidden until a comment delivers one, and the sidebar number, "View all N" and the overview's "All N" all count files. The overview drops its code group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(comments): lay several images out as justified rows (MUL-7649) Fixed-height tiles wrapped as soon as the column narrowed — three screenshots became two plus an orphan, with a ragged right edge. Rows are now justified from each image's real aspect ratio: every row shares one height and a full row runs edge to edge. A row takes as many images as fit above a minimum height, never grows past a maximum, and a last row that does not fill keeps the height of the row above it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(rich-content): dynamic block frame for html and mermaid fences (MUL-7649) A fenced ```html or ```mermaid block is part of the message body, so it now renders in one shared frame instead of two unrelated widgets: - Always-visible title bar: kind icon, the fence's title="..." (or the kind name), a kind chip, Preview | Source, fullscreen and copy. Previously the controls only appeared on hover and floated over the content. - The body takes its content's height (at least 120px) and collapses past 480px behind a fade and "Show all". An HTML block used to be a fixed 480px. - Loading keeps the height this block had earlier in the session; a script error or a Mermaid parse error is explained inside the frame with "View source" and "Copy error". - HTML gets the app's theme tokens (--foreground, --chart-1 ...); HTML that uses one also gets the app's color-scheme, so it follows dark mode. HTML that uses none keeps its own look. The sandboxed document reports its height and first uncaught error through a one-line postMessage bridge; the page checks the message source and clamps every value, and stops a document whose height follows the frame. rehype-raw drops the fence meta, so the title is read in the closed-fence parse. The Mermaid sandbox now also declares the app's color-scheme and font: in dark mode it was painted on an opaque white canvas, and its labels were drawn in a serif font that did not match the layout Mermaid measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(attachments): HTML files show as cards, not inline previews (MUL-7649) Reverses the MUL-2330 pin. An uploaded HTML file is a deliverable to open, not part of the text: like every other non-image file it shows as a card (a row inline in the body, a grid card under it) that opens in the viewer, and its contents are no longer fetched to embed a 480px iframe. HTML meant to be read in place is written as a ```html block, which renders as a dynamic block. - Attachment drops its html branch; HtmlAttachmentPreview is deleted and HtmlPreviewBody keeps only the inline source the viewer uses. - orderStandaloneAttachments groups images, then files (HTML included), so AttachmentList and the viewer sequence still walk the same order. - The MUL-2330 regression pins now assert the card and that no text fetch happens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * docs: charts go in html/mermaid blocks, files show as cards (MUL-7649) Agents that uploaded an HTML chart expecting it to render in the comment now get a file card instead. Tell them where each kind of content goes: - multica-platform skill (issues reference, routed from the table): a fenced ```html / ```mermaid block renders in place with a title bar and content height; an attached file, HTML included, is a card that opens the viewer. Covers title="...", the sandbox, the theme variables and their opt-in color scheme, and sizing to content rather than the viewport. - `multica issue comment add --attachment` help says the same in one line. - Comments docs (all five languages) describe both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(daemon): runtime brief says where charts and diagrams go (MUL-7649) The chart/file rule lived only in the multica-platform skill's issues reference, which an agent opens on demand, and the skill's description did not mention comment formatting, so an agent that just wanted a chart in its result had no reason to read it. Agents that uploaded report.html expecting an inline chart now get a card. - The brief's Output section carries one line, beside the file-delivery line, on the surfaces the web renders (issue comments and web/mobile chat): put charts and diagrams in the text as a fenced html or mermaid block, name it with title="...", and an attached file, HTML included, shows as a card. Theming and sizing stay in the reference. - Channel chats, autopilot run results and quick-create stdout do not render these blocks and do not get the line; the delivery tests pin both sides. - The skill description names "charts and files in comments" so the reference is picked for that task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(issues): locate a file in a collapsed thread (MUL-7649) "Show in comments" for a file posted in a reply only unfolded resolved threads and gave up after 30 frames, so a thread the reader had collapsed stayed shut and the reply never mounted. A reply now goes through the quick-jump rail's jumpToReply, which already undoes every kind of folding and waits for the reply to land. A root comment gets the same treatment for its own thread (the reader's collapse, or a resolved bar), then the jump. Found in review by Emacs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(viewer): the info panel's "Show in comments" closes the viewer (MUL-7649) The top bar's locate went through the viewer, which closes itself first; the same button in the info panel called the host's callback directly, so the page scrolled underneath a viewer that still covered it. Controls handed to describeItem gain close(), and the issue page builds one close-then-locate action for both entry points. Found in review by Emacs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(issues): the deliverables overview is a real modal dialog (MUL-7649) The overview was a portaled layer with role="dialog" and nothing else: focus stayed on the opener, Tab walked into the page underneath, and closing left focus wherever it was. It now renders through the shared Dialog, restyled to cover the window, so the primitive moves focus in, keeps it inside, returns it to the opener, and handles Escape. `G` back to the viewer's file stays. The dialog is named by its title ("MUL-123 deliverables"). Found in review by Emacs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
fc4a615700 |
MUL-7726: let the workspace choose the status merged PRs move an issue to (#8862)
* feat(pr): move issues to a workspace-chosen status when their PRs merge (MUL-7726) The merge automation completed an issue only when a PR said "Closes MUL-1", so the workspace switch mostly did nothing: most PRs are linked by title or branch and never completed anything. The workspace now picks what a merge does, in settings.pr_merge_status: "none", or the key of a started or done status (custom ones included, Blocked excluded). Absent means Done. When every linked PR is merged, the issue moves to that status. A closing keyword only links. - Decision: terminal, triage, "none", the per-issue switch and a new at_target state (the issue already has the target) come before the PR states, so the issue page only speaks when a merge would move the issue. The response adds target_status. - MoveIssueFromPullRequests replaces CompleteIssueFromPullRequests and writes the target status. The target is resolved through the delivery's shared catalog read (Resolver.WritableCategory). An archived or unknown choice, or a failed read, fails closed to no write. - Close intent is no longer synced or read. The column stays. - Migration 551 pins "none" on existing workspaces whose history shows a merge never moved every issue: auto-complete switched off, or a linked PR whose merges were not all keyword merges. Workspaces with no links, or with only keyword merges, keep the Done default. Replays are harmless. - CLI: `multica issue pr-automation <id> on|off` lets an agent keep one issue where it is. The multica-platform skill describes the new rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(views): choose the status merged PRs move an issue to on the GitHub page (MUL-7726) - Settings → Integrations → GitHub → Features: "After PRs merge, move the issue to" picks Don't change, or a started or done status, custom ones included and Blocked excluded. It replaces the read-only auto-complete row. The self-hosted Git page shows the same setting. The statuses page drops its switch, and a badge on the target status now links to the setting. - Issue sidebar: the line under the PR list names the target status ("Moves to In Review when #19 merges"). It says nothing when the workspace leaves status alone, when the issue already has the target, or on an older backend's no_close_intent. The per-issue switch reads "Keep status when PRs merge" and is hidden when it would change nothing. The menu opens the GitHub setting. The link hint only shows while auto-link is on. - core: derivePRMergeStatus replaces derivePRAutoCompleteEnabled; the auto_complete schema adds target_status and the at_target state. - Copy in 5 languages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * docs: describe the workspace-chosen status for merged PRs (MUL-7726) GitHub integration, self-hosted Git, issues and environment variable pages (4-5 languages) now say that the workspace picks what a merge does: Done by default, another started or done status, or no change. They also say that a closing keyword only links, and how to keep one issue's status. The troubleshooting entries explain what a missing line under the PR list means. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * refactor(pr): keep the merge automation out of agents' instructions (MUL-7726) Agents don't act on what a merge does to an issue, so they don't need a command for it or a rule to learn. Drop `multica issue pr-automation`, and cut the multica-platform skill down to how PRs get linked: no merge rule, no auto_complete field guide, no "the server moves it to done" note. People still keep one issue's status from its Pull requests menu, and the docs now point there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(pr): keep the retired auto-complete switch working for old desktop clients (MUL-7726) Review of #8862: an admin on a desktop client from before this change can still flip "Complete issues when their PRs merge". The client saved pr_auto_complete_enabled = false, and the save looked successful, but the server only read pr_merge_status, so merges kept moving issues to Done. - Settings writes: a flip of the retired switch becomes a choice (off → none, on → Done). An old client that echoes a chosen target without flipping the switch leaves the target alone. Every write mirrors the switch from the effective choice (false for none, absent otherwise), so old clients show the right state. - Reads: without pr_merge_status, the retired switch decides (off → none, else Done), on the server and in derivePRMergeStatus. This also covers settings written by a pod that predates the change during rollout. - Migration 551 sets the switch off on the workspaces it pins to none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
7551ff1ddc |
fix(wecom): answer an unreadable message once per message, not once per delivery (#8836)
WeCom redelivers a callback it did not get an ack for, and this receipt is sent from dispatchFrame — before c.handler, so the Router's own Claim never runs for it and nothing downstream deduplicates it. Unclaimed, every redelivery of one unreadable photo puts another apology in the chat, and each copy spends one of that conversation's active pushes. The claim is the same two-phase token the Router uses, on the same table and the same (installation, message) key, so a message answered here can never also be answered there. NewDeduper is exported for exactly that reason: router.go and NewResolverSet now build it the same way instead of each reaching for the private type. Three outcomes, chosen so the user's worst case is today's behaviour: already claimed says nothing; sent marks it; a FAILED send releases, because holding the claim over a receipt that never arrived turns one failed send into permanent silence. The wiring is the part that has to hold. RegisterWecom warns when Dedup is nil, since a missing one has no other symptom, and two tests cover the two ways it can go missing: TestRegisterWecomCarriesTheDeduperIntoTheChannel drives newWecomFactory, and TestWecomChannelGetsItsDeduperOnTheRealBootPath reads NewRouter itself with the anti-vacuity check taken as a delta between two routers. |
||
|
|
12f8f3f311 |
feat(agent): list Claude Opus 5.5 and GPT-6 Sol/Luna in the fallback catalogs (#8840)
Since the fallback catalogs only feed the model picker (#8811), listing current models there is cheap. Add the models the installed CLIs now advertise but the static lists were missing: - Claude: claude-opus-5-5 (Claude Code 2.1.280 list_models default) - Codex: gpt-6-sol (low..ultra) and gpt-6-luna (low..max), both default medium, from codex-cli 0.155.1 `debug models`; live discovery labels them "GPT-6 Sol" / "GPT-6 Luna" like GPT-6 Astra MUL-7691 Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
005462ec22 |
MUL-7705 docs(channels): correct comments main's own later changes made false (#8833)
Three comments described behaviour that later changes on main replaced. Comments only; no behaviour change. - slack/typing_indicator.go: EventTaskFailed carries the chat session id on the envelope like EventTaskCancelled (both go through service.taskEvent), not only in the payload map. chat:done's payload is a ChatDonePayload struct, so the helper's map read only ever matches the two task events. - wecom/types.go: outbound no longer needs a single replica when a sharded/dual realtime relay routes off-lease replies to the lease holder (#7429); legacy relay mode or no Redis still does. Also documents the per-chat quota gate (#8345): a slight overage is delayed, a burst past the caller's wait budget is refused before the write. - cmd/server/router.go: the boot-time SINGLE-REPLICA CONSTRAINT note now states the relay-dependent topology. Co-authored-by: Bohan-J <bhjiang@outlook.com> |
||
|
|
1aa815a738 |
fix(daemon): stop validating saved model settings against fallback catalogs (#8811)
When model discovery fails, providers substitute a static catalog so the picker stays usable. The daemon then validated saved thinking levels and service tiers against that stand-in, silently dropping values the static list did not know about — which forced hand-maintained model and per-model effort tables to chase every new release. Only a verified catalog (discovered, non-empty) may now reject, rewrite, or drop a saved model, thinking level, or service tier. Fallback and empty catalogs pass the saved values through to the CLI for every provider. The checks that describe the binary rather than its models stay: a Claude CLI without --effort (or without the saved value in its --effort list) still drops the level, and Codex's explicit standard tier still follows the CLI version. - Catalog.Verified / Catalog.CLIThinkingLevels; missingFromFallbackCatalog removed - claudeModelEffortAllow removed: the fallback picker offers the --help superset - ModelKnownIncompatibleWithProvider no longer consults static lists (prefix-only, same result) MUL-7691 Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
dbfc014bdd |
MUL-7610: fix(autopilot): avoid loading task history during webhook recovery (#8710)
* fix(autopilot): avoid loading task history during webhook recovery * refactor(autopilot): move webhook recovery task check into agent.sql Rename HasTasksByIssue to HasTaskForIssue and place it beside the other per-issue task existence queries. Group test imports. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bhjiang@outlook.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
64b06b3de8 |
fix(telegram): attach the file a reply quotes (#8828)
A reply that quoted a photo, video or file reached the agent without the file: in a group with an @-mention the quote rendered as "[empty or non-text message]", and in a private chat, or when replying to the bot's own message, the quoted message was not rendered at all. Only the trigger message's own file went through the media resolver. Telegram delivers the quoted message in reply_to_message with its file ids, so the adapter now picks that file the way it picks the sender's own, renders it in the quoted block as its placeholder above the caption, and carries both files in the raw envelope in body order. The resolver ingests every file, keys each object by its position as DingTalk and WeCom do, and tells the sender once when one could not be fetched. A quoted file is selected context in every chat and whoever sent it, as long as the reply addresses the bot; quoted text keeps the group-mention rule. Recent-context entries render a file the same way, so a captioned photo in the window no longer hides that a photo was there. |
||
|
|
e2f4a22030 |
feat: steer a running agent from the normal composer, per recipient (MUL-7631) (#8760)
* feat(server): steer running turns from ordinary comments (MUL-7631) A comment can now name recipients whose running turn should receive it (`steer_agent_ids` on POST /issues/:id/comments). Each named recipient with a running, steer-capable turn gets the comment bound to that turn instead of a follow-up run; any other recipient keeps its normal queued / coalesced / deferred trigger, so losing a race never drops input. - One comment can steer several turns: receipts are keyed by (comment_id, task_id) and returned as `supplements` on comments and timeline entries. The single supplement_* fields mirror the first receipt for older clients. - Completion reconciliation skips a steered comment only for the agent it steered, so the same comment still reaches recipients that were addressed without steering. - Delivery claims match the (comment, run) pair. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(issues): choose per recipient how a message reaches a running agent (MUL-7631) Steering moves from a separate "Add message" box on the run row into the normal reply and comment composers. Recipients keep the existing trigger rules; each recipient's current run on the issue decides what the message can do: - running (and steer-capable): add to the current run (default in that run's own thread), start after this run, stop and start over, or skip - queued: include when it starts, or skip - idle: start on send, or skip Receipts follow the message ("Waiting for Lambda to read it" → "Read by Lambda · after step N"), a failed delivery offers retry or sending it as a new run, and the run's step list marks where it read the message. A draft whose recipient finished meanwhile stays in place and says it will start a new run; a message with files is handled after the run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(server): steer only the chosen turn, and make a retried steer idempotent (MUL-7631) - Steering names the exact running turns the author chose (`steer_task_ids`). A chosen turn that ended before the send arrives is never swapped for a later turn of the same agent; that recipient keeps its normal trigger. - A steering send carries `client_request_id`. A retry after a lost response returns the original comment (200) instead of creating a second comment and a second delivery; a concurrent twin binding the same request is treated as already steered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(issues): guard steering sends against stale previews and broad resends (MUL-7631) - "Stop and start over" never stops a run while the trigger preview is still catching up with an edited @mention: the stop is dropped unless the preview answers the submitted text. - Steering sends the chosen turns' task ids and one request id per draft text, reused on retries of the same text. - "Send as a new run" on a failed receipt reaches only that receipt's agent: every other current recipient is suppressed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(issues): keep the reply box placeholder neutral (MUL-7631) The empty reply box cannot know who a reply goes to: the author may @ someone else or only talk to a teammate. The recipient chip already says what Send will do once there is text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * test(issues): give steering composer tests room for the preview debounce (MUL-7631) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(issues): choose the default reply to a running agent (MUL-7631) Preferences > Comments gains "When replying to a running agent": "Add to current run" (default) or "Start after this run". It only changes what a reply does without a per-message choice; the recipient chip still offers both, and replies that never steered by default (another thread, an idle or queued agent) are unaffected. Saved on this device next to the sticky comment bar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(server): save one comment per send and honor "don't start" on replay (MUL-7631) Idempotency now lives on the comment, not the delivery receipt. A send's client_request_id is stored on the comment behind a partial unique index (migrations 550/551), so a retry, or a concurrent twin, returns the saved comment (200) whether the send steered a running turn or fell back to a normal trigger. Before, a twin could save a second comment, and a send whose turn had ended kept no receipt to find. The agents an author chose not to start are stored on the comment too, and completion replay skips them. "Send as a new run" for one missed recipient, and "Won't start this time", no longer wake the other agent once its current run completes. An edit re-records the choice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(issues): repeat the original steering request on retry (MUL-7631) Until a send is settled, retrying the same text sends its first steering request unchanged, even after the chosen turn ended. The server then returns the comment the first attempt saved instead of posting it again as new input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(migrations): fail fast on locks in 549 and 550 (MUL-7631) Both take an ACCESS EXCLUSIVE lock on a table the product touches continuously: comment for every read and write, task_supplement for every daemon claim. Bound lock acquisition and execution, as 483 does for issue, so a long transaction makes the migration fail and retry on the next run instead of queueing all traffic behind it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(server): steer at most one turn per comment during rollout (MUL-7631) Steering is live, and servers from before per-run receipts claim every receipt of a comment at once. While they still serve daemons during a rollout, a comment bound to two turns could have the second copy marked in flight and never delivered. Until every server claims per (comment, run), a comment steers the first chosen turn only; later recipients keep their normal trigger. A follow-up lifts the limit after this ships. Also covers a steer_task_ids entry that names another issue's turn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(server): finish a saved send on retry; keep an edit's choices with its text (MUL-7631) A send's comment was saved before its agents were started or steered, and deduplication treated the saved comment as the whole send. A dropped connection cancelled the request between the two, and the retry only returned the comment, so no agent ever received it. - Attempts of one logical send now run one at a time under a transaction advisory lock, held on its own connection: a twin waits for the attempt in progress (up to 30s, then 409) instead of racing it, and a process that dies releases it. - After the comment is saved, dispatch runs on a non-cancelable context, and comment.client_request_dispatched_at (migration 552) records that it finished. A retry that finds a saved but undispatched comment finishes the dispatch with the comment's recorded choices. An edit's "don't start" choices were written by a separate statement after the text, so interleaved edits could leave an older edit's choice. They are now part of UpdateComment, under the same revision check as the text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(issues): offer one steered recipient per message during rollout (MUL-7631) The server steers at most one turn per comment until #8800, but the composer showed "Add to current run" for every running recipient and the server quietly queued the rest. Now only one recipient steers: the one the author picked, otherwise the first that would by default. The others show "Start after this run", and picking "Add to current run" on one of them moves the steer there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(server): claim a send before dispatching it; drop the request lock (MUL-7631) The previous recovery marked a send dispatched only after reaching its agents, so a failure after dispatch made a retry reach them all again. It also held a pooled connection per send for an advisory lock while the send needed more from the same pool, which could wait on itself until timeout. A send's comment now holds a dispatch claim instead (comment.client_request_dispatched_at, one conditional update): only the attempt whose claim takes effect publishes the comment and reaches its agents. A failed claim writes nothing else and returns 500 for the client to retry; a retry that finds an unclaimed comment claims and dispatches it; a claimed one is only returned. Concurrent attempts no longer wait on each other, and post-save dispatch keeps a 30s bound of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(server): let one comment steer several running turns (MUL-7631) Lifts the rollout limit from #8760. Every server now claims receipts per (comment, run), so a comment can be bound to each running turn its author chose, and each recipient receives it in its own run. Merge only after #8760 is deployed everywhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(issues): steer every running recipient a message addresses (MUL-7631) The client side of lifting the rollout limit: each running recipient can take the message in its current turn again, instead of only one per message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * refactor(issues): drop send-retry idempotency and two display extras (MUL-7631) A steering send now behaves like any other comment when its response is lost: a retry posts it again. The machinery that tried to make that retry return the saved comment grew with every edge it met, and ordinary comments never had it: - Remove comment.client_request_id, its unique index, and the dispatch claim (migrations 551/552, and 550 reduced to suppressed_agent_ids). - Remove the replay/resume path in CreateComment, the post-save non-cancelable dispatch context, and the client's sticky request id. Steering sends only `steer_task_ids`; each binding uses the comment id as its receipt request id. Two display extras go too, since the UI already shows the same thing: - The "run ended while you were writing" notice: the recipient chip already switches from "Add to current run" to its new action. - "after step N" on a read receipt: the run trace already places the message at the step where it was read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(issues): space agent names in Chinese copy; neutral recipients header (MUL-7631) - The steer badge joined names with Intl.ListFormat, which gives "Lambda和Orion" in Chinese. Latin names are now spaced from the Chinese joiner ("Lambda 和 Orion"), per the zh copy conventions; the enumeration comma stays unspaced. - The multi-recipient popover was still titled "Will start when sent" and its tooltip said "choose who runs", though each row now picks how that agent handles the message. Title it "Recipients" and reword the tooltip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(issues): translate the recipient source labels in Chinese (MUL-7631) "assignee" and "@mention" were left in English in zh-Hans; the recipient menu header shows them. Use 负责人 (per the zh conventions) and @提及. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
79b9327837 | fix(issue): bind update attachments safely (#8588) (#8766) | ||
|
|
ff8b285497 |
fix(runtimes): price Claude Opus 5.5 at its own tier (#8808)
Opus 5.5 had no pricing row. The dashboard resolver found no match for `claude-opus-5-5` and showed its spend as $0, while the backend's unanchored `claude-opus-5` rule matched it and billed it at Opus 5's 5/25 tier. Add Opus 5.5 at Anthropic's published rates ($4 input / $20 output / $5 5m cache write / $0.20 cache read, 0.05x input) to both tables, and end the backend Opus 5 rule at `claudeVersionEnd`, the same way the Fable 5 / 5.1 pair is split, so neither Opus rule can take the other's ids. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
ec70b224d7 |
MUL-7675: feat(grok): support live-run steering via Grok Build ACP (#8796)
* feat(grok): support steering active Grok Build runs * test(grok): cover steering before first agent output * fix(grok): bound interject acknowledgement time |
||
|
|
e909e9c89d |
MUL-7587 fix(daemon): show on-behalf-of identity in agent context (#8688)
* fix(daemon): expose original requester in agent context * fix(daemon): render run originator as on-behalf-of identity * docs(daemon): update stale Task Initiator comments to On Behalf Of Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bhjiang@outlook.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
df2e86cf3a |
fix(daemon): retry Windows task root record rename (#8786)
Refs #8785 |
||
|
|
0da0b46bbf |
MUL-7672: only a closing keyword completes an issue on PR merge (#8794)
* feat(pr): only a closing keyword completes an issue on merge (MUL-7672) #8758 made every linked PR count toward auto-complete, so a title-only "MUL-123: ..." PR moved its issue to Done on merge. Restore the earlier contract: a PR completes its issue only when its title or body puts a closing keyword (Closes/Fixes/Resolves) right before the identifier. - Body closing keywords link again; a bare body mention still links nothing. - close_intent is recorded on the link and follows the PR text until the merge/close event, then freezes; a link first made after merge carries none. - The decision requires every linked PR merged and at least one with close intent; a new no_close_intent state explains why a merge won't complete. - Everything else from #8758 stays: manual link/remove, per-issue and workspace switches, edge-triggered evaluation, the result line. - Docs (4 languages), UI copy (5 languages) and the multica-platform skill describe the keyword rule again. Co-authored-by: multica-agent <github@multica.ai> * fix(pr): sync close intent on every link of the PR (MUL-7672) Review of #8794 found two ways a withdrawn closing keyword still completed the issue: - close_intent was refreshed only for identifiers the PR still claimed, so a manual link, or an automatic link whose merge event (carrying the final text) arrived before the edit event, kept a stale true. - the refresh sat behind the auto-link toggle, so turning auto-link off froze whatever intent was recorded. Replace the per-link update with one PR-wide sync: until the merge/close event, every link of the PR (automatic or manual) gets close_intent exactly when the current title/body closes its issue. It runs whenever GitHub features are on, independent of auto-link, which now only decides which links are created. Co-authored-by: multica-agent <github@multica.ai> * fix(pr): decide close intent apart from auto-link ownership (MUL-7672) Re-review of #8794: the PR-wide close intent sync reused the auto-link verdict, and resolvePRLinkPolicy skips workspaces with auto-link off. With an installation bound to several workspaces, turning auto-link off in one of them therefore cleared a closing keyword only that workspace resolves, and the merge no longer completed the issue. The policy now reads every bound workspace with GitHub on, and records the identifier's sole resolver next to the auto-linking owner. Links still follow the owner (auto-link off workspaces are not competing claimants); close intent is permitted for the owner or the sole resolver, so an identifier that resolves in more than one workspace stays withheld. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
9c69661f7f |
feat(web): licensing FAQ, privacy policy, About team section, source-available wording (MUL-7558) (#8643)
* docs(license): name Index Labs (Hong Kong) Limited as the producer (MUL-7558) The LICENSE and NOTICE credited "Multica, Inc.", which is not a registered entity. Replace it with the company that holds the rights, define "the producer" (used throughout Part I but never defined), and point to where commercial licenses and branding waivers are requested. Co-authored-by: multica-agent <github@multica.ai> * feat(web): add licensing and privacy pages, describe Multica as source-available (MUL-7558) - /licensing: plain-language licensing FAQ with the rule of thumb and common scenarios, in en/zh/ja/ko. - /privacy: privacy policy based on what Multica Cloud actually collects; the Contact Sales privacy link now points here instead of /about. - About: new "Who's behind Multica" section (team story + contact routes; team member cards left for later). - Replace "open source" / "fully open source" with source-available wording across the landing copy, page metadata, JSON-LD, docs, READMEs, and the Helper agent prompt, since the license restricts hosted use. - Contact Sales consent copy says "Multica" instead of "Multica, Inc.". - Reserve the "licensing" workspace slug for the new top-level route. Co-authored-by: multica-agent <github@multica.ai> * fix(web): drop the Slack users section from the licensing page (MUL-7558) Co-authored-by: multica-agent <github@multica.ai> * fix(web): add the commercial-use FAQ in ja/ko and sharpen the source block headline (MUL-7558) - ja and ko override faq.items wholesale, so the new "Can I use Multica commercially?" entry (the FAQ's only link to /licensing) was missing there. Add it, plus a test that keeps every locale's FAQ in step. - The source block headline now states the value ("Every line, on your terms.") instead of repeating the license category. - Privacy policy: list apps connected through Composio among the integrations that receive data. Co-authored-by: multica-agent <github@multica.ai> * fix(web): align the privacy policy with what the product actually does (MUL-7558) Privacy review found promises the code does not keep: - Workspace deletion removes content from the service, but uploaded files are not yet erased from object storage and backups keep copies. Say so, and give an email route for erasing files. - AI: coding agents send prompts, code, and tool results to their own model providers; only the Cloud assist features use a provider we pick. Scope the training promise to Multica. - Crash reports: redaction filters recognizable emails and credentials in the error message only, not every field. - Self-hosted: list the snapshot fields (including the random deployment ID), and note that AI, integrations, and analytics follow the operator's configuration. - Sharing: cover workspace members, admins, and authorized agents; move legal and M&A disclosures out of the service provider list. - Add legal bases, consent withdrawal, and the right to complain, plus retention for billing records and analytics. Co-authored-by: multica-agent <github@multica.ai> * fix(web): build trust-page test dictionaries through createLandingDict (MUL-7558) main now passes a docs href to each landing dictionary factory, so the test's single-argument calls failed typecheck. Build the dictionaries the way the app does, mark the DOM-free test as node, and bring the new French mobile-app doc in line with the source-available wording. Co-authored-by: multica-agent <github@multica.ai> * fix(web): stop overpromising on data handling and align trust copy with product terms (MUL-7558) - The commercial-use FAQ now names both license triggers in every locale (offering Multica to outside users, and embedding it in a product you sell or distribute); ja and ko read as hosted-only before. - Replace "your data never leaves your network" and "code never passes through Multica servers" with what actually happens: agents run on your machines, workspace content is stored by Multica, coding tools send prompts to their model providers, and self-hosting keeps workspace data on your servers. Links to the privacy policy. - New and changed copy follows the terminology conventions: issue -> 任务/タスク/태스크, agent run -> 运行/実行/실행 (Run in English), workspace -> 工作区, onboarding -> 上手引导. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
54520de957 |
MUL-7646: fix(claude): report per-run usage for resumed sessions (#8792)
* fix(claude): report per-run usage for resumed sessions Co-authored-by: multica-agent <github@multica.ai> * fix(claude): preserve usage when counters reset Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Sol-Boy <sol-boy@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
1a09c65b1a |
feat(llm): support MULTICA_LLM_DISABLE_THINKING env (MUL-7162) (#8657)
* feat(llm): support MULTICA_LLM_DISABLE_THINKING env Co-authored-by: multica-agent <github@multica.ai> * docs(llm): scope reasoning_effort note to quick actions The disable-thinking docs claimed GPT-5.6-family models already get reasoning_effort=none from the server and do not need the new switch. That is only true for quick actions (GenerateJSON); chat auto-titling goes through GenerateText, which sets no reasoning field. Narrow the wording in .env.example and all four language docs so the translations stay in sync. Co-authored-by: multica-agent <github@multica.ai> * docs(llm): scope the disable-thinking note to accepting upstreams The previous wording called the switch "the only way" to turn off auto-titling reasoning, but on a standard OpenAI endpoint the switch adds chat_template_kwargs to the title request and the upstream rejects it, so titles fail instead of skipping thinking. Drop the "only way" claim and name the feature the way the list below does (follow-up questions). Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: zhudejun1 <zhudejun1@huya.com> Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: Multica Agent <agent@multica.local> |
||
|
|
4f5fbc9216 |
fix(vcs): explain untrusted TLS certificates and trust private CAs via Helm (MUL-7639) (#8761)
* fix(vcs): report untrusted provider TLS certificates on connect (MUL-7639) ConnectVCS reported every non-token failure as "could not reach the provider instance" and logged nothing, so a Gitea behind a private CA looked like a network problem. Log the underlying validation error and tell certificate failures (untrusted CA, host name mismatch, other verification failures such as expiry) apart from unreachable instances. Status codes are unchanged. Co-authored-by: multica-agent <github@multica.ai> * feat(helm): trust extra CA certificates in the backend (MUL-7639) backend.extraCACerts.configMap mounts an existing ConfigMap of PEM certificates read-only and points SSL_CERT_DIR at the system directory plus the mount, so the backend trusts an internal CA without dropping public CAs or disabling TLS verification. Unset renders unchanged. Co-authored-by: multica-agent <github@multica.ai> * docs(self-host): document trusting a private CA (MUL-7639) Co-authored-by: multica-agent <github@multica.ai> * fix(helm): render when values predate extraCACerts (MUL-7639) helm upgrade --reuse-values from an older chart carries no backend.extraCACerts key, and reading .configMap on it failed the whole render with a nil pointer even when no CA was wanted. Fall back to an empty dict, and cover the missing-key, default and enabled renders in the chart test. Co-authored-by: multica-agent <github@multica.ai> * docs(self-host): restart Compose backend after replacing a CA (MUL-7639) up -d keeps the running container when only a mounted CA file changed, so the backend kept its old trust store. Use restart instead. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
4f315a2714 | fix(dingtalk): avoid unnecessary issue link escapes (#8768) | ||
|
|
62efe26aa0 |
MUL-7620 feat(telegram): send and receive photos, videos and files (#8727)
* feat(telegram): send and receive photos, videos and files Telegram was text-only in both directions: dispatch dropped every non-text update with an "unsupported" notice, the outbound subscriber only knew sendMessage/editMessageText, and the channel was never declared to DeclareChannelFileDelivery, so agents were told they cannot attach files. Inbound: photos, documents, video, video notes, animations, audio and voice notes become chat attachments through the engine's MediaResolver seam (getFile, file host download, object storage, intent-ledger row before the PUT, as Slack does). The caption is the message text and a placeholder marker leads the sender's own text; the engine swaps it for the attachment link once the bind commits. Quoted and recent group context still go in front of it. Stickers and other non-file kinds keep the unsupported notice. Outbound: files the agent bound to its reply are sent into the chat as their own messages once the text settles (sendPhoto for common images up to 10 MB, sendVideo/sendAudio for mp4 and common audio, sendDocument for the rest up to 50 MB). A photo Telegram refuses to process is resent as a document. Both halves hang off the same store != nil branch that declares file delivery, so the agent is promised the hop only where it exists. Compared with the WeCom reference this drops relay routing, the three-state delivery classification, per-reply metrics and the pending/admitted counters: Telegram outbound is stateless HTTP and the delivery lease already guarantees a single sender per turn. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(telegram): address media review — deliver files once, admit before spawning, keep the notice without storage Review on #8727 raised five points; each lands here with a test. 1. A file-only reply could be delivered twice. closeTurn returned true whether this call ended the turn, a deeper attempt owned it, or it was settled already, and the empty-reply branch delivered files on all three. closeTurn now reports closedNow / closedAlready / closeHeld, and files go out only on closedNow. Regression tests: a replayed chat:done on another replica sends the files once; a completion an automatic retry has superseded sends nothing and the retry's answer still lands. 2. Admission sat behind the goroutine and the lookup. The attachment lookup now runs on the terminal worker, so a reply with nothing bound costs one indexed read and no goroutine; a delivery is spawned only for files known to exist, under a slot claimed first (non-blocking, four slots). Every slot busy sheds the reply with the notice instead of parking it. 3. Rebased on main over MUL-7585: recent group context, /new isolation and the privacy rule are kept; unaddressed group media is buffered and never a turn, as before; the media placeholder leads the sender's own text with quoted and recent context still in front. 4. Without object storage the member got no signal. The polling loop now knows whether the deployment stores media (ChannelDeps.AcceptsMedia, set from the same store != nil branch as the resolver) and keeps the old unsupported notice when it does not. A fetch that fails — over the 20 MB bot download limit, or a failed download — tells the sender so, instead of leaving the agent a placeholder nothing stands behind. 5. The failure notice claimed more than the code knew: a lost response may mean Telegram accepted the file. It now says delivery could not be confirmed and that the file stays attached in Multica, which holds either way. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(telegram): offset the media placeholder past prepended context; report a failed attachment lookup Second review round on #8727. 1. With quoted or recent group context ahead of the sender's text, the MediaRef carried only the placeholder, so the engine replaced occurrence 0: a member who typed "[Image]" in the window received the sender's file and the sender's own marker stayed bare. inboundMedia now carries PlaceholderIndex — the enrichers only prepend, so it is the count of the marker ahead of the sender's segment, literals included (the DingTalk rule) — and the resolver sets it as InlineIndex. Regression tests cover the recent-context and the quoted-message paths. 2. A failed ListAttachmentsByChatMessage dropped the files silently while the text already on screen referred to them. The read has no side effects, so it is retried three times 250 ms apart; if it still fails the member gets a notice worded for what is known — whether the reply had files could not be checked — never the one that presumes a file existed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
9df0cae78a |
MUL-7429: complete issues when every linked PR merges (#8758)
* feat(pr): complete issues when every linked PR merges (MUL-7429) Title/branch identifiers link a PR; keywords and the body no longer matter. Completion is evaluated only on a PR event for the issue (merge, link, unlink), so settings changes and reopening never complete an issue. Adds manual link/unlink with remembered exclusions and a per-issue auto-complete switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * feat(pr): issue-page PR automation and single auto-complete setting (MUL-7429) The issue's Pull requests section says what the merge rule will do next, links a PR by URL, removes one per row, and turns auto-complete off for that issue. The workspace switch lives under Issue statuses; the GitHub page reports it. Agent skill and docs describe the title/branch rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(pr): accept PR links pasted without a scheme (MUL-7429) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * test(skills): pin the PR auto-complete contract in the platform skill (MUL-7429) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(pr): re-check linked PRs at the completion write; skip stale GitHub events (MUL-7429) The status write now requires every linked PR to still be merged when it runs, so a PR linked between the decision and the write keeps the issue open. GitHub PR upserts ignore events older than the stored row, like the self-hosted path, so a late delivery cannot roll a merged PR back to open and turn a redelivered merge into a new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
0516dd57c3 |
MUL-7625: feat(antigravity): stream live tool execution events (#8734)
* feat(antigravity): stream live tool execution events * chore(i18n): remove obsolete French Antigravity live notice * fix(antigravity): preserve tool events under backpressure * fix(antigravity): retain tool errors in bounded previews * fix(antigravity): normalize tool parameters for transcript UI * fix(antigravity): move tool aliases and normalize file content |
||
|
|
d45aba1cd7 |
fix(tasks): settle supplements in application transactions (MUL-7630) (#8755)
Co-authored-by: Sol-Boy <sol-boy@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
4469bac632 |
fix(issues): duplicate mark follow-ups from #8741 review (MUL-7349) (#8752)
* perf(issues): resolve a page's duplicate originals in one read (MUL-7349) Filling duplicate_of read each distinct original with its own primary-key lookup, so a page of duplicates of different issues cost one round trip per row. Pages now pass the originals their rows point at to newStatusCategoryFiller, which loads them with one ListIssueRefsInWorkspace read, the same way labelsByIssue loads a page's labels. List, open issues, grouped, search and table rows do this; a response built on its own still resolves its original, so a caller that passes nothing is slower, never wrong. A pointer left on a reopened issue is not looked up at all, and an original that no longer exists is looked up once and left off. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): stop logging stale duplicate pointers as unmarks (MUL-7349) Servers from before the duplicate column reopened duplicates and deleted originals without clearing the pointer. After an upgrade, the next write to such an issue clears it, and the activity log compared raw pointers, so it logged "removed duplicate mark" for a mark that no longer counted and dropped the real status row in its place. Event publishers now send only live marks: a pointer counts while the issue is cancelled, the same rule reads apply. Separately, the status row is dropped only when a duplicate row was actually written to stand in for it; when the original is gone there is no duplicate row, and the reopen used to leave no activity at all. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): keep duplicate marks in background issue snapshots (MUL-7349) IssueToMap hardcoded duplicate_of to null on the assumption that no background publisher renders a marked issue. PublishAttachmentsChanged does: a channel /issue with media re-reads the issue after a download of up to 45 seconds, and a mark made in that window was broadcast as null. Clients patch their cache with that snapshot, so the list and board marker disappeared until the next refetch. IssueToMapResolved now resolves duplicate_of the way the HTTP rendering does; only a cancelled issue with a pointer costs a read. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
7be175346e |
MUL-7613: fix(agent): refresh live model catalogs (#8722)
* fix(agent): refresh live model catalogs Co-authored-by: multica-agent <github@multica.ai> * fix(agent): preserve live-only Codex settings on catalog fallback Co-authored-by: multica-agent <github@multica.ai> * fix(agent): retain Codex explicit-standard CLI version gate Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
1539c54ec5 |
feat(issues): make duplicate marks traceable and one click from the original (MUL-7349) (#8741)
* feat(issues): make duplicate marks traceable and one click from the original (MUL-7349)
- Log duplicate_marked / duplicate_unmarked on the duplicate and
duplicate_added / duplicate_removed on its original, with the other issue
linked in the activity feed; the delete and GitHub merge paths now carry
both ends of the mark so those rows are written too.
- Expose duplicate_of_issue_id on issue responses (only while cancelled) so
list rows, board cards and table cells can show an arrow to the original
that opens it directly.
- Banner links as a whole to the original and shows its status; the undo
action becomes the secondary "Not a duplicate" with a toast.
- Sidebar duplicates show reporters and a count; the status picker reads
"Change original" on an issue that already is a duplicate; the mark
picker opens with same-title and recently viewed candidates.
* fix(issues): keep the duplicate pointer in step with CI shape tests (MUL-7349)
- Add duplicate_of_issue_id to the CLI --fields whitelist, which the
list-endpoint shape test checks against a real response.
- Project the column in the search parity test's legacy query copy and
scan it, so both queries read through the one scanner.
- Mount the row marker's query and navigation hooks only when the issue
is a duplicate, so surfaces rendered without a QueryClient (the table's
inline title) and thousands of ordinary rows subscribe to nothing.
* fix(issues): resolve a duplicate's original on the server and log one row per mark change (MUL-7349)
- Issue responses carry duplicate_of {id, identifier, title, status}, filled
beside the status category only while the issue is cancelled and the
original still exists; the bare pointer is no longer exposed. Rows read it
directly, so the marker makes no request and is a real link where the row
is not one.
- A mark change suppresses the generic status_changed row; the unmarked row
records where the status went. Covered through the real UpdateIssue path,
with the test server wiring activity listeners like main.go.
- The picker applies one selectability predicate to search results and
suggestions, so an existing duplicate can no longer be chosen.
|
||
|
|
8355c7aeca |
fix(cli): name the TLS handshake timeout and stop masking it on login (MUL-7572, GH #8654) (#8744)
A Windows user whose network dropped the two-packet TLS ClientHello that Go 1.24+ sends by default (post-quantum key share, ~1.5 KB) saw only "Sign-in did not complete: the server could not issue an access token" and, on the --token path, "make sure it is valid and not expired". Neither mentioned the network, and the generic timeout copy pointed at MULTICA_HTTP_TIMEOUT, which does not govern the handshake. - classify net/http's "TLS handshake timeout" as its own kind, with copy that names the GODEBUG=tlsmlkem=0 check in both languages - let `multica login` keep the transport copy for transport failures; its sign-in copy now only overrides HTTP refusals - troubleshooting entry (en/zh/ja/ko) Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
93ae552ba9 |
feat: add messages to active Claude Code and Codex runs (MUL-7565) (#8682)
* feat: add live run supplements (MUL-7565) Co-authored-by: multica-agent <github@multica.ai> * fix: address MUL-7565 review feedback Co-authored-by: multica-agent <github@multica.ai> * fix: resolve MUL-7565 supplement panel and receipt review Co-authored-by: multica-agent <github@multica.ai> * refactor: remove MUL-7565 supplement ordinals Co-authored-by: multica-agent <github@multica.ai> * fix: close supplement composer after run remount * fix: exclude task supplements from completion reconciliation * fix: build a persistent binary for local daemon launches * feat: support Claude task supplements through SDK hooks * refactor: simplify task supplement delivery * test: remove redundant task supplement coverage * fix: remove redundant supplement delivery notices * refactor: extract local daemon launcher fix to its own PR * fix: preserve ordinary comment size compatibility * fix: accept empty task start acknowledgements * fix: bound task start response decoding and retries --------- Co-authored-by: Forge-Boy <forge-boy@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: yushen <ldnvnbl@gmail.com> |
||
|
|
9b8da372cc |
MUL-7450 fix(lark): surface the event-delivery check a silent Bot needs (#8735)
* fix(lark): surface the event-delivery check a silent Bot needs A Feishu app whose event subscription delivers to a request URL instead of the long connection binds successfully, shows Connected everywhere, and never receives a message. Multica has no webhook ingress, so those events go nowhere near us. Nothing in the product said so: the drop path in the connector writes no log line, an inbound audit row is only reached once a message enters the Router, and the troubleshooting docs listed only the causes this case is not. Three pointers, no behaviour change: - The connector logs the event type when the decoder declines a frame, so a socket that is up but only receiving events we do not handle is distinguishable from a healthy one. Heartbeats carry no event type and stay silent, so the log does not fill with noise. - The connected badge on the agent's Integrations tab names the long connection and links to the guide. That is where someone looks when the Bot stays quiet; the install dialog closes itself on success. - The integration guide's troubleshooting list gains the subscription mode and event subscription, in all four locales. Refs #8496 Co-authored-by: multica-agent <github@multica.ai> * fix(lark): report a dropped event type once per connection Review on #8735: one Info line per declined frame is unbounded. An app that subscribes to reactions or membership churn delivers those continuously, and there are thousands of Feishu installations. Report each event type once per connection instead. The map is read-loop-local, so it needs no lock and a reconnect re-reports. What the line is worth stays the same: it names what IS arriving on a socket whose drops leave no other trace. It says nothing about what is missing — an app delivering to a request URL sends no frames at all, so it produces no line, which is why install-time verification is the actual fix for #8496 rather than this. Also drops apps/web/next-env.d.ts from the branch: Next regenerated it during a typecheck run and it has nothing to do with this change. Refs #8496 Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
6ee01c33aa |
fix(cli): reference issue create attachments in the description (#8736)
`multica issue create --attachment` uploaded each file after the issue existed, stamping it with the new issue_id and leaving the description untouched. An issue renders only the files its description references, so the file was stored and reachable through the API while appearing nowhere on web, desktop or mobile. Upload the files first, unbound, append each one's markdown to the description (images inline, other files as a card), and bind every id via attachment_ids on the create call. Files already referenced by the description are not appended again, which keeps the quick-create path — where the user's pasted markdown is already in the body — from rendering the same file twice. Uploading first also drops the old partial-success state: a failed upload now means no issue was created, so the retry cannot duplicate one. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
96db419c0a |
feat: mark an issue as a duplicate from the status picker (MUL-7349) (#8728)
Enable step. The previous release added duplicate_of_issue_id and the
code that keeps it correct; this one lets people create a mark.
PUT /api/issues/{id} accepts a write-only duplicate_of_issue_id, which
sets status to cancelled and so reuses every status side effect
(status_changed event, stage barrier, wakeups). Marks are validated
under row locks on both issues: the target may not itself be a duplicate
and an issue with duplicates may not be marked, so the relation stays
one level deep and concurrent cross-marks cannot form a cycle.
GET /api/issues/{id}/duplicates returns both sides, and applies the
validity rule itself so a rollback past the previous release still reads
correctly.
The status picker on an existing issue gains a 'Mark as duplicate...'
action in its footer, outside the arrow-key listbox so it is never
mistaken for a status. A duplicate shows a banner linking to its
original with an unmark action; the original lists its duplicates.
Copy added for en, zh-Hans, ja, ko and fr.
Co-authored-by: J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
be085b1222 | fix(chat): avoid loading transcripts during cancellation (#8708) | ||
|
|
b7cdd76ad8 |
MUL-7349: feat(server): add the duplicate-mark column and keep it correct (1/2) (#8691)
* feat(server): add the duplicate-mark column and keep it correct (MUL-7349) Expand step for marking an issue as a duplicate of another. A duplicate is an ordinary cancelled issue that records which issue it duplicates; there is no duplicate status. This release adds the column and the code that maintains it, and nothing writes a pointer yet. - A mark survives only a write that leaves an already cancelled issue cancelled. Every other write drops it, so reopening removes a mark and cancelling again does not bring one back. Both status-writing queries carry the rule, which covers the UI, batch updates and background writers. - Deleting an issue clears the pointers of its duplicates in the same transaction, the way deleting a parent detaches its children. Shipping this before marking is what makes marking safe to roll back: the release a rollback lands on already clears the pointer. Tests seed a mark the way the next release will write it. Co-authored-by: multica-agent <github@multica.ai> * fix(server): compute issue updates from the locked row (MUL-7349) UpdateIssue built its next_* values in a CTE that read the row before the UPDATE took its row lock. A write that waited there behind another transaction then resumed with the values it had read before the wait and wrote them back, so an ordinary priority or status edit could restore a duplicate mark the other transaction had just cleared — by reopening the issue or by deleting its original — and did_change/did_activity were decided against the pre-wait row as well. Lock the row in the CTE instead, so every computed value comes from the row the statement is about to write. Tests hold the clearing transaction open and wait for the update to block on the row lock, so the interleaving is deterministic; both fail without this change. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
42727f697e |
MUL-7611: fix(comments): read only thread routing owners (#8712)
Comment owner routing loaded every task row on an issue, including context/result payloads, before selecting the current thread owners. Filter by issue and root comment in SQL and return one agent/squad pair per owner. Closes #8711 |
||
|
|
81f0fb418a |
test(execenv): stop the test repo template racing git auto maintenance (#8730)
The shared template repo is built once per test binary and handed to every test as an os.CopyFS copy, so nothing may write inside it after buildTestRepoTemplate returns. git commit breaks that: it forks a detached `git maintenance run --auto` and returns without waiting, leaving a background process that creates and removes lock files under .git/. CopyFS then lists .git/objects/maintenance.lock and fails to open it once maintenance has removed it, failing whichever test happened to call newTestRepo at that moment — most recently TestGitRootLockTimeoutDoesNotAdviseDeletingTheLock on an auth-only PR (#8706), which touches nothing in this package. Disable auto maintenance in the template's config. Copies inherit it, so the git commands tests run against their own copy cannot spawn a background process that races their assertions either. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
5403083420 |
MUL-7607 fix(auth): restore invitation-based signup (#8706)
Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
1879bc35e3 |
MUL-7351: fix(daemon): fence and retry task start acknowledgements (LANX-3) (#8400)
* fix(daemon): recover quick-create writes and task start acknowledgements * fix(daemon): fence task start retries by claim generation * fix(daemon): align start claims with main and canonical UTC |
||
|
|
d31fcbc9b7 |
MUL-7585 feat(telegram): inline recent group context on @mentions (#8673)
* feat(telegram): inline recent group context on @mentions When a member @-mentions the bot in a group, the agent only ever saw that single line. Lark solves this with a <recent_context> prefetch (lark/inbound_enricher.go, MUL-3084); Telegram could not copy it because the Bot API has no history endpoint — getUpdates is consume-once. Each installation's polling loop now keeps an in-memory ring of the last DefaultRecentContextSize (10) human group messages per chat and per forum topic, and inlines that window as a <recent_context> block ahead of an addressed group message, in the same recent → quoted → own composition Lark uses. The trigger and an explicitly quoted parent are excluded from the window; /new starts a chat without ambient context; p2p chats and unaddressed messages are never enriched. Unaddressed chatter is still never persisted or turned into a session (MUL-2671): it only surfaces as read-context on a turn a member directed at the bot, and the ring dies with the polling loop. The docs now say what the window contains and that Group Privacy must be off in @BotFather for the bot to receive the surrounding messages at all. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(telegram): state the recent-context retention and privacy contract accurately Review follow-up for #8673. Setup step 4 now presents Group Privacy on as the default, minimal-visibility choice and turning it off as the optional step that enables recent group context, with its cost spelled out. The Groups section no longer claims unaddressed chatter is "never stored": it says the message is held in a short in-memory buffer and saved with any addressed turn that pulls it in, that a reply to the bot triggers the context just like an @-mention, what Telegram delivers under each privacy setting, and that an admin bot receives everything regardless. Same fix in zh/ja/ko. The two code comments that relied on privacy mode being on, or claimed the message was never persisted, now describe the real contract. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
90e0bdf830 |
MUL-7586 Atomically create issues with custom properties (#8689)
* feat(issues): create with properties atomically Co-authored-by: multica-agent <github@multica.ai> * fix(issues): recover unavailable properties in create drafts Co-authored-by: multica-agent <github@multica.ai> * test(issues): isolate commit failure task assertions Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Forge-Boy <forge-boy@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
2b60f3b5de |
MUL-7594: fix(server): record creation activity for autopilot issues (#8684)
Autopilot and channel-created issues publish issue:created with a map payload; the activity listener only accepted the HTTP IssueResponse struct and silently dropped the creation activity. Reuse the existing extractIssueFields normalizer so both payload shapes record and broadcast the created entry. |
||
|
|
d2423aa35c |
MUL-7590: fix(execenv): support OpenClaw agents.entries per-agent workspaces (#8678)
* fix(execenv): pin per-agent workspaces under the OpenClaw agents.entries schema OpenClaw 2026.8 retired the agents.list config path and reports it as "Unknown config path: agents.list", moving the agents to a map keyed by agent id under agents.entries. The per-task wrapper only ever wrote agents.defaults.workspace, and an agents.entries.<id>.workspace entry outranks it verbatim: on a host where adding a second agent pinned the gateway agent to an absolute workspace, every task ran in that pinned directory instead of its prepared workdir, so the workdir skills/ never loaded and the agent edited the wrong tree. Read agents.entries when agents.list is gone, pin every entry workspace to the task workdir on write-back, and keep the registry fallback for the generations that have neither config path. The stdout-envelope branch now matches the path-aware wording, so a 2026.8 host that prints its envelope without exiting is read instead of failed closed, and the entries probe shares the agents.list deadline so the worst case stays three CLI deadlines. Co-authored-by: multica-agent <github@multica.ai> * test(execenv): cover the agents.entries probe, write-back and shared deadline Adds the regressions the review of the earlier attempt asked for: the current "Unknown config path" envelope with err == nil selects the entries probe and never the registry, both the plain and the managed-MCP flows pin every agents.entries.<id>.workspace to the task workdir, a valid-but-unset entries path falls back to the registry, and an envelope naming an unrelated path still fails closed. The worst-case budget test now drives the five-invocation chain and pins the config-schema pair to one shared deadline, so a fourth budget fails loudly. Co-authored-by: multica-agent <github@multica.ai> * fix(execenv): pin agents.entries workspaces without copying the entry Review feedback on the write-back: `config get` redacts the config before it returns the path it was asked for, so every sensitive per-agent field comes back as `__OPENCLAW_REDACTED__`. The wrapper is a sibling of the user's config in the $include merge and siblings win, so copying the resolved entry would write that sentinel over the user's real secret — memory search API key, web fetch headers, sandbox SSH credentials — for every task on the host, and nothing on OpenClaw's load path restores it. `agents.entries` is an object keyed by id and objects merge recursively, so `{"<id>": {"workspace": "<workdir>"}}` is enough to pin the workspace while every other field still arrives from the user's own config. The same reasoning drops the resolved payload from the row representation: an entries row now carries the id marker alone, so no redacted copy of the user's agent config is held in memory or written to the discovery cache file. Since only the source still decides the write-back, it is now carried explicitly (`openclawAgentsSource`: list / entries / registry) rather than as a boolean that effectively meant "not agents.list", and the source is recorded in the cache entry so a hit rebuilds the same wrapper. The cache shape version is bumped, so an entry written by an older daemon is a clean miss. Co-authored-by: multica-agent <github@multica.ai> * test(execenv): assert the entries write-back emits workspace alone Review feedback: the tests encoded the old contract (name / model carried into the wrapper verbatim). They now assert the wrapper carries exactly one key per entry — `workspace` — and that nothing else from the resolved payload reaches the file OpenClaw reads. The new regression drives the redaction shapes upstream's own redact-snapshot suite covers (memory search apiKey, web fetch headers, sandbox SSH credentials) through the entries probe of a real preparation, and fails if any of them ends up in the wrapper: with the copying write-back restored it reports the sentinel in `agents.entries.main`. The unit test for the write-back gate also covers a half-marked list, which a quiet per-row filter would have pinned anyway. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: tianshuai1 <tianshuai1@huya.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
8f1ad46ee8 |
MUL-7556: index GitHub PR address lookups (#8636)
* perf(db): index GitHub PR address lookups (MUL-7556) Co-authored-by: multica-agent <github@multica.ai> * perf(db): narrow GitHub PR address index (MUL-7556) Co-authored-by: multica-agent <github@multica.ai> * fix(migrations): renumber GitHub PR address index Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Forge-Boy <forge-boy@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
af62622e50 |
MUL-7288: fix(server): wake guest squad leaders on worker replies (#8314)
* fix(server): wake guest squad leaders on worker replies * fix(server): ignore tombstoned guest delegations * fix(server): preserve assigned squad routing under tombstones * test(server): narrow guest squad routing coverage Co-authored-by: multica-agent <github@multica.ai> * test(server): cover member-assigned guest squads Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Sol-Boy <sol-boy@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
5140b99d86 |
MUL-7577: remove no-start guidance from runtime and platform skill (#8680)
* fix(prompts): remove no-start guidance from runtime briefs Co-authored-by: multica-agent <github@multica.ai> * fix(skills): remove no-start guidance from platform instructions Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
da139a7c97 |
fix(wecom): name the two exits that leave a reply unaccounted for (MUL-7318) (#8348)
The WeCom outbound path had two silent exits: a completion whose delivery row named another platform, and one with no delivery row at all. From outside they were one quiet return, so an answer that reached the transcript but never the chat could not be told apart from ordinary traffic on the shared bus. Both now name themselves in the closed outcome set: not_wecom_turn (DEBUG), no_delivery_row (WARN, the one skip worth an alert — in practice a task enqueued before an upgrade), and route_unattributable (DEBUG) for a turn with no route and no batch owner to attribute it to. The metrics Help and logs draw the drop/skip line by obligation rather than by whether a frame was written, and tests pin that wording. The origin gate is one keyed read (GetTaskChannelOrigin) in place of GetAgentTask plus TaskInputIsChannelIngested. It also reports whether the batch owner is known, so an owner-less row is delivered when a route exists and stays quiet when none does — which keeps the auto-retry of a legacy channel turn answering its room. Co-authored-by: J <bohan@devv.ai> |
||
|
|
f41fae6b08 |
fix(cursor): own Windows background shells by launch Job membership (MUL-7417) (#8638)
* fix(cursor): own Windows background shells by launch Job membership (MUL-7417) captureCursorBackgroundProcess on Windows walked a machine-wide PID->PPID snapshot to prove a reported shell descends from the launch and to find its pre-existing children. Windows keeps a child's recorded PPID after the parent exits and reuses the PID, so the walk could reach a process owned by someone else; opening it failed with 'Access is denied' or 'not an owned descendant' and the whole capture aborted. The daemon then fell back to the idle watchdog and a Cursor run that later went quiet took the 10-minute watchdog path instead of completing. In CI this was TestCursorBackgroundLifecycle/multiple failing about 2 runs in 43. Take candidates from the launch's own Job Object instead (QueryInformationJobObject / JobObjectBasicProcessIdList). Descendants inherit the Job at creation and cannot leave it, so membership is the ownership proof: the parent-chain walk goes away, the PPID snapshot is used only to attach a member's pre-existing children, and a member that exits or is reused between listing and open is skipped instead of failing the capture. Creation ordering still tells a real child from one that points at a reused parent PID. Restore TestCaptureCursorBackgroundLateDescendant on Windows; it was excluded for exactly this race. ci.yml runs the Windows Cursor step with -count=20 on this PR only to show the race is gone; it is reverted before merge. Co-authored-by: multica-agent <github@multica.ai> * ci: restore -count=1 on the Windows Cursor background step The -count=20 stress run on this PR passed (run 35580440594, windows-execenv); the per-merge step goes back to a single pass. Co-authored-by: multica-agent <github@multica.ai> * fix(cursor): complete the Job member list and refuse unopenable live members (MUL-7417) Review follow-ups on #8638: - cursorJobProcessIDs grew its buffer only on ERROR_MORE_DATA. The kernel can also return success with NumberOfProcessIdsInList short of NumberOfAssignedProcesses, which a Job over the initial capacity or one growing during the query would hit; the short list then passed for the membership and could reject the target or leave children out. Retry until the counts agree, fail explicitly if they never do, and make the initial capacity a package var so a three-process tree exercises the growth path in a test. - The child walk skipped every open failure as 'exited or reused'. A live member whose DACL denies PROCESS_SET_QUOTA|PROCESS_TERMINATE also fails to open, and skipping it left the capture reporting success with that member outside the tool Job. Skip only PIDs proven to carry no member (handle outside the launch Job, or no longer listed after a re-query, confirmed by one more open); otherwise fail the capture so the caller takes its existing watchdog fallback. OpenProcess goes through a package var so a test can make a live member unopenable. Windows-only tests cover both, plus the plain claim-and-terminate path for a shell with a pre-existing child. Co-authored-by: multica-agent <github@multica.ai> * test(cursor): name the Job list growth test so the Windows CI filter runs it The windows-execenv step selects 'TestCursorBackground|TestCaptureCursorBackground'; TestCursorJobProcessIDsGrowsPastInitialCapacity matched neither and never ran. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |