mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-09 01:46:46 +08:00
zai-beta
336
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6db1921748 |
docs: publish open-connector services from the admin console
Replace the feature flag and OPEN_CONNECTOR_API_KEY_SERVICES steps with the /admin/connectors workflow, and have the opt-in e2e publish github as a platform admin before the user flow. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01G4Tynn8YQnR5koJTSvFC62 |
||
|
|
c479d44c49 | Merge remote-tracking branch 'origin/claude/project-thread-qefpx3' into claude/project-thread-s663w0 | ||
|
|
2ce16d8842 |
Merge origin/main into claude/project-thread-qefpx3
Resolves the UpdateAgent conflict in handler/agent.go by keeping both the connector_service_allowlist write and main's runtime-binding transaction, renumbers the open-connector migrations to 590-596 because main took 583/584, and regenerates sqlc. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01XqqpKySn2iWtXriK3Gsgak |
||
|
|
7733c92ed5 |
Merge PR #8 (open-connector) into PR #7 (admin console) base
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01G4Tynn8YQnR5koJTSvFC62 |
||
|
|
0b74a732f5 |
docs: self-hosted open-connector setup guide and e2e connect flow
Add the open-connector setup page (en, zh) covering the compose stack, Multica OPEN_CONNECTOR_* variables, OAuth client registration, reverse proxy rules and known limits, plus an opt-in Playwright spec that drives the OAuth connect flow against an in-process mock open-connector. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01XqqpKySn2iWtXriK3Gsgak |
||
|
|
caf2cfa5dd |
feat(server): platform admin API with audit log and DB-backed disabled users
Adds the backend for the operations console: platform_admin roles (super_admin / operator / viewer), an admin_audit_log table, and user.disabled_at so disabling an account is a stored state instead of a hard-coded denylist. The in-process disabled set is now refreshed from the database every 30 seconds, and the old emergency denylist entries are migrated into it. /api/admin exposes overview counts, user and workspace listings with detail, disable/enable, token revocation, the audit log, and platform role management. Every write requires a reason and is recorded with the actor, IP and user agent. PLATFORM_ADMIN_EMAILS bootstraps the first super_admin. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01FrVjAFWeckYV69HS51xUhy |
||
|
|
d908e4882d |
MUL-6710: fix(runtimes): revoke access when making runtime private (#7580)
* fix(runtimes): revoke access when making runtime private * fix(runtimes): regenerate queries for workflow step --------- Co-authored-by: m2keloop <[email protected]> |
||
|
|
8d95e80559 |
fix(github): fetch PR snapshots without Contents permission (#9332)
Co-authored-by: multica-agent <[email protected]> |
||
|
|
abef85c3c9 |
MUL-8086: docs: explain workflows and quick actions (#9331)
* docs: explain workflows and quick actions (MUL-8086) Co-authored-by: multica-agent <[email protected]> * docs: clarify workflow defaults, board handoffs, and quick action limits (MUL-8086) Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
6dc20dd314 |
feat(llm): use GPT-6 Luna as the built-in assist default (#9329)
Co-authored-by: multica-agent <[email protected]> |
||
|
|
9a50d0cc65 |
fix(docs): stop default-locale pages failing hydration with React #418 (MUL-8013) (#9244)
* fix(docs): stop default-locale pages failing hydration with React #418 SSG prerenders each page under its canonical `[lang]` route, so client components see `/en/agents` as the pathname at build time, while the browser URL for the hidden default locale is `/agents` (the i18n middleware rewrites it). Fumadocs resolves the page tree path with `usePathname()`, and the tree stores default-locale URLs without the prefix — so the prerendered HTML rendered tree-path fallbacks ("On this page" in the TOC popover trigger) while hydration resolved the real page name ("Agents"). The text mismatch threw React error #418 on every EN page load and React regenerated the whole tree on the client, dropping SSR-injected elements such as the next/font preload links. Non-default locales matched on both sides and were unaffected. Wrap the docs layout in fumadocs' `FrameworkProvider` with a `usePathname` that strips the hidden default-locale prefix, so prerender and hydration see the same locale-less path the page tree uses. The prerendered HTML now carries the real page names as well. Non-default locales keep their `/zh`-style prefix and behave exactly as before. Fixes #9243 * fix(docs): preserve static image imports in framework provider Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
88f6d6272b |
MUL-8057: feat(agent): run Meta Muse Code as an MSP runtime (#9309)
* feat(agent): run Meta Muse Code as an MSP runtime Muse Code speaks MSP (Muse Session Protocol) over `muse serve`: JSON-RPC 2.0 on stdio, like ACP, but with its own method vocabulary. A session opens with session/start or session/resume, work is submitted with turn/start, and assistant text, reasoning and tool calls arrive as transcript items. The adapter maps those items onto the Backend contract, reads the terminal turn outcome, and reports a notFound resume as ResumeRejected so the daemon retries on a fresh session instead of losing continuity silently. Approval is a session property, not a launch flag: `muse serve` has no --yolo, and the CLI documents --yolo as TUI-only. A fresh session is created with approvalMode=allowAll and a resumed session re-asserts it with session/setApprovalMode, which is what lets a claimed task run without waiting for a human. The container and per-task worktree remain the isolation boundary, as they are for every other backend. commandId must be UUIDv7 — Muse derives a turn id from it and rejects anything else — so the adapter mints time-ordered ids rather than v4. The model catalog comes from the session-free model/list query and the pick is applied through session/start modelId. * feat(runtime-profiles): allow muse as a protocol family A custom runtime profile may only be based on a protocol family the daemon supports, and the database enforces the same list. Widening both keeps the create flow, the server validation and the CHECK constraint in lockstep with agent.SupportedTypes. * fix(muse): complete runtime integration and lifecycle handling (MUL-8057) Co-authored-by: multica-agent <[email protected]> * docs(muse): document runtime setup and support (MUL-8057) Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: yushen <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
6d5cdcbed2 |
MUL-7488: fix(execenv): exclude committed local HEAD changes from resume replay (#8549)
* fix(execenv): exclude clean HEAD advances from resume replay * fix(execenv): normalize carried WIP before replaying local edits Co-authored-by: multica-agent <[email protected]> * fix(execenv): resume after conflicting local draft commits Co-authored-by: multica-agent <[email protected]> * fix(execenv): limit snapshot conflict handling to affected paths Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
191c782d60 |
MUL-7988: recover daemon when profile health port is unavailable (#9217)
* fix(daemon): recover unavailable profile health ports Co-authored-by: multica-agent <[email protected]> * fix(daemon): wait for profile shutdown before restart Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Yushen <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
3d021489f4 |
MUL-7697: fix(daemon): keep host TMP/TEMP for every Windows task (#9197)
* fix(daemon): keep host TMP/TEMP for every Windows task Git Bash's /tmp usertemp mount is shared by all live MSYS processes of a user, whichever runtime started them. #8985 stopped Windows Claude tasks from seeding it with a task-private dir, but any other provider still could, and later tasks then lost /tmp once that dir was deleted. Apply the rule to every provider on Windows: TMP/TEMP inherit the host temp, TMPDIR stays task-private, and Claude keeps its CLAUDE_CODE_TMPDIR default. The Git Bash integration test now alternates providers. Closes #8824 Co-authored-by: multica-agent <[email protected]> * test(daemon): expect inherited TMP/TEMP on Windows in env test Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
eb59a69722 | fix(daemon): preserve host temp for Windows Claude Git Bash (#8985) | ||
|
|
92d0b6666d |
MUL-7812: fix(agent): deliver MCP servers to Pi tasks (#9063)
* fix(agent): deliver MCP servers to Pi tasks Pi skipped untrusted project config in JSON mode, and Multica never wrote .pi/mcp.json, so composed MCP servers never reached the runtime. Co-authored-by: Cursor <[email protected]> * fix(agent): hand Pi its MCP servers through a temporary extension Writing .pi/mcp.json and passing --approve trusted the whole project directory and failed when that file already existed. An explicit --extension loads the servers without project trust. Co-authored-by: Cursor <[email protected]> * docs: mark Pi as reading agent MCP configuration The provider comparison still said Pi skipped the MCP field. Pi now receives that configuration. Co-authored-by: Cursor <[email protected]> * fix(agent): harden Pi MCP registration Co-authored-by: multica-agent <[email protected]> * fix(agent): allow empty Pi MCP configuration Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Cursor <[email protected]> Co-authored-by: Yushen <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
e0f84dda47 |
fix(docs): use stable self-host upgrade references (MUL-7897) (#9091)
* fix(docs): cite the compose lines that pin MULTICA_IMAGE_TAG The self-host upgrade warning pointed at a volume mount and a GitHub key comment, so a reader checking the pin looked at the wrong lines. Co-authored-by: Cursor <[email protected]> * fix(docs): use stable self-host upgrade references Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Cursor <[email protected]> Co-authored-by: Bohan <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
bb14e8763c |
feat(issues): add multi_text and multi_url custom property types (MUL-7496) (#8559)
* feat(issues): add multi_text and multi_url custom property types Workspaces could only hold list-shaped values as preset option lists (multi_select) or member references (multi_actor). There was no way to keep a list of free-form strings or links on an issue, so teams with "related docs / spec links / aliases" needs had to either cram them into one text field (no structure, nothing clickable) or burn one select option per link. This adds two property types: - multi_text: an ordered list of free-form text entries - multi_url: an ordered list of http(s) URLs Each entry is validated exactly like a single text / url value (length caps, http(s)-only for URLs), duplicates are dropped, insertion order is preserved, and values are capped at 20 entries to respect the 16KB properties-bag budget. Values ride the existing jsonb bag, so the jsonb_path_ops GIN index, the @> containment filter, and the client value schema all keep working unchanged. Filtering matches any single list element exactly (equality) plus "No value" — the containment form the filter compiler already emits for array elements, so no new server predicate is needed. List types deliberately get no sort order and cannot group, like the other multi-value types. On the web, list values render as chips; multi_url chips are clickable and open in a new tab, and the editor popover offers a per-entry open button, so several related documents or links stay directly reachable from the issue. The filter menu reuses the scalar input (equality-only plus "No value"). CLI: comma-separated --value form for both types, element-equality --property filtering, and display_values rows. Server-side allowlist widened by migration 500 (check constraint re-added NOT VALID then validated, same pattern as migration 341). * fix(properties): address list property review nits Co-authored-by: multica-agent <[email protected]> * fix(properties): scope list drafts to the edited issue Co-authored-by: multica-agent <[email protected]> * test(agent): avoid fork races when writing zeroclaw fixtures Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
43b0571f99 |
MUL-7821: feat(cli): mark duplicates with issue status/update --duplicate-of (#8979)
* feat(cli): mark duplicates from issue status and issue update (MUL-7821) Add --duplicate-of to `multica issue status` and `multica issue update`. It resolves an issue key or UUID and sends duplicate_of_issue_id, which the server already accepts (MUL-7349). The CLI rejects a status other than cancelled, and description or attachment changes, before any request or upload. It fails when the response has no duplicate_of field, so a server older than v0.5.2 cannot silently drop the mark. Closes #8974 Co-authored-by: multica-agent <[email protected]> * docs: teach agents to mark duplicates with --duplicate-of (MUL-7821) Add the command to the agent runtime brief, the multica-platform issues reference, CLI_AND_DAEMON.md and the CLI docs tables, so agents record the mark instead of cancelling a duplicate and explaining it in a comment. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
104bf7be35 |
MUL-7732: regroup settings by scope and consolidate scattered pages (#8874)
* feat(mcp): report how many agents use each workspace MCP server The workspace MCP library listing now carries agent_count: the number of live (non-archived) agents each entry is assigned to. Settings shows it so an admin can tell whether replacing or removing a server affects anyone. The field is optional in the client schema, so an older server that omits it simply shows no count. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(settings): regroup settings by scope and consolidate scattered pages Implements the MUL-7732 settings redesign: - Navigation is grouped by who a setting affects: Personal, the current workspace (with Issues & workflow and Connections & extensions sub-groups), and This device on desktop. Pages and sections carry a scope badge (account, this device, workspace), and members see a lock on pages only owners and admins can change plus a shared read-only notice. - Settings search in the nav indexes page and row titles and descriptions; opening a result scrolls to and highlights the row. - Preferences is one page instead of three tabs, with a searchable time zone picker, a segmented theme control and a field grid for the create-issue dialogs. - Code merges Repositories, GitHub and self-hosted Git. Repositories are read-only rows edited through dialogs; self-hosted Git connects in a dialog that ends on the one-time webhook secret. - The PR merge rule moves to Statuses & transitions as an automatic transition; Code only shows its current value. Built-in statuses show edit and archive as unavailable instead of refusing after a click. - Integrations becomes Messaging (IM channels only) with a breadcrumb on detail pages; Composio moves to Personal as Connected apps. - Members gets tabs, search, invite and share-link dialogs and a seat summary. MCP servers show assignment counts and explain how MCP, connected apps and plugins differ. Workspace General shows members plain values and changes the issue prefix through an explicit dialog. - Skill labels are managed from the Skills page; Settings > Labels keeps the issue catalog. - Successful saves no longer toast; failures still do. Old ?tab= values (github, repositories, integrations, issue, chat, labs, and the Composio OAuth callback) resolve to the new pages, since server redirects still use them. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * docs: point settings paths at the regrouped settings pages GitHub, repositories and self-hosted Git now live under Settings > Code, IM bots under Settings > Messaging, and the PR merge rule under Settings > Statuses & transitions. The GitHub guide also drops the old master switch step in favor of pausing from the GitHub row. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(settings): edit the PR merge rule on the Code page The "After PRs merge, move the issue to" rule sits with the other pull request rules on Code again instead of a separate Automatic transitions section. It applies to every code host, so pausing GitHub features does not lock it. The statuses page goes back to Issue Statuses, and its merge badge links to the rule on Code. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(settings): manage skill labels under Settings → Labels again The Skills page entry was buried at the bottom of the filter menu's Labels submenu. Settings → Labels switches between the issue and skill catalogs again, using the members page's segmented switch (now SettingsViewTabs), and the Skills page keeps its shortcut. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(settings): keep GitHub resume reachable after a disconnect Pausing GitHub features is a workspace setting, but the pause and resume item lived in a menu that only rendered with an installation. A workspace paused before disconnecting, or with github_enabled=false and no installation, lost the way back, and every PR switch stayed disabled. On deployments without a GitHub App it could not even reconnect, though Co-authored-by works without one. The menu now shows whenever GitHub is connected or paused, and only Disconnect requires an installation. The paused note shows in either state. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(settings): let the URL choose the open members list The members page read `section` only to initialize its list tab, so a search result for invitations or share links opened while already on the page changed the URL but not the list, and back/forward did nothing. With a router the open list is now derived from the URL, and switching lists replaces `section`. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(settings): tell repositories on different ports apart The manual repository dialog reused the GitHub import's identity, which drops the port, so https://git.example.com:9443/acme/api.git read as a duplicate of the same path on :8443 and could not be saved. Non-default ports now stay in the identity; default ports, including 22 for ssh://, still compare equal to the scp-like form. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * test(comments): stop the recipient-menu tests flaking under CI load The steering tests opened a recipient chip menu while the composer could still re-render its chips, from the previous send settling or the preview catching up. That closed the menu, and the menu steps also used the 1s default wait inside the 5s default test timeout. Under parallel load locally, about 1 in 20 runs failed the same way as CI. Menu choices now go through a helper that reopens the menu until the item shows. The remaining waits get the 5s the first steps already had, and each test gets 15s. 140 of 140 loaded runs pass. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
815fe37b1f |
MUL-7685: fix(agents): paginate task history and aggregate duration (#8804)
* fix(agents): paginate task history and aggregate duration * test(agents): cover pagination boundaries and query lifecycle * fix(migrations): register agent history index retry cleanup * fix(agents): show totals only for complete task history * fix(agents): average duration over the full rolling window The server bounds duration totals by completed_at > now() - 30 days, which can span 31 calendar days. deriveAgentActivity summed duration only after trimming buckets to the chart's 30 local-day slots, so runs on the oldest partial day were dropped from the average. Sum duration for every returned bucket before slotting. Co-authored-by: multica-agent <[email protected]> * fix(migrations): renumber agent history index to 552 main now carries 551_pr_merge_status, and migration numbers from 129 onward must be unique (TestMigrationNumericPrefixesAreUnique). Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
9c6622787d |
MUL-7735: docs(readme): fix stale facts, sync the Chinese README, retake the hero screenshot (#8869)
* MUL-7735: docs(readme): fix stale facts and bring the Chinese README in line Facts that had drifted from the code: - Runtimes table was missing ZeroClaw (25 rows against the 26 CLIs in scripts/agent-cli-command-names.txt). - Architecture diagram routed Desktop and iOS through Next.js; only Web does. Desktop and iOS talk to the Go backend directly. - A single execution is a Run, not a Task (conventions glossary; the docs page is already titled Runs / 运行). - Mobile runs natively on iPad too; link the mobile-app docs page. - New Feishu/Lark connections are mainland-China Feishu only. - "Any Git host" overclaimed; it is GitHub, GitLab, Gitea, and Forgejo. Chinese README: channel list now matches English (adds WeCom and Telegram and the community-maintained note), docs links point at /docs/zh/ with Chinese heading anchors, adds the missing CLI-skill row and Star History badges, and uses the UI's terms (聊天, 自托管). Structure: the CLI count now appears once, in the Runtimes section, instead of four places per file; Get started and the five-minute walkthrough are merged; the self-host block states the anonymous telemetry default and the DO_NOT_TRACK opt-out; Stay in the loop gains steering a running agent. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * MUL-7735: docs(assets): retake the workspace overview screenshot The hero image predated the sidebar regrouping (Work / AI Team) and the Usage -> Analytics rename. Retaken from a seeded local environment at the same 1600x900 size and WebP q80 (85 KB -> 82 KB). The README and docs index pages share this file. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
692dd019ac |
MUL-7713: docs(troubleshooting): explain macOS TCC re-prompt after CLI upgrade (#8843)
* docs(troubleshooting): explain macOS TCC re-prompt after CLI upgrade The Homebrew CLI is ad-hoc signed and installed under a versioned Cellar path, so macOS drops the daemon's privacy grants on every upgrade and the next run that touches a protected folder blocks on an unanswered prompt. Document the symptom, the cause, and how to re-grant access (en/zh/ja/ko/fr). Part of #8720 * docs(troubleshooting): scope macOS TCC section to launchd daemons - Say the hang applies when the dialog names multica (launchd/LaunchAgent daemons), instead of stating it for every daemon. - List all upgrade paths: multica update and the Runtimes page Update button. - Restart the daemon the way it was started (launchctl kickstart for a LaunchAgent), and note auto-update is only on by default for Multica Cloud. - fr: use "le CLI" / "daemon" like the rest of the page; zh: link text matches the Desktop page title. Part of #8720 Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
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. |
||
|
|
51e95dca2d |
fix(issues): fit PR auto-complete copy on one line (MUL-7678) (#8797)
* fix(issues): shorten the no-close-keyword hint under the PR list (MUL-7678) "No PR says “Closes MUL-123”, so merging won’t complete this issue" wrapped to two lines in the default-width sidebar. Drop the trailing clause: the line sits in the auto-complete slot under the PR list, and "Closes" is the GitHub keyword developers already read as "completes on merge". The keyword and identifier, the part a reader acts on, stay. Same cut in all five locales. The docs already describe this line as "no PR says `Closes MUL-123`", so they need no change. Co-authored-by: multica-agent <[email protected]> * fix(issues): lead the no-close-keyword hint with the outcome (MUL-7678) Review of #8797: the line sits in the auto-complete status slot next to "Completes when #19 merges", and when it shows the PR is usually one click from merging. Dropping "merging won't complete" left the fix but not the warning. Put the outcome first and keep the keyword: Won't complete: no "Closes MUL-123" Measured with Inter at 12px in Chromium: 229px (235px for a five-digit number), one line in the 260px text column of the default 320px sidebar. "Won't complete on merge: ..." measured 285px and wrapped. zh, ja and ko use the same terse shape and also fit; fr takes two lines. Co-authored-by: multica-agent <[email protected]> * fix(issues): fit the PR auto-complete menu toggle on one line (MUL-7678) "Don't auto-complete this issue" measures 204px at 14px Inter, over the 198px text column of the w-60 menu, so it wrapped. Use "Turn off auto-complete" / "Turn on auto-complete" (154px / 152px): same verbs as the "Auto-complete is off for this issue · Turn on" status line, and the menu already belongs to this issue's Pull requests block. fr wrapped too and gets the same shape; zh, ja and ko already fit and stay. The docs path to the toggle is updated to the new label. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
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 <[email protected]> * 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 <[email protected]> * fix(web): drop the Slack users section from the licensing page (MUL-7558) Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
554937f36b |
docs(telegram): update French pages for photo, video and file support (#8790)
The French pages from #8757 were translated before #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. |
||
|
|
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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> --------- Co-authored-by: zhudejun1 <[email protected]> Co-authored-by: multica-agent <[email protected]> Co-authored-by: Multica Agent <[email protected]> |
||
|
|
d5a25d7438 |
MUL-7608 feat(mobile): add Simplified Chinese localization (#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 #8741 are localized too. |
||
|
|
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 <[email protected]> * 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 <[email protected]> * docs(self-host): document trusting a private CA (MUL-7639) Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> * 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 <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]> |
||
|
|
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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(pr): accept PR links pasted without a scheme (MUL-7429) Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * test(skills): pin the PR auto-complete contract in the platform skill (MUL-7429) Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
920c3ea6d9 |
feat(docs): add French documentation aligned with UI terminology (MUL-7637) (#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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
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 <[email protected]> |
||
|
|
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 <[email protected]> * 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 <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
5403083420 |
MUL-7607 fix(auth): restore invitation-based signup (#8706)
Co-authored-by: multica-agent <[email protected]> |
||
|
|
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 <[email protected]> * 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 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]> |
||
|
|
8c9f865e3e |
MUL-7480: Revert comment steering feature (#8648)
* Revert "MUL-7480: Link delivered steers to final replies (#8623)" This reverts commit |
||
|
|
3de6275eb3 |
MUL-7480: Link delivered steers to final replies (#8623)
* feat: link steer receipts to final replies Co-authored-by: multica-agent <[email protected]> * fix: keep steer final reply locators stable Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Forge-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
8ce795b007 |
MUL-7480: Steer active agent turns from comments (#8605)
* feat: steer active agent turns from comments (MUL-7480) Co-authored-by: multica-agent <[email protected]> * fix: make comment steering hint driven Co-authored-by: multica-agent <[email protected]> * fix: harden active comment steering races Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Forge-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
6b69b3710c |
Revert "MUL-7517: clarify squad leader identity framing (#8597)" (#8600)
This reverts commit
|
||
|
|
a51fd83072 |
MUL-7517: clarify squad leader identity framing (#8597)
* fix(squads): clarify leader identity framing (MUL-7517) Co-authored-by: multica-agent <[email protected]> * fix(squads): address briefing review feedback (MUL-7517) Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Forge-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
2e728fc6de |
MUL-7358 fix(agent): require OpenCode >= 1.1.54 so pre-fix builds cannot fill the host disk (#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. #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 #8392 Co-authored-by: multica-agent <[email protected]> * 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 <[email protected]> --------- Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
1fcdff78d0 |
MUL-7436 feat(auth): slide UI session expiry instead of forcing a re-login every 30 days (#8486)
* feat(auth): slide UI session expiry instead of fixing it at login A session token's exp was stamped once at login and never moved, so every continuously active user was logged out 30 days later, always mid-task. The session now re-issues itself once it drops below half its TTL, turning that scheduled logout into an idle bound. Renewal is stateless: it copies the claims of a token we signed and moves exp, so there is no session table and no DB read on the hot path. - Browsers are renewed inline by middleware.Auth on safe requests, keeping the session HttpOnly — no client change and no multi-tab coordination. - Clients holding the session as a string (Desktop, mobile) get POST /api/auth/refresh, which only ever accepts a UI JWT. PATs, agent task tokens and cloud-node PATs are refused: a machine credential must not become an interactive session. An expired JWT cannot be revived. - CSRF tokens are now bound to a new sid claim, stable across renewals, so re-issuing the auth cookie no longer invalidates a CSRF token another tab already read. The pre-existing binding stays valid so sessions that predate this keep working until they renew. - CloudFront cookies are re-signed alongside a renewal, so the CDN policy cannot outlive its session. The idle bound is one-sided and the comments say so: a session last used while it still had over half its TTL left is not extended, so the guaranteed survivable gap is TTL/2, not TTL. Refs MUL-7436 Co-authored-by: multica-agent <[email protected]> * feat(auth): renew the session on Desktop, mobile, and legacy token-mode web Completes the client half of sliding sessions. Browsers on cookie auth need nothing — the server re-issues their cookie inline — so this covers the clients that carry the session as a string and have to persist a new one. Renewal follows use, not a clock. Checks run at launch, on returning to the foreground, and on interaction; there is deliberately no interval timer, because a timer would keep an abandoned but still-open window (or a backgrounded phone) signed in forever. The server decides whether a session is renewable and tells the client when to ask again, derived from the deployment's configured TTL — so no client reads exp, hardcodes a cadence, or has to reason about clock skew. Renewal is not a session event, and each step protects that: - Concurrent triggers collapse into one request, so two valid tokens never race to be the one stored. On mobile the single-flight flag is set before the first await, since reading the Keychain is itself async. - A response that lands after logout or an account switch is discarded by comparing against the session the attempt started from; writing it back would resurrect a session the app has already torn down. - Offline, 5xx and an unreadable body all keep the current session, which is still valid for at least another half TTL. Only a real 401 ends a session, and that path is untouched — nothing here can clear tabs or drafts. - WebSocket clients now read the token at connect time instead of pinning the one captured when they were built, so a reconnect after a renewal authenticates with the current credential. Web also retries a rejected CSRF token once. Session-bound CSRF makes that unreachable in the steady state; it covers the single transition where a session predating that binding is renewed for the first time. Refs MUL-7436 Co-authored-by: multica-agent <[email protected]> * fix(auth): address review blockers on sliding session renewal Six fixes from review, each with a regression test that fails without it. An expired session was answered 403, not 401. The CSRF gate runs before the JWT is validated, and it read `sid` through the expiry-enforcing parser — so an expired cookie lost its sid, its perfectly good CSRF token failed to verify, and the user's first action after expiry was a rejected write instead of a return to the login page. Reading the session id and deciding access are now different questions with different parsers: the first only needs the signature, the second still enforces expiry. Rolling back was not survivable. A previous release can verify only the token-bound CSRF binding, so once a session was issued or renewed by this one, a rollback left every signed-in user able to read and unable to write, with no client retry able to help. Both bindings are now issued: clients echo both, the server accepts either, and the token-bound cookie is re-minted on every renewal so a rollback taken at any moment still finds a usable pair. Mobile could resurrect a session it had just ended. Keychain deletes are async, so a renewal that sampled storage after logout began but before the delete landed would write its replacement back. A session epoch, incremented synchronously at the start of logout, a 401 teardown and a new sign-in, closes the window comparing tokens cannot — including the case where logout lands during the renewal's own write, which is now undone rather than left behind. Mobile only checked at launch and on foreground, so an app held in the foreground and used continuously would never check again and could expire under the user's hands. Every touch now passes through a capture-phase responder that declines the gesture it observes. CloudFront cookies were re-signed by a middleware not every renewing route mounts — the plugin bridge renews without it, and would have slid the session forward while leaving the CDN policy behind. Re-signing moved next to the renewal itself, so the two cannot diverge by route. The renewal check interval could exceed the window it has to land in: a flat five-minute floor meant any TTL under ten minutes produced a cadence longer than the renewal window, logging out clients that followed it exactly. The cadence is now capped at half the threshold, which outranks both clamps. Refs MUL-7436 Co-authored-by: multica-agent <[email protected]> * fix(auth): close the CORS, credential-write and short-TTL gaps Three blockers from the second review round. The two CSRF values now travel in one header instead of two. A second header name would have to be in the server's CORS allowlist, and that list is version-specific: a browser holding a cookie this release issued, talking to a rolled-back server, would send a header that server never allowlisted. The preflight fails, the request never reaches a handler, and no retry helps — precisely the half-logged-in state the second cookie exists to prevent. With one header name the server just tries both bindings, and the client switches to the token-bound value when a server rejects the session-bound one. It switches back as soon as the cookie changes, keyed on the rejected value rather than a flag, so it cannot oscillate once per renewal. A test now pins the CSRF header to the allowlist so a future second header fails here rather than in production. Mobile credential writes are serialized, and a stale renewal is refused rather than undone. Compensating after the fact meant deleting whatever was stored — which, after a logout and a fresh sign-in, is the new account's token: the app would be logged in in memory and signed out on the next launch. The check and the write now happen inside one serialized unit, so a logout or a sign-in cannot land between them and a refusal writes nothing at all. AUTH_TOKEN_TTL has a documented one-minute minimum. Clients are told when to check again in whole seconds, so a shorter TTL asks the wire to carry a sub-second cadence: it truncates, and below about four seconds it serialises to 0. Values under the minimum are clamped up with a startup warning rather than honoured. Mobile's floors were never actually lowered in the previous commit — 1h/1m, not the 5s/5m claimed — so both clients now floor at 5s, below anything the server can send, and fall back to 30s, which fits the smallest supported window. The cadence tests assert the serialised integer a client actually receives, not the internal time.Duration. Refs MUL-7436 Co-authored-by: multica-agent <[email protected]> * fix(auth): keep a failed renewal cheap and a renewed token shared Two blockers from the full-PR review, plus the documentation the config change owes. A failed check no longer consumes the normal cadence. The two were one number, so a single 503 spent whatever the server had last suggested — with the 30-day default that is three days. A client told to check again in three days on day 12, returning on day 29 with one day of session left and hitting one 503, would not have been allowed to retry until day 32: two days after the session it was trying to save had expired. Failures now buy a short backing-off delay of their own, reset by the next success, so recovery follows the network rather than the cadence. The bearer token is read from shared storage per request instead of cached per client. Desktop runs one ApiClient per window over one localStorage, so a session renewed in one window left the others sending a credential on its way out until it expired and took the whole session down. A 401 is also now matched against the credential that produced it: a request that went out just before a renewal can be answered after it, and treating that as expiry would tear down a session that is demonstrably alive, clearing tabs and drafts with it. Documentation catches up with both the behaviour and the new minimum, in all four languages: sessions are an idle bound rather than a countdown from sign-in, the bound is one-sided, and AUTH_TOKEN_TTL clamps up at 60s. The rationale for that minimum is corrected too — it is the 5s floor every client applies to the derived cadence, not whole-second serialisation, which only binds far below it. Also removes two claims that were not true: that a failed renewal leaves at least half a TTL (it happens inside the window, so less), and that an open untouched window never renews (cookie-mode sessions slide on any authenticated request, background polling included). Refs MUL-7436 Co-authored-by: multica-agent <[email protected]> * fix(auth): back off on a renewal response that carries no cadence A 200 whose body fails schema validation does not throw — parseWithFallback returns zeroes — so both clients reset the failure count without moving the next-check deadline, and every subsequent interaction fired another request at a server already answering badly. A missing cadence is now treated as a failure and takes the same short backoff; a token that did arrive is still applied. Also settles the documentation wording: the 30 days is not an idle timer. Renewal only happens past the halfway point, so an unused session can end sooner than 30 days after its last use, and saying otherwise promised precision the design does not have. Refs MUL-7436 Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
91ad871864 |
MUL-6648: feat(cli): add issue comment update command (#7507)
* feat(cli): add issue comment update command * fix(cli): require revisions for comment updates |
||
|
|
3fe16b8fcb |
MUL-7422: paginate archived inbox and load it on demand (#8471)
* fix(inbox): paginate archived notifications on demand Co-authored-by: multica-agent <[email protected]> * fix(inbox): isolate archived deep-link lookup states Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Lambda <[email protected]> Co-authored-by: multica-agent <[email protected]> |