chore: upgrade fork to latest upstream Multica - #9
Conversation
…ica-ai#7980) * fix(wecom): show the agent the message a sender quoted An aibot callback carries the content of the message the sender replied to, in `quote`. The adapter's aibotMsgCallback never declared the field, so it was dropped at the JSON boundary. That makes a reply unanswerable. "这个怎么处理" quoting an alert is a complete question in the chat and an empty one to the agent, which gets the four words and none of what they point at. The sender sees a bot that ignored what they were obviously asking about. Rendered as a labelled blockquote ahead of the sender's own words, using the same [Image]/[File]/[Video] vocabulary the media placeholders already use so an agent reading every channel through one prompt meets one spelling. A quoted 图文混排 nests one level deeper than a mixed run, which is the only reason quotedMessage exists rather than reusing mixedItem outright. Deliberately NOT in ownCommandSource: the command parsers read the first non-empty line, and a quoted line is not one the sender typed here. Prefixing it would let a quote of somebody else's "/issue …" file an issue nobody asked for — covered by a test. WeCom sends no msgid and no author userid alongside the quoted content, so it can only be rendered, never resolved back to a stored message. * fix(wecom): strip a bare directive that arrives behind a quote Review found the case the first commit missed: /new and /clear with no body of their own, sent as a reply. normalizeWeComControlLayout declines a directive with an empty body and no media on purpose — that is the shared pending sentinel, and consuming it would open a session with an empty first turn. A quote breaks the premise the gate rests on. "/new" replying to an alert is not an empty message; the alert is the whole of what the person sent. The gate could not see the quote, so the directive stayed in the body — and once rendered behind "> [Quote] …", Router could no longer strip it either: both of its routes (re-parsing Text, and Text == CommandText) are defeated by the prefix. /new was persisted as the first turn of the session it had just opened, and inherited as context by every later turn. /clear failed the other way: the FreshSession branch rewrote Text to the empty command body, so the quote — the only thing the person sent — disappeared without a word. A quote is now content, the same way media already is: it is the reason the directive-only layout is not the sentinel. hasMedia becomes hasOtherContent and the consumed-directive bookkeeping moves after the quote is prepended, so CommandText names the body that is actually left rather than reaching the same place through Router's empty-CommandText fallback. Router needed one change of its own, and no adapter could have made it: persistMessage read CommandText and media only, so a turn whose entire content is a quote looked empty. Text is now consulted too — by that point it is "" for a genuinely bare directive, which keeps the sentinel intact. Also bounds the quoted block at 500 runes. The sender quoted a document to point at it, not to resend it, and their own words follow the block. Pinned from both sides, because neither alone can see the defect: wecom/regression_quoted_directive_test.go proves the adapter hands over a stripped body, engine/regression_quoted_body_swallows_directive_test.go proves Router keeps it. Both files also pin the sentinel — a bare directive with nothing else must still pass through — so they fail on over-stripping as well as under-stripping. Verified by reverting each change in turn: the adapter revert fails the two adapter cases, the Router revert fails the /new case, and the sentinel tests pass throughout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xUxTgkvJSCUpBTJDWZTYF * fix(wecom): mark a quote as sender-selected context The rebase landed on main's HasSelectedContext (message.go:149), which says what this branch's Router change was reaching for and says it better: Text may carry context the sender chose rather than typed, and such context is input even when a control command has no body of its own. Router now derives persistStartedMessage and bareFresh from it, and DingTalk, Lark and Telegram already set it. So the Router edit is gone and the adapter sets the field: a WeCom quote is context the sender picked by replying to it. Behaviour is what review verified — a bare directive behind a quote is a turn whose prompt is the quoted message — but the rule is now main's, applied to one more adapter, instead of a second meaning growing on this branch's empty-Text check. That also answers the question review left open before merge. main decides that a selected quote is a turn; a WeCom quote is a selected quote. If that decision is revisited, it moves in one place for every channel rather than in each adapter. Pinned on both sides: the adapter test asserts the flag is set behind a quote and unset for a bare directive with nothing else, and the Router regression sends it. Hardcoding it false fails TestABareNewBehindAQuoteLeavesOnlyTheQuote. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wv2qRKBY6fCBnhVae7Mj58 * fix(wecom): give Router a command source for a quoted screenshot Review found the case the branch owns without having introduced it: this makes WeCom the third adapter that enriches Text, and the first that does not satisfy the invariant at engine/router.go:200-208. Quote a message, then reply with only a screenshot — an ordinary shape in a room, the quoted-screenshot case seen from the sender's side. ownCommandSource answers "" for a standalone photo on purpose: a placeholder is not words anybody typed. quotedContext then prepends the quote, and Router's fallback assigns the ALREADY enriched Text, so the Chat is named after the message the sender replied to: "[Quote] 生产库连接数打满了". Verified end to end through chatTitleSource / deriveFirstMessageTitle before fixing. The body as it stood before enrichment is the honest command source, which is lark's shape (ws_frame_decoder.go:113). It is still only what the sender sent, and the placeholder in it is dropped downstream by deriveFirstMessageTitle — so the title lands back on the media path the same screenshot takes with no quote, which the engine test asserts by comparing the two. Only fills an EMPTY command source, so a screenshot with "/issue 登录坏了" under it still files that issue, and the bare-/clear assignment above keeps precedence. Pinned from both sides, as everything else on this branch: the adapter test fails with "CommandText is empty behind an enriched Text", the engine test compares the quoted and unquoted titles and refuses to pass if the fallback it guards against stops leaking. Known and left as is: a bare /new or /clear behind a quote still titles the Chat "[Quote] …", because there the quote genuinely is all the person sent. Stripping the label belongs in title derivation, not here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wv2qRKBY6fCBnhVae7Mj58 --------- Co-authored-by: YeJing <yejing@wisefin.tech> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…son guards (multica-ai#8340) * fix(desktop): do not prepend PATH fallbacks over login-shell Node Prepending /usr/local/bin after fix-path let a stale system Node 12 shadow nvm/fnm, breaking shebang CLIs (CodeBuddy/OpenClaw) on --version. Append only missing fallback dirs so recovered shell PATH keeps precedence. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai> * fix(agent): align CodeBuddy stream-json guards with Claude Pass stderr into resume rejection, fail on prompt_too_long terminal_reason, and force foreground when Bash requests run_in_background — the same failure modes Claude already handles for Multica-managed headless runs. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai> * fix(agent): align CodeBuddy guards with real CLI protocol Address review on multica-ai#8340: force CODEBUDDY_CODE_DISABLE_BACKGROUND_TASKS, fail on system/task_* background events, and drop Claude-only prompt_too_long / async_launched handling that CodeBuddy 2.150.0 never emits. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai> * fix(agent): update CodeBuddy handleUser test after signature change Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai> * fix(agent): force CodeBuddy background-disable env last for Windows Append CODEBUDDY_CODE_DISABLE_BACKGROUND_TASKS=1 so os/exec's case-insensitive last-wins dedup keeps it over lowercase custom_env. Also extract Desktop PATH fallback helper with unit tests. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
* fix(chat): render canonical settled assistant answers Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): preserve task message arrival order Co-authored-by: multica-agent <github@multica.ai> * fix(chat): retain settled process narration 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>
…not fill the host disk (multica-ai#8510) * fix(agent): require OpenCode >= 1.1.54 so old builds cannot fill the host disk OpenCode <= 1.1.53 ignores TMPDIR, TMP and TEMP when its embedded Bun runtime extracts a native module, writing into the shared system temp directory whatever the daemon exports. Each successful run leaves one 4-8 MB module behind under a fresh, non-content-addressed name, so nothing ever reuses or overwrites it. Upstream fixed this in 1.1.54 (2026-02-10). Reproduced on Linux against pinned builds, verifying each binary's self-reported version: 1.1.49, 1.1.52 and 1.1.53 write into the shared /tmp with the three variables pointed elsewhere; 1.1.54 and everything after honour them. multica-ai#8392 is what that costs a self-hosted daemon left on a pre-fix build — ~2,960 files, 11.16 GiB, root filesystem at 99%, and no error anywhere until the disk runs out. The per-task temp directory cannot contain a CLI that never reads the variables, and deleting by filename in a shared /tmp is not safe — a module another live process still has dlopen'ed looks identical. Refusing the CLI at registration is the only place this can be stopped, and it turns a silent host outage into "opencode version X is below minimum required 1.1.54 — please upgrade". Note this floor is unlike the others in the table: every existing entry names a protocol the backend speaks through, so an older CLI cannot serve a task at all. A pre-1.1.54 opencode serves tasks correctly; it damages the host while doing it. The table comment now says which kind each entry is. Refs multica-ai#8392 Co-authored-by: multica-agent <github@multica.ai> * docs: list the OpenCode minimum runtime version The install-agent-runtime callout enumerates the per-runtime floors the daemon enforces at registration; OpenCode now has one, so it belongs in the list. Updated in all four translations. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: J <agent@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
…s environment-independent (multica-ai#8512) Four tests in this package failed for every agent running the suite from inside a Multica task, and passed in CI: TestHermesMemoryStorePathLayout TestPruneHermesMemoryStores TestHermesSessionStorePathLayout TestPruneHermesSessionStores They isolate themselves by pointing HOME at a t.TempDir() and then assert a path under $HOME/.multica. But HermesMemoryStorePath / HermesSessionStorePath resolve through cli.ProfileDir, and multicaConfigRoot consults MULTICA_TASK_CONFIG_ROOT before it ever reaches HOME — returning that root directly, without the .multica segment. The daemon sets that variable for every task it runs (taskMulticaEnvironment), so overriding HOME had no effect and the tests were asserting against a branch they never took. CI never sets the variable, so it stayed green and the trap only ever hit agents. Clearing the variable once in TestMain makes the package resolve profile dirs the same way in both environments. Preferred over a per-test t.Setenv(cli.TaskConfigRootEnv, "") for three reasons: - It also covers TestPruneHermesMemoryStoresDisabled and TestPruneHermesSessionStoresDisabled, which passed for the wrong reason: their stores were never created under the root the pruner scans, and the assertions held only because retention <= 0 disables the pruner outright. - t.Setenv cannot be called from a parallel test, so the per-test fix would force future tests that resolve a profile dir to choose between running in parallel and running in the right environment. - New tests in the package are correct by default rather than by remembering to copy a line. A test that wants the task-local branch still sets the variable itself. Verified on the same commit, whole package: go test ./internal/daemon/execenv/ -count=1 ok env -u MULTICA_TASK_CONFIG_ROOT go test ./internal/daemon/execenv/ ok go test ./internal/daemon/execenv/ -count=1 -race ok go vet ./internal/daemon/execenv/ ok Co-authored-by: multica-agent <github@multica.ai>
…ared profile (multica-ai#8483) Deleting a profile-backed runtime instance is refused while its profile exists, and the refusal said "delete its runtime profile instead". For the case that produces it most often — a retired machine's leftover row in a profile other, healthy machines still use — that points at a workspace-wide delete which takes those machines' runtimes too, and which a bound agent refuses anyway with a second message naming no agent and no machine. Retention GC already deletes these rows after 7 days offline, ignoring profile_id, so the refusals now describe that instead of recommending the destructive route: - Instance refusal is status-aware, and only promises automatic cleanup when it matches the gate gcRuntime actually applies: the runtime's own undrained tasks plus those owned by every user agent bound to it, archived included. - Profile refusal names the blocking agents and the machine each sits on, with a per-class remedy — ordinary agents can be reassigned or archived; Mika cannot be archived but can be rebound; a Builder session is reachable only by its creator. Those clauses come from counts over the whole blocker set, so a class outside the 20-row sample is still reported. - Response bounded at 20 entries with an exact total, capping rows read inside the locked delete transaction. - CLI surfaces the server's sentence instead of dumping the raw JSON body. When a delete is allowed or refused is unchanged; the guard predicates and the instance-refusal error code are untouched. The remaining capability gap — retiring an offline instance on demand — is tracked separately. Fixes MUL-7409
…multica-ai#7623) `multica runtime profile set-path` was accepted on Windows but the daemon never used the override: the gate required a Unix executable bit, which Go never sets on Windows files (0666/0444 for regular files; 0111 only on directories). Every override — including a plain .exe — read as not executable, so the daemon fell back to PATH: either failing registration with a message that blamed the user's filesystem, or silently registering a different install than the pinned one. appendProfileRuntimes now resolves the override through resolveAgentExecutablePath (exec.LookPath) — the same contract agent launches use — and records the resolved path rather than the raw override, so Windows PATHEXT completion picks up .cmd shims and extension-less pins. Unix exec-bit checks are unchanged, and a stale or mistyped override still falls back to PATH with the existing warning. Regression coverage runs on the real Windows runner via the existing windows-execenv job. Fixes multica-ai#7613
…ultica-ai#8516) The integrations directory derived each channel's "Connected" badge from `installations.length > 0`. That read is correct for GitHub and VCS, which hard-delete their row on uninstall, but wrong for the five IM channels: revoking a bot flips `status` to 'revoked' and KEEPS the row for audit, so the count can never fall back to zero. A torn-down Feishu bot kept showing a green "Connected" on the Channels card while its own detail row correctly rendered 已撤销 — same payload, same cache, two different answers, and refreshing never converged because it was a derivation bug, not staleness. Read `status === "active"` through a shared `hasActiveInstallation` helper, matching what every other consumer of that payload already does. Unknown or missing states fail closed to "not connected", consistent with the existing schema decision in packages/core/api/schemas.ts. GitHub and VCS keep their count-based reads. Refs multica-ai#8496 — that report bundles three defects and this fixes one; inbound delivery and external-revocation detection remain open.
Co-authored-by: Sol-Boy <sol-boy@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai>
…not only after a half-width space (multica-ai#8502) The mention picker inherited Tiptap's default `allowedPrefixes: [" "]`, so an `@` only opened it after a half-width space or at the start of the text. CJK is typed with no separator (or with a full-width U+3000 from the IME), which left the picker unreachable. The boundary rule now lives in `packages/core/markdown/mention-boundary.ts` and is shared by the web/desktop editor and the mobile composer: an `@` glued to a Unicode word character stays inert (`user@example.com`, `josé@example.com`, `почта@mail.ru`), while scripts written without word spaces open it (`你好@Mi`, `こんにちは@Mi`, `안녕하세요@Mi`). `用户@example.com` opening is a documented, accepted ambiguity. The MUL-5429 typed-`@` provenance check is unchanged, so a pasted `@` still does not open the picker.
) The cmd/multica tests redirect os.Stdout into an os.Pipe and read it only once the command has returned. Nothing drains the read end in between, so a command can print no more than the pipe will buffer before its write blocks and never completes. On Linux that buffer is 64KB and nothing here reaches it. On macOS a pipe with no reader stops accepting writes after 512 bytes, so any test whose command prints a sizeable JSON payload deadlocks outright: go test ./cmd/multica/ runs until the binary's timeout instead of finishing, and -run on a single such test reproduces it on its own. Start the reader before running the command, in every place that captures a pipe this way. captureStderr already did this; the shape just had not reached the stdout side or the copies inlined into individual tests. The package went from never completing inside a 500s timeout to passing in under two seconds.
* fix(daemon): durably replay terminal reports Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): harden terminal report replay 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>
…ogress (multica-ai#8495) * fix: distinguish cancelled child work from done in stage progress * test: align cancelled stage batch expectation * fix: preserve named-stage cancellation warning in batch * test: cover historical cancellation in batch-closed stage * style: restore trailing newline in batch test
…k attribution (multica-ai#8412) * fix(dingtalk): preserve answer context in quoted replies * fix(dingtalk): narrow quoted reply handling to core behavior * fix(dingtalk): apply conservative quote policy to card nodes Apply dingTalkReadableQuotedText to raw card TEXT and LINK values. Mark filtered nodes unavailable while preserving neighboring text, links and image placeholders. Update captured-snapshot expectations and cover node-local degradation. Follow the conservative policy accepted by Bohan-J in PR multica-ai#8061: withhold legitimate quoted content containing || rather than pass an unrecognized opaque envelope to the agent as the sender's words. This is a projection policy, not a vendor-defined opaque-content detector. Review decision: multica-ai#8061 (review) Original concern about heuristic false negatives: multica-ai#8061 (review) Opaque sample: open-dingtalk/dingtalk-stream-sdk-go#22 Validation: DingTalk package race tests, callback ACK regression, and git diff --check passed. Updated card regressions failed before the fix.
* fix(agent): bound silent Pi provider failures Co-authored-by: multica-agent <github@multica.ai> * fix(agent): preserve Pi errors across cancellation 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>
…i#8489) * fix(comments): explain @ALL member-only behavior * fix(comments): clarify @ALL broadcast semantics Do not promise notification delivery for edits or zero-recipient drafts. Keep the preview tied to the structured @ALL token and cover both states. --------- Co-authored-by: 高春晖 <gaoch@lianlianpay.com>
…ultica-ai#8321) Adds "Project status" as its own issue filter dimension, next to "Project": pick "In Progress" once instead of ticking every active project, and the result follows project lifecycle changes instead of freezing a project-id snapshot. Server side extends the existing issue table filter protocol with an optional, validated `project_statuses`, filtered through a parameterized, workspace-bound EXISTS on `project`. No migration and no new index — the predicate resolves through `project_pkey`, and the planner falls back to the existing `idx_issue_project_status`. Client side wires it as a normal dimension: filter menu, chip, reset, saved views and Gantt/Swimlane, with the project catalog treated as "cannot answer yet" rather than "no filter" while it loads. Project status changes, deletes and realtime project events invalidate the issue table cache, since issue rows themselves do not change. Default off; closing the filter reverts the behavior.
Co-authored-by: multica-agent <github@multica.ai>
…lure (multica-ai#7805) * fix: fail private runtime owner mismatches (PUCK-89) Co-authored-by: multica-agent <github@multica.ai> * fix: keep runtime mismatch repair off idle claim path * test: cover mixed runtime claim outcomes (PUCK-89) Co-authored-by: multica-agent <github@multica.ai> * fix: settle runtime owner mismatches during claim * fix: reject ownerless agents on private runtime claims * chore: restore claim version sampling layout (PUCK-89) Co-authored-by: multica-agent <github@multica.ai> * fix: re-authorize delivery at the finalize boundary, dedicated runtime_access_denied client copy, fixture convention (PUCK-89) Blocker 1 — final delivery gate: FinalizeTaskClaim now runs a caller- supplied authorize closure inside its transaction. The singular and batch claim paths share finalizeClaimDelivery, which re-locks the runtime row (FOR UPDATE), re-reads the agent, and re-verifies the private-runtime owner fence against CURRENT ownership before the task token commits. A concurrent re-registration that would change owner_id blocks until the gate commits, closing the stale-snapshot TOCTOU. Mismatched tasks settle through the existing failClaimedTaskBeforeLaunch -> FailTask path; singular keeps 200 {"task":null}, batch skips the task and keeps returning valid ones. Regressions cover both paths. Blocker 2 — client copy: runtime_access_denied gets dedicated actionable copy (make runtime public / rebind-copy agent) on the issues blocked-trigger mapping, both chat send-failure toasts, the chat failure-reason map, the mobile dispatch-reason helper, and the autopilot run-now toast. Generic fallbacks unchanged; locale keys added for en/ko/ja/zh-Hans. Blocker 3 — fixture convention: runtime_access_denied_test.go now uses testutil.Call; daemon_runtime_access_test.go seeds its chat task via the dbfx.Task fixture. Test semantics unchanged. Co-authored-by: multica-agent <github@multica.ai> * fix: bind final task token to locked runtime owner Co-authored-by: multica-agent <github@multica.ai> * fix: keep delivered user context on current owner Co-authored-by: multica-agent <github@multica.ai> * test: use testutil.Call in settlement-failure claim regression Convert the last manual httptest.NewRecorder flow in TestFinalizeClaimDelivery_SettlementFailureIsUnsettled to the repository's required testutil.Call helper (hard convention from review) and drop the now-unused net/http/httptest import. No production code changes. Co-authored-by: multica-agent <github@multica.ai> * PUCK-132: settle queued private-runtime owner mismatches as runtime_access_denied Align persisted task failure semantics with admission semantics: queued private-runtime ownership mismatches now settle as runtime_access_denied, letting clients reach the dedicated recovery copy instead of the generic invalid_task_identity copy. Actual task/agent identity violations (agent rebound, agent deleted, response identity mismatch, error_agent_runtime_changed) keep invalid_task_identity. - add taskfailure.ReasonRuntimeAccessDenied (permanent, non-retryable ownership authorization failure; agent process never launched) - swap the reason in the two owner-mismatch settlement paths in handler/daemon.go (response-assembly recheck + delivery-gate authz-error default branch, including ownerless-runtime denial) - regression A (queued issue task), B (queued chat task + assistant failure message via existing FailTask path), C (agent rebind keeps invalid_task_identity) Co-authored-by: multica-agent <github@multica.ai> * PUCK-132: map persisted runtime access failures in clients * fix(i18n): add missing fr translations for runtime_access_denied keys Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: worker-opencode <worker-opencode@multica.local> Co-authored-by: puck-181 <puck-181@local>
* fix(chat): stream first agent text sooner (MUL-7465) Co-authored-by: multica-agent <github@multica.ai> * fix(codex): make delta streaming lossless (MUL-7465) Co-authored-by: multica-agent <github@multica.ai> * fix(codex): retain mismatched pending deltas (MUL-7465) 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>
…der can't wedge a turn (multica-ai#5878) Owning the runtime process tree (multica-ai#7522) made cmd.Cancel kill every descendant in that tree, so the usual pipe holder dies with it and both readers reach EOF. A descendant outside the tree does not: on POSIX because it called setsid and left the process group, on Windows because startOwnedProcessTree failed open and the child runs unowned, so the kill reaches the leader alone. The join after the forced shutdown was unbounded, so the turn then hung forever: no tool result, no reply, nothing until the user cancelled by hand. Reap the process as part of that forced shutdown instead. cmd.Wait() returning is what closes the parent ends of the pipes, so it frees a reader the kill could not reach, and the 10s WaitDelay the claude, codearts and antigravity backends already use bounds Wait itself once the context is cancelled. The readers are still joined before the buffers are read, so providerErr.Finalize keeps its documented precondition — a fully drained stderr pipe — instead of racing the copier and splitting a provider error across its flush. The same reap now runs in the deferred cleanup, where joining the readers before the goroutine closes msgCh keeps a live reader from panicking in trySend. What this buys is a bounded recovery, not a repaired turn: past the existing 2s drain grace the backend delivers from the buffers it has, so an exceptional shutdown can report a turn without output the readers had not handed over yet. It does not fix the Windows terminal tool — that belongs in hermes' own bash startup probe (NousResearch/hermes-agent#73403, still open) — and it does not reap the escaped process, which is out of the daemon's reach by definition. Co-authored-by: multica-agent <github@multica.ai>
…ai#8539) Co-authored-by: Sol-Boy <sol-boy@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai>
…i#8519) * fix(issues): preserve quick-create original input Co-authored-by: multica-agent <github@multica.ai> * fix(issues): validate quick-create origins 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>
…ultica-ai#8538) * fix(autopilots): make an existing schedule editable again (MUL-7478) The edit dialog's schedule panel locked whenever an autopilot carried two triggers of any kind, and pointed at a detail page whose trigger rows were read-only. Between them there was nowhere in the UI to change a schedule: the only way out was deleting the autopilot and building it again. Two halves, and both are needed — the narrowed lock alone would leave the notice pointing at a page that still cannot edit, and the row editor alone would leave the most common case (one schedule beside one webhook) taking the long way round: - Count only schedule-kind triggers when locking. A single schedule beside a webhook has exactly one row a cron can land on, which is the row the save path already targets, so there was never an ambiguity to protect. - Give each schedule row its own editor, opening on the schedule it runs and patching that trigger alone. Label and enabled ride along, so pausing a schedule no longer means deleting it. - Replace the greyed-out editor behind the lock with a notice. A disabled editor still showed the first of the schedules as if it were the whole story; the notice says how many there are and sends the reader to the Triggers list already on screen behind the dialog. Co-authored-by: multica-agent <github@multica.ai> * fix(autopilots): send only the fields the schedule editor changed Review found the new per-row dialog writing a full row on every save. Three consequences, all of them real: - The server reads a changed cron / timezone / enabled as a substantive edit and republishes the rule version, moving the trigger's accountability to whoever saved. Since parseCron -> toCron normalizes (a bare cron on a zoned row returns carrying its TZ= prefix), even a rename resent a textually different expression and took that responsibility with it — the exact over-transfer MUL-4302 settled. - A dialog left open while someone else edited the same trigger wrote its own stale reading back over their change. - A save with nothing edited still made the server recompute and rewrite next_run_at from an expression the user never touched. The dialog now snapshots the trigger at mount, sends only the fields that moved, and has nothing to save until one does. The cron gate applies only to a write that carries a cron, so a row whose stored expression the server can no longer preview can still be switched off. Two more from the same review: - The label input stayed editable while the submit validated over the network. A label typed in that window was not in the request already in flight and vanished under its success toast; it locks with the editor now. - A disabled trigger keeps its next_run_at — the dispatcher filters on `enabled` rather than clearing the column — so the row claimed "Disabled" and a next run in the same breath. The badge is the true one. Co-authored-by: multica-agent <github@multica.ai> * fix(autopilots): measure schedule edits against the row as it was opened The dirty checks read `trigger.label` / `trigger.enabled` live off props while their inputs were seeded once at mount. The detail query refreshes under an open dialog — a teammate saving this same row — and the component does not remount, so an untouched control and its prop drifted apart and the field counted as this user's edit. Save then sent the value they never set, back over the one that had just landed. Every baseline is now snapshotted at mount together, and the label baseline is trimmed the way the submitted value is, so a stored label carrying stray whitespace does not arm Save the moment the dialog opens. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai>
…ultica-ai#8519)" (multica-ai#8546) This reverts commit a647204.
…op the skill section (multica-ai#8548) Co-authored-by: multica-agent <github@multica.ai>
…ltica-ai#8543) * docs(changelog): add v0.5.0 release entry (2026-09-18) (MUL-7481) Adds the 0.5.0 changelog entry to all four landing locales and bumps the web and desktop app versions. Range v0.4.44 → 594ff89 (55 PRs). Minor bump because the release ships full French product UI, which is worth announcing on its own. Left out of the changelog, as agreed on MUL-7481: Triage (triage_v1 not rolled out), the plugins_v1 / billing_workspace_subscriptions gate error semantics, UI Lab, CI/test/docs, prompt and skill changes, the Issue status category migration, and internal performance work. Co-authored-by: multica-agent <github@multica.ai> * docs(changelog): retitle the v0.5.0 entry around agent behavior (MUL-7481) The title listed the top of `features` and carried a small feature (Autopilot schedule editing) while leaving out the day's agent work. Drop the small feature, keep the French UI, and put the agent side in: runs are both steadier (Grok/Pi/Copilot/Codex/Cursor/Hermes, durable terminal reports) and leaner — a resumed agent no longer re-reads the whole Issue and every comment (multica-ai#8377, multica-ai#8488). That last one now has its own improvements line so the title stays backed by the entry. 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> Co-authored-by: Bohan-J <bhjiang@outlook.com>
…6) (multica-ai#8742) Wakeup fire times are absolute instants, but the UI formatted them in the wakeup's stored timezone. The CLI stores UTC by default, so a one-time wakeup at 14:05Z read as "Today 14:05" for a viewer in UTC+8 instead of 22:05 local, with no timezone label. Format next_fire_at with the Viewing Timezone preference everywhere (sidebar summary, details, workspace wakeups list). The stored timezone still describes cron schedules only. Closes multica-ai#8740 Co-authored-by: multica-agent <github@multica.ai>
… (MUL-7572, GH multica-ai#8654) (multica-ai#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>
…riginal (MUL-7349) (multica-ai#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.
* 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>
Co-authored-by: Sol-Boy <sol-boy@multica-ai.local> 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>
…2) (multica-ai#8748) Co-authored-by: multica-agent <github@multica.ai>
…ltica-ai#8749) * docs(changelog): add v0.6.0 release entry (2026-09-23) (MUL-7630) Co-authored-by: multica-agent <github@multica.ai> * docs(changelog): switch release entry to v0.5.2 and tighten title (MUL-7630) Co-authored-by: multica-agent <github@multica.ai> * docs(changelog): fold steering into a single entry (MUL-7630) Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai>
…UL-7349) (multica-ai#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>
…multica-ai#8755) Co-authored-by: Sol-Boy <sol-boy@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai>
…-7637) (multica-ai#8757) * docs(i18n): translate documentation corpus to French (MUL-7637) Add French (.fr.mdx) translations for all 45 docs pages plus meta.fr.json navigation, mirroring the English source. Product and UI terms follow the in-app French locale at packages/views/locales/fr/ (issue -> tâche, run -> exécution, autopilot -> automatisation, Chat -> Discussion, Settings -> Paramètres, etc.). Status and role identifiers stay lowercase English per the conventions glossary, with the French UI label shown once where they are defined. Co-authored-by: multica-agent <github@multica.ai> * feat(docs): serve French docs locale (MUL-7637) Register fr in the docs i18n config and add the French Fumadocs chrome, language-switcher label, and home hero copy. Fumadocs maps fr to Orama's built-in French tokenizer, so search needs no custom localeMap entry. Co-authored-by: multica-agent <github@multica.ai> * feat(i18n): link French UI to French docs (MUL-7637) Route French users from the landing page, runtime guides, the autopilot webhook filter help, and the Slack/Lark/DingTalk/Telegram setup links to /docs/fr. The four integration tabs and the runtime helper each carried a copy of the same locale-prefix ternary; they now share docsLocalePrefix. Co-authored-by: multica-agent <github@multica.ai> * fix(i18n): localize remaining docs entry points (MUL-7637) French users still reached English docs from the Help menu, the Agents and Skills "Learn more" links, and the landing footer. The views links now use docsLocalePrefix; the landing dictionaries take the docs href from docsHrefForLocale, so locales that reuse another dictionary (French reuses English) still link to their own docs. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai>
…628) (multica-ai#8746) * fix(issues): order thread replies by send time, not by trigger (MUL-7628) multica-ai#8131 took run replies out of the thread's chronological reply list (multica-ai#4033) and re-inserted each one after the comment that triggered it. With two agents working in one thread, the agent asked first but finishing last landed mid-thread, so the newest reply was not at the bottom. A run that replied, or ended without replying, now sits at that time like any comment (MUL-7211). A run still working stays right after the input it answers, matching how an active standalone run keeps its enqueue slot (MUL-7632). Everything one run posted still renders together in posting order (MUL-7548). When a run's input is not the row directly above, its reply (or activity block) shows a "Replying to <name>: <preview>" line that scrolls to and flashes that input. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): place each run comment in a thread at its own time (MUL-7628) A run's earlier comments were kept together in its slot at the time of its latest one (MUL-7548), so a reply written between two of them read after both. Now that every thread comment sorts by its own time, that grouping only reorders the conversation: sort each run comment on its own. The run slot is its latest comment, and the "Replying to" line looks past the run's own earlier comments when checking what is above. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai>
…ca-ai#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
* 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>
…multica-ai#8763) * feat(attachments): full-window viewer that pages through every file (MUL-7642) The preview was a centered card capped at max-w-6xl, so a screenshot never showed larger than 1152px wide whatever the screen, and prev / next only walked images. - The viewer takes the whole window: no card, a near-black stage, a top bar in the dark token set, prev / next in 64px side gutters, type / pixel size / file size instead of the MIME type. Markdown and text read on a centered sheet in the app theme; PDF and HTML fill the stage. - The surface sequence now covers every previewable kind, so an issue pages through its screenshots, PDFs, reports and HTML in render order. `collectAttachmentSequence` takes the inclusion rule; mobile keeps the images-only `collectImageSequence`. - The issue description block no longer lists every issue attachment as its own: comment uploads keep issue_id, so they were counted at the description's position. - A focused video / field keeps its arrow keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(attachments): viewer image swap and desktop window chrome (MUL-7642) Review of multica-ai#8763: - Paging from a PDF (or any non-image) onto an image that had not decoded yet handed the PDF's URL to <img>; its load error was blamed on the image being opened, which got marked broken and skipped. The settled-URL hook now only ever holds a URL that decoded as an image, and the <img> reports errors only when it shows the current file. - On desktop the full-window top bar sat under the macOS traffic lights and over the app's top-bar drag region. The viewer now enters immersive mode while open, marks itself no-drag, lets its top bar drag the window, and opts the bar's controls back out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(attachments): viewer waits for the media re-sign before loading (MUL-7642) On clients that cannot load `/api/attachments/{id}/download` natively (desktop, split-origin web, self-host proxy mode) the viewer handed that endpoint to <img> while the re-sign / authenticated byte fetch was still in flight. The native load failed, and in a sequence the failure read as a broken image: it was marked unavailable and skipped before the loadable URL arrived. `useResignedInlineMedia` now also reports whether the upgrade is pending. The viewer holds the previous frame (or shows loading) until it settles, and the neighbour prefetch waits too. The inline figure keeps its behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(attachments): no focus ring around the viewer stage on open (MUL-7642) The viewer focuses its zoom canvas on open so +/-/0 and the arrows work without a click. That programmatic focus can match :focus-visible, and on the full-window viewer the canvas ring drew a light frame around the whole stage. The canvas now marks focus it placed itself; the stylesheet drops the ring for it until focus leaves, so tabbing back in still shows 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>
…ultica-ai#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 multica-ai#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 multica-ai#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>
…a Helm (MUL-7639) (multica-ai#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>
…i#8702) Add a standalone Simplified Chinese UI to the mobile app, with a Settings → Language picker (Follow system / English / 简体中文) whose manual choice persists across restarts. Mobile keeps its own i18next resources rather than reusing web/desktop copy, following the conventions.mdx glossary: issue statuses and categories stay lowercase English identifiers, Squad is 小队. - Failure reasons: REASONS now mirrors web's REASON_LABEL, and a drift test pins it to the en/zh-Hans failure_reason keys. - Production env: .env.production stays committed with the public multica.ai defaults; the *:prod scripts layer a gitignored .env.production.local on top for self-hosting or a custom bundle ID. - Duplicate-mark activity rows from multica-ai#8741 are localized too.
…ca-ai#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>
…multica-ai#8790) The French pages from multica-ai#8757 were translated before multica-ai#8727 landed, so both still said the Telegram bot only accepts text. Translate the current English passages, reusing the terms the French pages already use.
…tica-ai#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>
…available wording (MUL-7558) (multica-ai#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>
…ica-ai#8794) * feat(pr): only a closing keyword completes an issue on merge (MUL-7672) multica-ai#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 multica-ai#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 multica-ai#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 multica-ai#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>
…ltica-ai#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>
There was a problem hiding this comment.
This upstream merge brings in 122 commits and 77 database migrations (schema 467→547). The code changes I reviewed function correctly. Key observations:
Production deployment carries significant risk due to the migration scope. The PR description documents the necessary precautions: maintain database backups, keep workers stopped during migration, and have a database restore plan ready since image-only rollback is insufficient.
Verify that the Singapore deployment environment variables (CODERPUSH_BACKEND_IMAGE and CODERPUSH_WEB_IMAGE) are set to verified digest references before deploying.
You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.
What does this PR do?
Update the CoderPush fork to upstream
e909e9c89d524cd54c1d5f4fa5963882093efdc9and prepare immutable backend/web images for the Singapore deployment. Preserve the fork's duplicate-assignee guard and completion instructions while retaining upstream cancellation handling.Related Issue
User-requested production upgrade from v0.4.43 plus handoff backport.
Type of Change
Changes Made
How to Test
Risks
Production moves from schema 467 through 77 migrations. Migration 468 deletes obsolete link rows and drops columns. An image-only rollback is insufficient; retain and rehearse a database restore. Keep workers and backend stopped during the final backup and migration. Preserve production secrets, signup domain policy, ports, uploads, and runtime configuration.
AI Disclosure
AI tool used: Codex.
Prompt / approach: Inspect live production, merge upstream in an isolated worktree, preserve custom behavior, verify migrations and tests, and deploy with backups and rollback evidence.