mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-03 19:20:07 +02:00
codex/surface-system
4022 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d8075a5775 |
fix(agents): tighten skills-tab intro and drop redundant import hint (#3265)
Three small UI cleanups on the agent Skills tab: - The blue "Importing creates a workspace copy that your team can edit and reuse" callout was visual clutter — drop it (and the Info icon import that it relied on). - The intro paragraph conflated two things: the workspace-skills concept (applies to every runtime) and the Allow-locally-installed-skills toggle (only honoured by Claude Code and Codex; verified — none of copilot/cursor/gemini/opencode/openclaw/hermes/pi/kimi/kiro read agent.SkillsLocal). Rewrite the intro to only describe the main concept; the toggle's own local_hint_on/off strings still carry the Claude/Codex caveat where it belongs. - The trimmed intro now fits one line, so flip the header row from items-start to items-center so the text sits on the same baseline as the "Add skill" button instead of clinging to its top edge. |
||
|
|
6bc3d14eb3 |
fix(daemon/execenv): refresh stale Codex config copies across env reuse (MUL-2646) (#3268)
* fix(daemon/execenv): refresh stale Codex config copies across env reuse (MUL-2646)
`copyFileIfExists` previously short-circuited whenever the per-task
`codex-home/{config.toml,config.json,instructions.md}` already existed,
so once the files were seeded at first Prepare they were never refreshed
again — even though `Reuse()` calls `prepareCodexHomeWithOpts` on every
resume. A user who rotated their Codex `~/.codex/config.toml` between
runs (e.g. switching the active `[model_providers.X]` `base_url`, or
pointing `env_key` at a freshly rotated API key) kept reading the stale
per-task copy on session resume. Codex then issued requests to the new
URL using the old key and the API rejected the token.
Treat any existing `dst` as something to drop and re-copy from the
current shared source, mirroring the symlink path that already refreshes
`auth.json` (#2126). The daemon-managed sandbox / multi-agent / memory
blocks are applied via marker-bracketed idempotent passes after the
copy, so a re-copy + re-ensure cycle preserves them.
Co-authored-by: multica-agent <github@multica.ai>
* fix(daemon/execenv): drop per-task Codex copy when shared source removed (MUL-2646)
Extend the MUL-2646 fix to the deletion arm of "sync the shared source":
`syncCopiedFile` (renamed from `copyFileIfExists`) now also removes the
per-task `dst` when the shared `src` is absent. The prior version
short-circuited on missing src and left `config.toml` / `config.json` /
`instructions.md` from the previous Prepare lingering in the per-task
home — so a user who removed a provider by deleting `~/.codex/config.toml`,
or pulled `config.json` / `instructions.md` out of the shared home, would
keep replaying the stale copy on session resume.
For `config.toml` the subsequent `ensureCodex{Sandbox,MultiAgent,Memory}Config`
passes recreate the file with only the daemon-managed default blocks, so
removing the shared file cleanly drops every user-managed
`[model_providers.X]` / `model_provider` line. For `config.json` and
`instructions.md` there is no daemon default, so they disappear in
lockstep with the shared source.
Adds `TestPrepareCodexHome_DropsCopiedConfigWhenSharedSourceRemoved`
covering the new path, and extends the refresh-arm test to assert the
multi-agent / memory marker blocks are still present after the copy is
refreshed.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
612ac8f28e |
feat(issues): server-side sort + fix drag position corruption (#3228)
* refactor(editor): split rich text styles * feat(issues): server-side sort + fix drag position corruption in non-manual sort Backend: ListIssues and ListGroupedIssues now accept `sort` and `direction` query params (position/priority/title/created_at/start_date/due_date). ListIssues converted from sqlc to hand-written SQL for dynamic ORDER BY. Priority sort uses CASE expression for semantic ordering. Frontend: query keys include sort so changing sort triggers server refetch. Client-side sortIssues() removed from board-view and list-view. Drag-and-drop: non-manual sort disables within-column reorder (prevents silent position corruption). Cross-column drag only updates status/assignee, preserves original position. Column overlay shows current sort during drag. Cache: query key split into prefix (list) for invalidation and full key (listSorted) for queryOptions. All optimistic update paths use prefix matching via getQueriesData to work with any active sort. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(board): prevent drag flicker by settling columns until mutation refetch After drag-and-drop, the optimistic cache patch updates position values without reordering the bucket array. The useEffect that rebuilds columns from TQ data would overwrite the correct local drag order, causing cards to snap back then forward. Fix: isSettlingRef blocks column rebuilds between drag end and mutation onSettled. Also invalidate issueKeys.list on WS position changes so other windows refetch correctly sorted data instead of showing stale bucket order. Includes debug logs (to be removed after verification). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(board): stabilize drag-and-drop for non-manual sort modes Three behavioral fixes for board drag when sort != position: 1. Settling: isSettlingRef + settleVersion blocks column rebuilds between drag-end and mutation settle, preventing the optimistic cache patch (which updates position values without reordering the bucket array) from overwriting the correct local column state. 2. Non-manual cross-column: handleDragOver returns prev (no visual card movement — column highlight + sort label is sufficient). handleDragEnd uses overCol directly instead of findColumn on the card's current position (which would be the source column). Cards use useSortable({ disabled: { droppable: true } }) to suppress within-column insertion indicators. 3. Collision detection: when no card droppables exist (disabled in non-manual sort), return column droppables from pointerWithin instead of falling through to closestCenter, so isOver reflects the column the pointer is actually inside. Also: WS position changes now invalidate issueKeys.list so other windows refetch correctly sorted data. Insertion-position prediction intentionally omitted — PostgreSQL's en_US.utf8 collation (glibc) cannot be faithfully replicated in JavaScript (ICU/V8), and an inaccurate indicator is worse than none. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sort): manual sort ignores direction param on both ends Manual sort (position) is user-defined order via drag-and-drop — reversing it has no product meaning. Backend: sort=position now skips the direction query param and always uses ASC. Both ListIssues and ListGroupedIssues handlers. Frontend: sort object omits sort_direction when sortBy is position. Direction toggle hidden in the display popover for manual mode. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * perf(board): memo columns + stabilize references to reduce re-renders - BoardColumn, PaginatedBoardColumn, PaginatedAssigneeBoardColumn wrapped in memo() — only columns with changed props re-render - IssueAgentActivityIndicator wrapped in memo() — 111 snapshot subscribers no longer trigger full re-render on every WS task event - buildColumns rewritten from O(groups × issues) to single-pass O(n) - EMPTY_IDS constant replaces ?? [] fallbacks (stable reference) - EMPTY_CHILD_PROGRESS constant replaces new Map() default - BOARD_COL_WIDTH / BOARD_CARD_WIDTH constants shared between column and DragOverlay for consistent card dimensions - issueListOptions + issueAssigneeGroupsOptions use placeholderData: keepPreviousData so sort/filter changes don't flash a full-page skeleton - Loading skeleton scoped to content area only — header stays rendered during data transitions Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: remove outdated server-side sort implementation plan Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
40da88fc16 |
fix(agent): mirror $HOME/.claude.json into isolated config dir (MUL-2661) (#3261)
PR #3200 introduced per-agent `skills_local=ignore` isolation that mirrors the host's Claude config dir into a per-task scratch dir, omitting `skills/` to keep broken local skills out of the CLI's discovery path. The mirror walks entries inside `hostConfigDir` (default: `$HOME/.claude/`), but Claude Code's default layout stores its main config — login state, project history — at `$HOME/.claude.json`, a *sibling* of `~/.claude/` rather than inside it. Once `CLAUDE_CONFIG_DIR=$ISOLATED` is set, the CLI looks for `$ISOLATED/.claude.json`, finds only `backups/.claude.json.backup.*` (those live inside `~/.claude/` and DO get mirrored), and exits with: Claude configuration file not found at: …/.claude.json Not logged in · Please run /login — so every agent with `skills_local=ignore` on a host using the default Claude layout dies on the first turn. Flipping the toggle back to "merge" restores the host CLAUDE_CONFIG_DIR and recovers the agent; that's the workaround Bohan flagged in MUL-2661. Fix: after the existing `mirrorHostClaudeExceptSkills`, run a new `mirrorHostClaudeJSONIfMissing` that pulls `$HOME/.claude.json` into the scratch dir as `.claude.json` when (a) the dest doesn't already have one and (b) the host source dir is the default `$HOME/.claude/`. The custom-CLAUDE_CONFIG_DIR path is left alone because a pinned custom dir is expected to be self-contained — silently borrowing `$HOME/.claude.json` from a different account would mask credential drift. The helper goes through `createFileLink`, so it inherits the same symlink → junction → hardlink → copy fallback chain the rest of the mirror uses on Windows-without-Developer-Mode hosts. Tests: - `TestMirrorHostClaudeJSONIfMissing_DefaultLayoutMirrorsParentFile` covers the happy path with an injected `homeDir`/`fileLink`. - `TestMirrorHostClaudeJSONIfMissing_AlreadyPresentNoop` asserts a pre-existing dest `.claude.json` (from a custom CLAUDE_CONFIG_DIR mirror) is not overwritten. - `TestMirrorHostClaudeJSONIfMissing_CustomHostDirSkipped` locks in the custom-host-dir gate. - `TestMirrorHostClaudeJSONIfMissing_MissingSourceNoop` documents the env-var-auth-only / fresh-install case. - `TestClaudeExecuteIsolatesProvidesClaudeJSONFromHome` is the end-to-end MUL-2661 regression: a fake `\$HOME` with the default split layout, `skills_local=ignore`, fake claude binary that prints whatever `.claude.json` reaches the scratch dir. Asserts the file rides through. Verified the test fails (with the documented MUL-2661 error message) when the new mirror call is removed. Verification: - `go test ./pkg/agent/...` green (full agent suite). - `GOOS=windows GOARCH=amd64 go vet ./pkg/agent/...` clean. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
960befa56f |
feat(agent): per-agent toggle to isolate host-machine skills (MUL-2603) (#3200)
* feat(agent): per-agent toggle to isolate host-machine skills (MUL-2603)
Adds an agent-scoped `skills_local` switch ("ignore" default / "merge") so
shared agents stop inheriting the operator's user-global Claude skill
directory. A single broken local skill on one operator's machine was
crashing the Claude CLI before it ever read stdin — the daemon saw a
"broken pipe" with no recoverable signal (GitHub #3052).
- DB: migration 108 adds `agent.skills_local` (NOT NULL DEFAULT 'ignore'),
with sqlc CreateAgent/UpdateAgent updates and handler validation.
- Claude runtime: when the agent is in "ignore" mode the backend points
CLAUDE_CONFIG_DIR at an empty per-task scratch dir under the task cwd
(fallback: OS temp), strips any inherited override, and cleans up after
the run. Workspace skills under `{cwd}/.claude/skills/` still load.
"merge" preserves the legacy inherit-from-machine behavior; Codex and
other isolated backends are no-ops.
- UI: new Skills toggle in the Create Agent dialog and the Agent → Skills
tab, with EN/zh-Hans copy and SkillsLocalToggle shared between the two.
- Tests: unit coverage for the new env helper, isolation dir lifecycle,
full Claude execute paths (ignore + merge), and the handler tristate
contract. Existing skills-tab test updated for the new copy.
- Docs: updated `/skills` docs (EN + ZH) and added a 0.3.7 changelog entry
in the landing-page i18n.
Co-authored-by: multica-agent <github@multica.ai>
* fix(agent): preserve claude login + validate skills_local input (MUL-2603)
Address Elon's review on PR #3200:
1. Skill isolation no longer drops the operator's Claude login. The
per-task scratch dir now mirrors every entry under `~/.claude/`
as symlinks except `skills/`, so `.credentials.json`, settings,
plugins, etc. reach the CLI exactly as on the host while the
user-global skills directory stays hidden. Without this, default
`ignore` would have broken every Claude agent on a non-API-key
host the moment migration 108 landed.
2. Internal CreateAgent callers (agent_template, onboarding_shim)
now set `SkillsLocal: "ignore"`. The Go zero value was about to
trip the migration-108 CHECK constraint and 500 template /
onboarding agent creation.
3. Create / update handler validation no longer normalizes garbage
to "ignore". The strict 400 path is now reachable on bad client
input; the drift-safe `normalizeSkillsLocal` stays on the read
side only.
UI copy + docs clarified that the toggle is Claude-only; other
runtimes ignore the setting.
Verification:
- `go test ./...` green (full suite locally).
- `pnpm --filter @multica/views exec vitest run agents/components/tabs/skills-tab.test.tsx` green.
- Handler DB-backed tests still skip locally without docker (same
as Elon's run) — CI will validate the create / update paths
against migration 108.
Co-authored-by: multica-agent <github@multica.ai>
* fix(agent): mirror effective claude config dir with windows fallback (MUL-2603)
Address Elon's second-round review on PR #3200:
1. The per-task scratch dir now mirrors the *effective* host Claude
config dir, not unconditionally `~/.claude/`. Precedence: agent
`custom_env` CLAUDE_CONFIG_DIR > parent process env > `~/.claude/`.
Without this, an operator who pinned Claude at a managed install
(custom env CLAUDE_CONFIG_DIR) would get the wrong credentials in
the scratch dir, because `buildClaudeEnv` strips that env before
handing it to the child. We resolve the source up front and feed
it to the mirror, so the override env still points at the right
bytes.
2. Mirror entries now go through platform-aware linkers. On Windows
without Developer Mode / admin, `os.Symlink` is denied, which
previously left the scratch dir empty and broke Claude Code auth
on default `ignore`. The new helpers try symlink first, then fall
back to a directory junction (`mklink /J`) for dirs or a hardlink
(same-volume content share) / copy for files. Mirrors the
execenv/codex_home_link_windows.go pattern.
3. Tests:
- `TestResolveHostClaudeConfigDir` locks in the custom_env >
parent_env > `~/.claude` precedence.
- `TestNewIsolatedClaudeConfigDirMirrorsCustomHostDir` confirms
the scratch dir picks up `.credentials.json` from a synthetic
custom host dir, proving the source resolution actually
propagates into the mirror.
- `TestNewIsolatedClaudeConfigDirEmptyHostIsNoop` documents the
env-var-auth-only case (no host source ⇒ empty scratch dir).
- `TestMirrorHostClaudeExceptSkillsWith_FallbackWhenSymlinkFails`
exercises the Windows-no-Developer-Mode path via the new
`mirrorHostClaudeExceptSkillsWith` seam, asserting credentials
and sub-dir children still reach the scratch dir after the
symlink stand-in fails.
- `TestMirrorHostClaudeExceptSkillsWith_PropagatesFirstLinkError`
confirms callers see the per-entry error when even fallback
fails (so the warn-log fires on broken Windows installs).
- `TestCopyFileRoundTrip` covers the last-resort copy fallback
and its EXCL no-overwrite contract.
- `TestClaudeExecuteIsolatesUsesCustomEnvSource` is the
end-to-end check: an agent with custom_env CLAUDE_CONFIG_DIR
reads its credentials from the pinned dir, not `~/.claude/`.
4. Docs: `apps/docs/content/docs/skills.{mdx,zh.mdx}` updated to
describe the effective-source resolution and the Windows
fallback chain so the docs match the runtime behaviour.
Verification:
- `go test ./...` green (full server suite locally, including
`pkg/agent` 23 cases covering the new + existing isolation
paths).
- `GOOS=windows GOARCH=amd64 go vet ./pkg/agent/...` and
`go test -c -o /dev/null` both compile clean, confirming the
Windows-tagged linker file builds.
Co-authored-by: multica-agent <github@multica.ai>
* fix(agent): default skills_local to merge to preserve legacy behavior (MUL-2603)
Per Bohan's product decision on PR #3200, the per-agent host-skill toggle
defaults to "merge" — the pre-MUL-2603 inherit-from-machine behavior —
so existing personal workflows that rely on locally installed Claude
Skills keep working unchanged. Agent owners explicitly opt into "ignore"
when they need to harden a shared agent against a broken local skill on
one operator's machine (GitHub #3052).
Also audited all 11 runtimes for user-global skill discovery paths and
documented the scope of the toggle. Only Claude reads a user-global
`~/.claude/skills/`; Codex isolates via `CODEX_HOME`, the ACP backends
(Hermes / Kimi / Kiro) and the JSON-stream backends (Copilot / Cursor /
Gemini / Pi / OpenCode / OpenClaw) anchor discovery to the task workdir
and never read a user-global skill directory. UI copy and docs now say
"for runtimes that support it (currently Claude Code)" everywhere so
the scope is explicit.
Changes:
- Migration 108: column default flipped to 'merge'.
- Handler CreateAgent: missing field → "merge"; explicit "ignore" /
"merge" still validated, garbage still 400.
- normalizeSkillsLocal: drift-safe coercion now lands on "merge" for
anything that isn't the exact literal "ignore".
- agent_template.go / onboarding_shim.go: internal CreateAgent callers
send "merge" instead of "ignore" to match the new default.
- Claude runtime (`claude.go`): isolate-mode gate flipped from
`SkillsLocal != "merge"` to `SkillsLocal == "ignore"`, so "" (legacy
daemons / older clients) and "merge" both walk `~/.claude/` directly.
- Create Agent dialog + Skills tab: toggle defaults to on (merge); only
duplicate of an explicit "ignore" agent carries through. The
isolation opt-in is now `skills_local: "ignore"` when the user flips
off; "merge" is omitted from the request body.
- i18n (EN + zh-Hans): copy reframed — "On (default) — merged"; "Off —
ignored. Recommended for shared agents".
- Docs (`/skills`, `/guides/agents.zh`): describe new default and
enumerate which runtimes act on the toggle.
- Landing changelog 0.3.7: retitled "Per-Agent Local-Skill Toggle"; note
the on-by-default behavior + off-to-isolate framing.
- Tests:
- `TestClaudeExecuteIsolatesHostSkillsWhenIgnoreOptedIn` replaces the
old by-default isolation case (now requires explicit "ignore").
- New `TestClaudeExecuteDefaultModeKeepsHostConfigDir` locks in that
default ExecOptions preserve the host CLAUDE_CONFIG_DIR.
- `TestClaudeExecuteIsolatesUsesCustomEnvSource` now explicitly opts
into "ignore" mode.
- Handler tests: omitted → "merge"; explicit "ignore" round-trips;
preserve-existing test seeds "ignore" and asserts "merge" flip-back.
- `TestNormalizeSkillsLocal_DriftStaysSafe`: only literal "ignore"
maps to ignore; everything else → "merge".
- `skills-tab.test.tsx`: toggle ON by default; flip OFF when agent
opted into "ignore". Intro-text matcher anchored to a more specific
phrase so it no longer collides with the toggle hint copy.
Verification:
- `go test ./...` green (full server suite locally).
- `GOOS=windows GOARCH=amd64 go vet ./pkg/agent/...` and
`go test -c -o /dev/null` both compile clean (windows-tagged linker
file still builds).
- `pnpm typecheck` green across all packages and apps.
- `pnpm --filter @multica/views test` 88 files / 771 tests green.
- `pnpm --filter @multica/core test` 43 files / 390 tests green.
- Handler DB-backed tests still skip locally without docker; CI will
validate the create / update paths against migration 108.
Co-authored-by: multica-agent <github@multica.ai>
* chore(landing): drop 0.3.7 changelog entry from this PR (MUL-2603)
The landing-page release notes belong in a separate release-prep PR, not in the feature PR.
Co-authored-by: multica-agent <github@multica.ai>
* fix(agent): propagate skills_local=ignore to codex user-skill seed (MUL-2603)
Make the per-agent skills_local toggle real for Codex too, not just Claude.
Previously the toggle was only consumed by the Claude backend, while the
daemon's execenv layer always seeded Codex's per-task CODEX_HOME with the
host machine's user-installed skills from ~/.codex/skills/. A shared Codex
agent with skills_local=ignore could still inherit a broken local skill
from one operator's machine.
Now: PrepareParams/ReuseParams carry SkillsLocal; hydrateCodexSkills
skips seedUserCodexSkills when SkillsLocal == "ignore" so the per-task
CODEX_HOME exposes only workspace skills to the codex CLI. Default
("merge", or empty from older servers/clients) preserves existing
inherit-from-machine behavior. UI / docs are updated to reflect the
contract honestly: Claude Code and Codex honor the toggle; other
runtimes (Hermes / Kimi / Kiro / Copilot / Cursor / Gemini / Pi /
OpenCode / OpenClaw) leave $HOME untouched and discover user-level
skills natively, so the toggle is a no-op for them today.
New tests: TestPrepareCodexSkillsLocalIgnoreSkipsUserSeed,
TestPrepareCodexSkillsLocalMergeSeedsUserSkills, and
TestReuseCodexSkillsLocalIgnoreSkipsUserSeed cover Prepare(ignore),
Prepare(merge), and the toggle-flip-on-reuse path.
Co-authored-by: multica-agent <github@multica.ai>
* docs(skills): scope skills_local toggle copy to Claude Code + Codex (MUL-2603)
Off-state hint and Skills tab intro now explicitly call out Claude Code +
Codex as the only runtimes that honor the toggle, with "other runtimes
ignore this setting" wired into both states (en + zh-Hans), so users on
non-Claude/Codex agents don't read "Off" as runtime-wide isolation.
Docs (skills.mdx, skills.zh.mdx, guides/agents.zh.mdx) stop describing
Hermes / Kimi / Gemini / Copilot / Cursor / Pi / OpenCode / OpenClaw / Kiro
as having native user-level skill discovery; the daemon simply does not
manage user-level skill discovery for those runtimes today, and the toggle
is a no-op regardless of where it is set.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
312ee29cb6 |
fix(views): always show assignee meta row on board cards when assignee property is enabled (#3250)
When no assignee was set, the entire meta row (assignee + dates + child progress) could disappear because showAssignee required both storeProperties.assignee AND issue.assignee_id. Now the row visibility depends only on storeProperties.assignee, and unassigned cards show "Unassigned" text with a clickable picker to assign. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
ec1589f7b6 |
fix(deps): add eslint phantom dep detection + fix existing violations (MUL-2654) (#3249)
* fix(deps): add eslint phantom dep detection + fix existing violations (MUL-2654) Introduce eslint-plugin-import-x/no-extraneous-dependencies rule to prevent phantom deps from causing production build splits when pnpm creates peer-dep variants. Fix all existing phantom deps across the monorepo, unify catalog references, and enable desktop smoke CI on PRs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * revert(ci): remove desktop smoke PR trigger per user feedback The existing smoke workflow only verifies packaging completes — it does not actually start the app or check rendering. This means it wouldn't have caught the white-screen bug (which was a runtime issue, not a build failure). Adding it to PRs would slow CI without providing meaningful protection. The ESLint no-extraneous-dependencies rule is the actual prevention mechanism. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(deps): sync pnpm-lock.yaml for rehype-sanitize dep classification Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(ui): move rehype-sanitize to deps + declare eslint-config (MUL-2654) - Move rehype-sanitize from devDependencies to dependencies (used in production Markdown.tsx) - Add @multica/eslint-config to devDependencies (imported by eslint.config.mjs but previously undeclared) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
e33b893c3f |
feat(views): sticky group headers in list view (#3221)
* feat(views): add sticky positioning to list-view group headers Group headers now stay pinned at the top of the scroll viewport so users always know which status group they are looking at. Background changed from semi-transparent to opaque to prevent content bleeding through. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(views): remove top padding from list-view scroll container for sticky headers The `p-2` padding on the scroll container caused an 8px gap above sticky group headers. Replace with `px-2 pb-2` to keep horizontal and bottom padding while allowing headers to stick flush to the top. Sync skeleton containers in issues-page and my-issues-page to match. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(views): use p-2 pt-0 instead of px-2 pb-2 for list-view scroll container Reporter preferred adding pt-0 to override the top padding from p-2, keeping the original p-2 shorthand intact. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(views): opaque sticky header hover + cursor-pointer on trigger Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
c5b4c45e41 |
fix(chat): unify expand and drag-to-max rendering (MUL-2653) (#3235)
* fix(chat): unify expand and drag-to-max rendering so both produce same dimensions (MUL-2653) Expand button used CSS `inset-3` (parent minus 24px each side) while drag-to-max used explicit 90%-of-parent pixel dimensions — different sizes for the same conceptual state. Expand also hid resize handles, preventing drag-back. Now both paths render with explicit width/height at bottom-right and resize handles stay visible in all states. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(chat): animate width/height via framer-motion for smooth expand toggle (MUL-2653) Move width/height from style prop into animate prop so framer-motion interpolates size changes. Remove layout="position" which only tracked position. Drag uses duration:0 for instant feedback. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
e351f89843 |
fix(desktop): declare phantom deps + dedupe react-query in renderer (MUL-2651) (#3232)
Packaged renderer was bundling two copies of @tanstack/react-query because apps/desktop imported it without declaring the dep, so Node resolution fell through to the hoisted root variant (react@19.2.0, pulled in by apps/mobile), while packages/core resolved to the catalog variant (react@19.2.3). Two physical paths → two QueryClientContexts → "No QueryClient set" white screen on launch. - Declare @tanstack/react-query, lucide-react, zustand as direct deps via catalog: so apps/desktop resolves to the same peer variant as packages/core/views. - Add @tanstack/react-query to renderer dedupe as a defense-in-depth bound against future peer drift. Verified: realpaths under apps/desktop, packages/core, packages/views all point to @tanstack+react-query@5.96.2_react@19.2.3; production renderer bundle now contains exactly one "use QueryClientProvider to set one" string (was 2) and no useQueryClient\$1 suffix. Co-authored-by: multica-agent <github@multica.ai>v0.3.8 |
||
|
|
bf1e375015 |
fix(docker): copy source.config.ts into web deps stage (MUL-2649) (#3227)
apps/web postinstall runs fumadocs-mdx, which reads apps/web/source.config.ts. The deps stage only copied package.json files, so `pnpm install --frozen-lockfile` failed with "Could not resolve /app/apps/web/source.config.ts" and blocked the GHCR multica-web image build in the v0.3.7 release. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
636fa1adb4 |
fix(issues): hide activity-block header while only recent entries are shown (#3226)
In the trailing activity block's default truncated state ("last 8 shown,
N older hidden"), we were rendering two stacked chevron rows: a "v N
activities" collapse header and a "> Show N more activities" reveal link.
Visually that looked like nested folds even though they're parallel
controls, and the header is redundant when the user just wants a glance
at recent activity.
Drop the header in the truncated default state. It reappears the moment
the user clicks "Show N more" — at that point they're seeing the full
block and a fold-back affordance becomes useful again. Blocks that fit
within the 8-entry limit (and non-trailing blocks, which never truncate)
keep their header as before.
|
||
|
|
441fa18db4 |
feat(issues): truncate trailing activity block to most recent 8 (MUL-2628) (#3219)
* feat(issues): truncate trailing activity block to most recent 6 (MUL-2628) The trailing activity block defaults to expanded, but a block with dozens of entries still drowns the comment area. Show only the most recent 6 by default; older entries fold behind an in-place "Show N more activities" toggle. Non-trailing blocks are unchanged — they still collapse whole. The "show older" choice is tracked per block id in a separate Set so it survives the block losing its trailing position (when a new comment lands after it) and survives a collapse/re-expand cycle. Co-authored-by: multica-agent <github@multica.ai> * refactor(issues): bump trailing activity block visible limit from 6 to 8 (MUL-2628) User feedback on the original PR: 6 felt slightly too tight. Bumped the trailing-block truncation threshold to 8 entries to give the "most recent activity" view a bit more headroom before older entries fold behind the "Show N more activities" toggle. Test count is unchanged; the existing trailing-block / non-trailing-block truncation cases were adjusted to exercise the new 8-entry boundary (10-entry trailing block → 2 hidden; 8-entry trailing block → none hidden; 10-entry non-trailing block → all visible after expand). Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
13f74e651a |
feat(agents): remove custom_env from agent resources, add audited env endpoint (MUL-2600) (#3209)
* feat(agents): remove custom_env from agent resources, add audited env endpoint (MUL-2600)
The agent resource shape (list / get / create / update / archive /
restore responses + WebSocket events) no longer carries `custom_env`
values. Reads/writes of env now flow exclusively through a dedicated
`/api/agents/{id}/env` endpoint that is owner/admin-only, rejects
agent-actor sessions, applies a "****" sentinel preserve guard on
PUT, and writes a persistent audit row per reveal/update.
Why
- `multica agent list --output json` historically returned plaintext
`custom_env` for owner/admin callers (the redaction gate gave only
members the masked map). Any agent token running on the workspace
inherits its owner's role and could read every other agent's
secrets just by listing.
- Patching list/get redaction alone (PR #3175 direction) left
symmetric leaks via mutation responses, WS events, the "reveal"
path itself (no actor-aware auth), and a `****` overwrite footgun
on UpdateAgent.
What changed
- Backend: drop `custom_env` from AgentResponse; add coarse
`has_custom_env` + `custom_env_key_count`. Strip env handling from
UpdateAgent (silently ignored if sent). Keep CreateAgent's
custom_env acceptance.
- Backend: new GET/PUT `/api/agents/{id}/env` handlers in
`internal/handler/agent_env.go`:
- resolveActor → 403 for agent actors (closes the lateral-movement
path).
- Owner/admin role gate via existing helper.
- PUT honours value == "****" as "preserve existing value".
- Both write to `activity_log` with `agent_env_revealed` /
`agent_env_updated` actions. Audit details record key names only,
never values.
- Daemon claim path (`ClaimAgentTask`) unchanged — `TaskAgentData`
still carries plaintext env for runtime injection.
- SQL: new `UpdateAgentCustomEnv` query; sqlc regenerated (v1.31.1).
- CLI: new `multica agent env get|set` subcommands. `--custom-env*`
flags removed from `multica agent update`; the no-fields error
now points to the new path.
- Frontend: drop env fields from `Agent` + `UpdateAgentRequest`; add
`getAgentEnv` / `updateAgentEnv` client methods; rewrite env-tab
to show "N variables configured" + explicit "Reveal & edit"
button, fetching values only on intentional reveal.
- Locales: parity-safe additions to en + zh-Hans.
- Docs: agents-create.{mdx,zh.mdx} reflect the new threat model and
endpoint.
- Mobile: schema drops `custom_env` / `custom_env_redacted`, adds
metadata fields.
Tests
- Handler tests pinned the new invariants: no env in list/get
responses, owner reveal happy-path + audit row, agent-actor 403,
`****` sentinel preserves real values, UpdateAgent silently
ignores `custom_env`, pure `mergeAgentEnv` cases.
- CLI tests pivot to the new flag surface: `agent update` MUST NOT
expose the env flags; `agent env set` MUST expose
--custom-env-stdin/--custom-env-file.
- Frontend test fixtures updated; pnpm typecheck / test / lint
pass cleanly.
This is a breaking API change. Scripts that read `custom_env` from
`/api/agents` must migrate to `GET /api/agents/{id}/env`.
Co-authored-by: multica-agent <github@multica.ai>
* fix(agents): close actor-spoofing + audit fail-closed in env endpoints (MUL-2600)
Addresses Elon's review of #3209:
* Mint a task-scoped `mat_` token per claim, bound to (agent, task,
workspace, owner). Daemon injects it into the agent process in place
of its own credential. Auth middleware authoritatively rebuilds
X-User-ID / X-Agent-ID / X-Task-ID from the token row and sets
X-Actor-Source=task_token; that header is server-set only — incoming
values are stripped before any auth branch runs. resolveActor honors
the header so an agent that strips X-Agent-ID / X-Task-ID still
resolves as actor=agent.
* GetAgentEnv / UpdateAgentEnv are now fail-closed on audit-log
failures: GET refuses to return plaintext, PUT persists inside the
same tx as the audit row so they commit/roll back together.
* PUT /api/agents/{id} returns 400 when the body carries custom_env
instead of silently dropping it — directs callers to the audited env
endpoint.
* Agent actors never see mcp_config, even when the underlying member
is owner/admin; mutation broadcasts go through a redaction shim so
WS subscribers don't pick it up either.
* Fix backend test that asserted dense JSON (jsonb::text renders
whitespace) and frontend test that assumed a unique "Test User"
match.
Co-authored-by: multica-agent <github@multica.ai>
* fix(agents): close residual MUL-2600 gaps from review (MUL-2600)
Migration 108 FK now correctly references agent_task_queue(id) instead
of the non-existent agent_task table; the previous name blocked CI
backend migrations.
Task-token-authenticated requests can no longer be re-routed at a
different workspace by passing workspace_slug / workspace_id /
?workspace_id / a URL workspace param. ResolveWorkspaceIDFromRequest
and resolveWorkspaceUUID both short-circuit on X-Actor-Source=task_token
and return only the token-bound X-Workspace-ID; buildMiddleware adds a
defence-in-depth 403 if any URL-resolved workspace disagrees with the
token binding.
mcp_config no longer leaks back to agent actors through UpdateAgent /
CreateAgent / ArchiveAgent / RestoreAgent HTTP responses — the same
redactAgentResponseForActor helper that GetAgent/ListAgents use is now
applied to mutation responses too. WS broadcasts were already redacted
via broadcastAgentResponse.
FailTask and every TaskService cancel path (CancelTask /
CancelTasksForIssue / CancelTasksForAgent / CancelTasksByTriggerComment
/ BroadcastCancelledTasks) now eagerly DeleteTaskTokensByTask so the
mat_ token's 24h window doesn't outlive a terminated task. Failure is
non-fatal — the FK cascade and expiry remain durable guards.
Doc-only: clarify that PUT /api/agents/{id} now hard-rejects bodies
that carry custom_env (was previously "silently ignores").
Tests:
- middleware: TestResolveWorkspaceIDFromRequest gains a task_token
case asserting client-supplied slug/id/query cannot override the
bound workspace.
- handler: TestUpdateAgent_RedactsMcpConfigForAgentActor and
TestUpdateAgent_KeepsMcpConfigForMemberActor pin the mutation-
response redaction contract per actor type.
Co-authored-by: multica-agent <github@multica.ai>
* fix(agents): match redacted mcp_config as JSON null, not Go nil (MUL-2600)
`AgentResponse.McpConfig` is `json.RawMessage` without `omitempty`, so
the redacted response serialises as `"mcp_config": null`. On decode,
`json.RawMessage` keeps the literal bytes `null` rather than collapsing
to Go nil, which made the assertion fire on a non-leak.
The product contract (field always present, distinguished from "no
config" via `mcp_config_redacted`) is intentional, so adjust the test
to check for "no secret-bearing content" instead of weakening the
contract via `omitempty`.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
5877cff9de |
docs: add 2026-05-25 changelog entry (#3218)
* docs: add 2026-05-25 changelog entry Co-authored-by: multica-agent <github@multica.ai> * docs: clarify iOS changelog availability Co-authored-by: multica-agent <github@multica.ai> * docs: remove reverted squad fix from changelog Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
2b3e408db1 |
Revert "fix(squad): skip leader on agent reply to explicit member @-mention (MUL-2624) (#3217)" (#3222)
This reverts commit
|
||
|
|
f9dfb3b9fc | Fix duplicate desktop back navigation (#3210) | ||
|
|
ce98b1c9ef |
fix(squad): skip leader on agent reply to explicit member @-mention (MUL-2624) (#3217)
When a user explicitly @-mentions an agent on an issue assigned to a squad, the existing rule already suppresses the squad leader on the mention comment itself — the user is routing deliberately, the mentioned agent owns the next step. The leader was still woken on the agent's reply, though, so it would re-@ the user every time the agent answered. Extend the suppression to the second leg of that explicit exchange: when an agent reply lands as a child of a member comment that carried a routing @mention (agent/member/squad/all — issue cross-refs still ignored), the leader stays out. The CreateComment handler already pins agent parent_id == task.TriggerCommentID, so this fires exactly when the agent's reply is provably tied to the upstream routing comment. Top-level agent comments and agent-to-agent threads continue to wake the leader so coordination keeps working everywhere else. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
b9bf2653be |
fix(desktop): retry tab scroll restore until virtualized content rehydrates (#3214)
Follow-up to #3196. Switching tabs and back on a long issue still landed at scrollTop=0 because issue-detail uses Virtuoso with customScrollParent — Virtuoso wires its scroll/resize observers in a passive useEffect, which fires *after* useLayoutEffect. So at the moment the restore hook ran, the spacer that gives the scroll container its tall scrollHeight hadn't been re-established yet (scrollHeight === clientHeight), and the browser silently clamped `scrollTop = saved` down to 0. Diagnostic console output confirmed this: marker key=true saved=10356.5 currentScrollTop=0 scrollHeight=750 clientHeight=750 → set scrollTop to 10356.5 actually now 0 Fix: keep the synchronous set as the fast path, then if the assignment was clamped, retry across rAF frames for up to ~500ms (30 frames at 60fps). That gives Virtuoso's passive effect time to re-establish the spacer, after which the next tick succeeds. Cancel any in-flight retry when the effect tears down (Activity hidden again or component unmount). Existing 4 tests in use-tab-scroll-restore.test.tsx still pass — the synchronous fast path covers the simple-content case they exercise. A jsdom regression for the Virtuoso scenario didn't reproduce reliably (the clamp + rAF interplay needs a real browser), so this relies on manual verification: open issue-detail, scroll deep into comments, switch tabs, switch back — scroll position now holds. |
||
|
|
5c1fad4508 | refactor(editor): split rich text styles (#3211) | ||
|
|
90455abd8d |
fix(desktop): preserve tab scroll position across Activity visibility cycles (MUL-2602) (#3196)
Closes #3183. Tabs render under `<Activity mode="visible|hidden">`, which keeps React state but drops DOM scrollTop when the subtree leaves layout. Switching to another tab and back sent users to the top of long discussions. `useTabScrollRestore` records the scrollTop of every element marked with `data-tab-scroll-root` while the tab is visible (capture-phase scroll listener) and restores them in a useLayoutEffect on the next visible transition, before paint. Saved offsets are dropped when the tab's path changes so intra-tab navigation lands at scroll=0 instead of inheriting the previous route's position. Mark scroll containers in views with `data-tab-scroll-root` (issue detail + chat message list ship with the marker; other views can adopt the convention as needed). `useAutoScroll` previously called `scrollToBottom()` on every effect mount, which would have overwritten the restored offset every time a chat tab cycled back to visible. Guard it with a once-per-instance ref. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
9d5c023145 | fix(markdown): disable code ligatures (#3038) | ||
|
|
cfc652aa5f |
fix(daemon): close stdin pipe in Pi adapter to deliver EOF (#2188) (#3118)
Pi reads its prompt from argv (positional, see buildPiArgs) and never expects interactive input, so the Pi backend previously left cmd.Stdin nil. Under systemd, the resulting /dev/null character device has been observed not to satisfy Pi's readable-side wait, leaving runs stuck in "working" forever (#2188). Attach an explicit StdinPipe and close it immediately after Start so the child sees an EOF on a FIFO, matching the pattern already used by the Claude, Codex, Hermes, Kiro, and Kimi backends. The fix is defensive on the daemon side because Pi is mid-refactor and is not accepting issues upstream; once Pi itself stops blocking on stdin, this close is still correct (a closed pipe is a no-op for a process that does not read it). Test asserts the structural invariant: a shell-stub `pi` inspects /proc/self/fd/0 and only emits a valid event stream when stdin is a FIFO. If a future change drops the StdinPipe and stdin reverts to /dev/null (char device), the stub exits non-zero and the test fails. |
||
|
|
1c5e483b1c |
feat(pricing): add DeepSeek, Kimi K2.6, and Zhipu GLM cost tracking (MUL-2606) (#3204)
Adds rows to MODEL_PRICING for the Chinese-model SKUs listed on each provider's official pricing page, so opencode / OpenRouter-routed runtimes stop showing $0.00 in the dashboard for these models. Sources (now cited inline above the table): - DeepSeek: https://api-docs.deepseek.com/quick_start/pricing - Moonshot: https://www.kimi.com/resources/kimi-k2-6-pricing - Zhipu z.ai: https://docs.z.ai/guides/overview/pricing Notes vs the closed PR #3170: - Only SKUs that exist on the official pages are added. glm-z1*, deepseek-v4-pro at $0.55/$2.19, kimi-k2.6 at K2's tier were all hallucinated and are NOT included. - deepseek-chat / deepseek-reasoner are routed by DeepSeek to deepseek-v4-flash, so they share the v4-flash rate. - deepseek-v4-pro is priced at the post-promo standard rate ($1.74 / $3.48), not the 75%-off promo that ends 2026-05-31. Brief over-estimate beats a sudden 4x jump on June 1. - glm-*-flash are priced at $0 because z.ai's free tiers are the literal published price. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
cd71b0fe05 |
fix(daemon): disable Codex native auto-memory in per-task config.toml (#3202)
Codex CLI's auto-memory subsystem writes summaries to `$CODEX_HOME/memories/raw_memories.md` and `state_*.sqlite`, then reads them back on the next turn. The daemon never cleared these files across Reuse(), and Codex CLI may also pull from user-level `~/.codex/memories/` entirely outside the per-task isolation. Either path leaks unrelated context into new Multica tasks — multica#3130 saw `D:\Project\MoHaYu\ WowChat` Raw Memories injected into a brand-new issue's first turn. Write a daemon-managed block into the per-task `config.toml` that sets `features.memories = false`, `memories.generate_memories = false`, and `memories.use_memories = false`. Codex then neither writes nor reads its memory subsystem regardless of where the residual files live. The user's global `~/.codex/config.toml` is never touched. Pattern mirrors `ensureCodexMultiAgentConfig`: idempotent managed-block upsert, two TOML layout variants (root dotted-key vs. inside a `[features]` / `[memories]` table) to satisfy strict toml-rs parsing, and a `MULTICA_CODEX_MEMORY` env-var escape hatch. MUL-2598 Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
8e9df90d32 |
feat: include repo description in agent brief (#3203)
Add Description field to RepoData structs so that workspace repo descriptions (set via the settings UI) are preserved through normalization and rendered in the agent brief as: - <url> — <description> When no description is set, the existing format is unchanged. Closes MUL-2610 Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
6703072241 |
Improve landing header CTA hierarchy (#3197)
* Improve landing header CTA hierarchy * Remove duplicate landing login action |
||
|
|
a8cda1bd96 |
feat: add description field to workspace repository settings (#3198)
- Add optional description field to WorkspaceRepo type - Show description input below URL in edit mode - Display description text in view mode - Update isDirty to compare descriptions - Update tests for new field Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
be54e79f38 |
fix: upgrade github.com/jackc/pgx/v5 to 5.9.2 (#3192)
Remediates two pgx security advisories in a single bump: - CVE-2026-33816 (fixed in 5.9.0) — pgproto3 memory-safety DoS from malformed messages sent by a malicious server. - GHSA-j88v-2chj-qfwx / CVE-2026-41889 (fixed in 5.9.2) — SQL injection via placeholder confusion with dollar-quoted literals under QueryExecModeSimpleProtocol. Not reachable in this codebase (no simple-protocol callers), but pinned to clear future scanner runs. No source changes needed: pgx 5.9.x adds no breaking APIs over 5.8.x (adds PG protocol 3.2 support, SCRAM-SHA-256-PLUS, OAuth, plus pgtype/pgconn bug fixes). Minimum Go bumped to 1.25 in 5.9.0; repo already on 1.26.1. MUL-2597 Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
6261ea45fd | Improve board and squad hover cards (#3188) | ||
|
|
077bc055f7 |
fix(views): sort timeline entries by created_at on WebSocket append (MUL-2582) (#3139)
* fix: sort timeline entries by created_at on WebSocket append When multiple agents post comments concurrently, WebSocket events may arrive out of chronological order. The handlers blindly appended new entries to the end of the cached timeline array, causing display misordering. This fix sorts the array by created_at (with id as tie-breaker) after each insert. Changes: - use-issue-timeline.ts: sort after comment:created and activity:created - issue-ws-updaters.ts: sort in appendTimelineEntry Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(views): extract sortTimelineEntriesAsc helper, cover mutation onSuccess Review feedback from @Bohan-J: useCreateComment.onSuccess also appends unsorted (mutations.ts:558). When the local user posts a comment whose HTTP response returns after a concurrent WS event, the unsorted append leaves the cache misordered and the subsequent WS dedup skips re-sort. Extract sortTimelineEntriesAsc helper and reuse it in all three web cache writers: - comment:created WS handler - activity:created WS handler - useCreateComment.onSuccess Mobile keeps its own inline sort (apps/mobile/CLAUDE.md boundary). Add regression tests for sort position (mid-insert and oldest-insert). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
3df26ddd28 |
feat(self-host): add Helm chart for Kubernetes deployment (#2377)
* Include k8s deployment instructions * Use helm for deployment * docs(self-host): add Helm / Kubernetes deployment to quickstart (en + zh) * fix(helm): gate backend ExternalName alias behind a value The unprefixed Service/backend in the chart is load-bearing, but as written it limits the chart to one release per namespace and fails helm install whenever a Service/backend already exists in the namespace (without --take-ownership). Gate the alias behind frontend.compatibility.backendAlias (default true, so existing installs are unchanged). Operators running a web image with a patched REMOTE_API_URL can set it to false to drop the Service entirely. Document the one-release-per-namespace constraint and the opt-out in values.yaml and the SELF_HOSTING.md Kubernetes section. Addresses review item #1 on PR #2377. * fix(helm): add backend startupProbe so cold installs survive migrations The entrypoint runs `./migrate up` before serving traffic. On a cold cluster (Postgres still coming up) this can take minutes, during which the livenessProbe (initialDelaySeconds 30 / periodSeconds 30) trips and restarts the pod 1-2 times. Add a startupProbe on /healthz (failureThreshold 30, periodSeconds 10, ~5 min budget). Kubernetes disables liveness/readiness until it passes, so migrations finish without the pod being killed, and the aggressive livenessProbe is untouched for steady-state. Update the SELF_HOSTING.md install step, which no longer expects 1-2 restarts. Addresses review item #2 on PR #2377. * fix(helm): roll backend pods on config/secret change via checksum annotations envFrom does not watch the referenced ConfigMap/Secret, and helm upgrade alone does not change the pod template hash, so editing values.yaml + `helm upgrade` left the old backend pods running stale config. Add checksum/config (hash of the rendered configmap.yaml) and checksum/secret (hash of the live existingSecret via lookup, since it is created out-of-band and has no chart template) to the backend pod template. Config edits now actually re-roll the backend on upgrade, and Secret rotations do too. lookup is empty under `helm template`/`--dry-run`; that placeholder is harmless and documented inline. Addresses review item #3 on PR #2377. * docs(self-host): sync quickstart with new startupProbe behavior SELF_HOSTING.md was updated to reflect that the backend now stays Running but not Ready while Postgres comes up (startupProbe absorbs it, so no restart), but the EN/ZH quickstart docs still described the pre-startupProbe behavior of "may restart 1-2 times". Bring them in line. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan Jiang <52446949+Bohan-J@users.noreply.github.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
f59c34eea8 |
docs: clarify Chinese README license label (#3189)
The LICENSE file adds commercial restrictions on top of Apache 2.0, so the README should not advertise the project as plain "Apache 2.0". Match the actual terms. Closes #3144 Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
5f1f08e466 |
feat(web): add use-cases content pipeline with welcome page (MUL-2349) (#2795)
* feat(web): add use-cases content pipeline with welcome page (MUL-2349) Wire fumadocs-mdx into apps/web with an independent collection rooted at content/use-cases/. Add the first page at /use-cases/welcome (header + H1 + prose + screenshot + footer) using the about-page visual shell. - source.config.ts + lib/use-cases-source.ts (separate from apps/docs) - features/landing/components/mdx/screenshot.tsx wraps next/image - public/use-cases/welcome/screenshot-1.png placeholder (55KB) - next.config.ts wraps NextConfig with createMDX() - .gitignore + eslint ignore .source/ Co-authored-by: multica-agent <github@multica.ai> * feat(web): bilingual db-boy use case with cookie locale (MUL-2349) Extends the use-cases pipeline into the first real article. - ZH + EN MDX (auto-data-analysis.{zh,en}.mdx) sharing three real screenshots; sensitive fields on db-boy-profile.png (RDS host, DB name, password) are blurred in-place. - Cookie-based locale: /use-cases/<slug> reads multica-locale server-side via lib/use-cases-i18n.ts (mirrors LandingLayout's cookie + Accept-Language fallback). Same URL serves either language; no [lang] segment so all other landing routes stay unchanged. - Frontmatter schema (source.config.ts): z.looseObject with declared hero_image / updated_at (required) / category (optional); a preprocess converts YAML-auto-parsed Date back to a YYYY-MM-DD string. - MDX components factory createMdxComponents(locale) routes the secondary CTA to /docs/zh (ZH) or /docs (EN); internal MDX links use <Link> for SPA nav; full-width and half-width colons both trigger [CTA: ...] / [占位图: ...] markers; 副 and Secondary both work as the secondary CTA prefix. - Index page localizes hero / subtitle / card CTA / metadata; sort fallback uses an epoch placeholder so undefined-order disappears. - Landing header + footer surface use-cases entry in both locales. - Detail route: sticky header, right-rail TOC with anchor jumps, scroll-mt-[100px] on H2/H3 so anchor jumps don't slip under the sticky header. - Drop welcome demo page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(web): resolve code review blockers on use-cases PR - Add `use-cases` to reserved_slugs.json + regenerate TS (P1: prevent future workspace slug collision) - Fix dead links in both MDX files: /features/* → /docs/* (P2) - Remove duplicate brand suffix in page title metadata (nit) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> * fix(web): align usecases locale routing * chore: refresh web mdx lockfile * fix(web): type mdx next config adapter * fix(web): wrap settings route page --------- Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f0c32d5728 |
fix(views): move member count from header badge to section label in squad popover (MUL-2586) (#3178)
Remove the standalone member-count badge from the squad profile card header and display the count inline with the Members section label (Members · N). Add max-height + scroll guard on the member list to prevent card overflow with many members. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
44ee74eb25 |
feat(views): add squad popover hover card to ActorAvatar (#3176)
Squad avatars now show a hover card on dwell, matching the existing agent and member cards. The card displays the squad name, member count badge, description (line-clamp 2), and a members list (top 3, leader first) with agent status dots. Clicking an avatar navigates to the squad detail page. Closes MUL-2586. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
993cf550ad |
docs(readme): add Autopilots to features list (#3155)
Autopilots are a shipped product feature with full UI and backend support, but were missing from the README features list. Add a bullet in both EN and zh-CN versions, placed next to Autonomous Execution since both cover how work gets triggered and run. |
||
|
|
ba945c1141 |
fix(runtimes): price claude-opus-4-7[1m] at standard Opus tier (MUL-2584) (#3152)
Claude Code reports the 1M-context Opus beta as `claude-opus-4-7[1m]`. The pricing resolver had no tolerance for the bracketed context tag, so the row missed the maintained catalog and its tokens were silently excluded from cost totals. Add a `[...]` context-tag strip alongside the existing provider / dot↔dash / date-snapshot normalizations. The 1M variant is priced at the standard $5/$25 Opus rate; aggregated daily totals don't carry per-request prompt sizes, so the >200K 2× surcharge can't be applied precisely. Mild under-estimate beats the previous $0. Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
3e1066a638 |
feat(ui): add repository search to resource pickers (#3126)
* feat(ui): make project resource list scrollable * feat(ui): add repository search to resource pickers |
||
|
|
bfb7c85491 |
fix(selfhost): derive local port URLs from env (MUL-2506) (#2939)
* fix(selfhost): derive local port URLs from env * fix(selfhost): derive local script URLs |
||
|
|
660e27b981 |
fix(runtimes): extend self-healing delete guard to list row menu (MUL-2569) (#3081)
Follow-up to #3076. The detail-page guard left a bypass via the runtimes list row menu — owners could still walk Runtimes → kebab → Delete → toast → runtime reappears. Extract isSelfHealingRuntime into the shared utils module so detail and list agree on the predicate, and drop the kebab entirely for self-healing rows (the menu's only item was Delete). Also swap the lingering English "daemon" in the zh-Hans delete_disabled_tooltip for 守护进程 to match the rest of the file. Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
fd0fe1d08a |
feat(mobile): Multica for iOS — first version (#2337)
* docs(mobile): establish independence rules and tech-stack baseline - Refactor root CLAUDE.md sharing rules into a single Sharing Principles section, replacing scattered mentions across 10 places with one source of truth + minimal "(web + desktop)" qualifiers on existing sections - Add apps/mobile/CLAUDE.md with locked tech-stack baseline: Expo SDK 54, React Native 0.81, NativeWind 4 + Tailwind 3.4, react-native-reusables, TanStack Query 5, Zustand, expo-secure-store - Mobile pins React directly (does NOT track root catalog:) so the Expo SDK / RN release schedule isn't blocked by web/desktop upgrades - Visual tokens are mobile-owned (transcribed from packages/ui/styles/ tokens.css by hand, not imported); Tailwind v3.4 vs v4 mismatch makes file sharing impractical anyway - Document mobile build/release pipeline (main CI excludes mobile, separate mobile-verify and mobile-release workflows, EAS Update for OTA) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(mobile): v1 shell — auth, workspace switching, inbox + my-issues - Auth: email OTP login mirroring packages/core/auth/store.ts behavior (401 clears token, non-401 preserves; token written only on verify success); expo-secure-store with key "multica_token" matching desktop - Workspace context: /[workspace]/ URL slug as source of truth (deep- link friendly), ApiClient auto-injects X-Workspace-Slug, SecureStore persists last-selected slug for cold-start restore - Bottom tabs (Ionicons): Inbox / My Issues / Settings - Inbox: actor avatar, unread brand-dot, status icon, time-ago + body subtitle. getInboxDisplayTitle mirrored from packages/views/inbox/ components/inbox-display.ts - My Issues: priority bars (matching IssuePriority bar counts from packages/core/issues/config/priority.ts), status dot, identifier, title, assignee avatar - Settings: account info + workspace switcher; switching replaces nav to /[newSlug]/inbox so back stack doesn't trail to old workspace - Multi-env: .env.staging / .env.production / .env.development.local with EXPO_PUBLIC_API_URL; APP_ENV in app.config.ts swaps bundleIdentifier so dev/staging/prod coexist on a device - Build: dev:mobile + dev:mobile:staging scripts; main turbo build/typecheck/lint/test filter excludes @multica/mobile Tech-stack (locked in apps/mobile/CLAUDE.md): - Expo SDK 55, RN 0.83.6, React 19.2.0 (pinned, NOT catalog) - NativeWind 4 + Tailwind 3.4 (intentional mismatch w/ web's Tailwind 4; visual tokens transcribed by hand from packages/ui/styles/tokens.css) - TanStack Query 5 with AppState focus listener; Zustand 5 Not in this commit (intentional): issue detail page, mark-read mutation, pull-to-refresh polish — next iteration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(mobile): unignore data/ + dedup, layout, mark-read, SVG icons, issue page Critical: previous commit ( |
||
|
|
c280fc0879 |
fix(editor): sync TitleEditor when defaultValue changes externally (MUL-2565) (#3080)
* fix(editor): sync TitleEditor when defaultValue changes externally (MUL-2565) Tiptap's useEditor consumes `content` only at mount, so a WS-driven title update left the editor showing the old text. Worse, the next blur ran onBlur's value-vs-issue.title compare with stale editor bytes and silently mutated the title back, rolling the external change. Add a useEffect that calls editor.commands.setContent when defaultValue diverges and the editor is unfocused (preserve in-flight user typing). Pass emitUpdate:false to avoid an onUpdate echo loop. Co-authored-by: multica-agent <github@multica.ai> * fix(editor): refine TitleEditor focus guard to focused+dirty only (MUL-2565) Reviewer flagged that the previous "focused → skip" guard was too coarse: a user who clicked into the title field but had not yet typed would leave the editor doc stale when an external title update arrived, and the next blur would compare the stale text to the new server value and silently roll the external update back. Track the previous defaultValue in a ref and only skip when the editor is both focused AND its current text diverges from that previous value (meaning the user has actually typed). Focused-but-clean updates fall through and accept the new external value. Adds a regression test covering the focused-but-clean external update case. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
0339599ff6 |
docs(changelog): add 2026-05-22 release notes (#3082)
* docs(changelog): add 2026-05-22 release notes Co-authored-by: multica-agent <github@multica.ai> * docs: tighten zh changelog copy Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai>v0.3.6 |
||
|
|
a55c03a0b3 |
fix(agent): inject Workspace Context into agent brief (MUL-2542) (#3078)
* fix(agent): inject Workspace Context into agent brief (MUL-2542) The per-workspace `workspace.context` field (Settings → General) was stored in the DB but never reached the agent prompt. Plumb it from the workspace row through the claim response, the daemon's Task struct and TaskContextForEnv, and render it as `## Workspace Context` in the meta brief above `## Available Commands`. Heading is skipped when the field is empty so workspaces that haven't set a context don't see a bare header. Applies to every task kind — issue, comment, chat, autopilot, quick-create — so the shared system prompt is consistent regardless of trigger source. Co-authored-by: multica-agent <github@multica.ai> * chore(server): gofmt files touched by workspace-context injection Run gofmt on the files that buildWorkspaceContext injection touched. Cleans up composite-literal alignment in execenv task context and struct-tag alignment in Task / AgentTaskResponse / RegisterRequest. No behavior change. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: J <agent-j@multica.ai> |
||
|
|
ed8f43867c |
fix(runtimes): guard delete for self-healing local runtimes (#3076)
Deleting an online local runtime has no lasting effect — a live daemon re-registers itself within seconds (#2404). Disable the delete button for online local runtimes and explain why in a hover tooltip. Also drop the redundant topbar delete button (the Diagnostics card already owns the delete action), and navigate back to the runtimes list after a successful delete instead of leaving a stale detail page. |
||
|
|
d6fdd8d74e |
feat(onboarding): upgrade welcome_page card to slides + add Helper Stay-current rule (#3073)
- Card 3 (welcome_page): swap "HTML welcome page" for a single-file HTML slide deck. Prompt inlines frontend-slides constraints (viewport 100vh, clamp typography, density caps, anti-AI-slop aesthetic, CSS-only staggered load-in). Cards 1 (intro) and 2 (tour) unchanged. - Helper instruction: add a "Stay current" section telling the agent to surface contradictions between this instruction and CLI/docs/repo, propose an updated instruction, and wait for user confirmation before applying via CLI — never self-update silently. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f2e6dc75bd |
feat(create-issue): collapse start date into ⋯ overflow menu (#3063)
Start date is a low-frequency field for most issues, so the always-on inline pill was crowding the property toolbar. Move it behind the ⋯ overflow menu by default: the pill only appears once a value is set, or transiently while the calendar popover is open after the user picks "Set start date..." from the menu. Closing the popover without a value returns the pill to the menu-only state. To make the menu item open the popover programmatically, lift the picker's open state via new controlled `open` / `onOpenChange` props (matching the priority-picker pattern). MUL-2557 Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
0bb51ccd0e |
feat(issues): mention parent assignee in child-done system comment (MUL-2538) (#3065)
* feat(issues): mention parent assignee in child-done system comment (MUL-2538)
Per Bohan's product call on MUL-2538 ("方案 C"), the platform's child-done
system comment now @mentions the parent assignee — member, squad, or
agent — and the platform fires the matching side effect explicitly:
- agent → mention task via TaskService.EnqueueTaskForMention
- squad → leader task via TaskService.EnqueueTaskForSquadLeader
- member → 'mentioned' inbox row + EventInboxNew broadcast
The generic comment listener still short-circuits on author_type='system'
(see notification_listeners.go) so smuggled mention links in the child
title can never light up unrelated members; the parent assignee mention
is the only side effect, and it is fired from the handler with explicit
guards rather than the listener path.
Guards retained / added:
- Comment-fire gates from prior PR unchanged (status transition, parent
state, no parent).
- Loop guard: skip trigger when child and parent share the same assignee
(same agent / same squad / same member). The comment + mention still
render so the timeline tells the full story; the second task does not
fire.
- Idempotency: HasPendingTaskForIssueAndAgent dedupes rapid-fire enqueues
for the same parent (back-to-back child completions).
- Readiness: archived agents / missing runtimes are silently skipped.
Tests:
- TestChildDoneMentionsParentAssignee_{Agent,Member,Squad} verify the
mention link + the matching trigger / inbox row.
- TestChildDoneSelfTriggerGuard_SameAgent asserts that an agent assigned
to both the child and the parent gets the comment + mention but no
second task — the documented loop break.
- TestChildDoneNotifiesParent updated: when the parent has no assignee
(its existing fixture), no routing mention should appear; the assigned
branches are exercised by the new cases above.
Co-authored-by: multica-agent <github@multica.ai>
* feat(issues): skip child-done parent notification for human assignees (MUL-2538)
Humans read their own timeline manually — an automated system comment
is pure noise for member-assigned parents, and there is no agent task
to trigger. Skipping the notification entirely also removes the mention
question (no comment → no mention → no inbox row).
The agent / squad / unassigned branches stay unchanged.
Co-authored-by: multica-agent <github@multica.ai>
* fix(issues): close cross-squad shared-leader loop in child-done dispatch (MUL-2538)
Elon's review of PR #3065 flagged that triggerChildDoneAgent and
triggerChildDoneSquad only compared the child's direct assignee, so a
child-done event could still wake the same agent when:
- parent assigned to agent A, child assigned to a squad whose leader is A;
- parent and child assigned to two different squads sharing the same
leader agent.
Replace the per-side checks with a single effectiveChildAgentOwner helper
that reduces the child to "the agent that would actually act on it" (the
agent assignee, or the squad's leader) and lets both trigger paths compare
apples to apples. Add coverage for both newly-blocked cases, and tighten
the documented side-effect semantics (squad triggers leader only — no
member fan-out; notification_preference is not consulted, downstream
agent_task / inbox pipeline still respects mutes).
Also fix the member-skip test fixture to write user_id, matching the
production invariant that issue.assignee_id for assignee_type='member'
references user_id (validateAssigneePair, server/internal/handler/issue.go).
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>
|
||
|
|
5bc77f2953 | fix(pi): strip leaked tool markup safely (#2956) |