2631 Commits

Author SHA1 Message Date
LinYushen
141c294cdb P0: isolate Redis relay pools (#1701)
* Isolate Redis relay pools

* Fix Redis relay shutdown order
v0.2.17
2026-04-26 11:26:13 +08:00
Black
04f813a70f fix PR 1573 follow-up colors (#1699) 2026-04-26 11:14:40 +08:00
Bohan Jiang
c7a2d53f76 docs(changelog): publish v0.2.17 release notes (#1700)
* docs(changelog): publish v0.2.17 release notes

Covers commits between v0.2.16 (2026-04-24) and the v0.2.17 cut
(2026-04-26): --custom-env flag for agents, agent CLI stderr tail in
failure messages, configurable update download timeout, plus reliability
fixes around daemon cancellation, server heartbeat, Codex execenv, Pi
skills path, Windows console, CJK markdown URLs, attachment downloads
and autopilot run-only context.

Both en.ts and zh.ts updated.

* docs(changelog): trim small/internal items from v0.2.17 entry

Drops items that read as internal polish or were too narrow to belong in
release notes:
- Skills landing intro polish
- Codex execenv plugin-cache cleanup
- CLI exact-name/ShortID assignee resolution
- Settings invite role label rendering
- Skills SKILL.md fast-path
- CJK markdown URL-boundary fix
- Relative attachment download URLs

Keeps the user-facing wins: --custom-env, stderr-tail in failure
messages, configurable update timeout, cancelled-task classification,
heartbeat probe/claim split, plus the higher-impact fixes.
2026-04-26 11:10:20 +08:00
Bohan Jiang
aca74293dd fix(agent/claude): surface stderr tail on writeClaudeInput failure + lock with e2e test (#1698)
#1674 wired claude's post-handshake error path through withAgentStderr but
left the writeClaudeInput failure branch returning a bare "broken pipe"
error. That branch fires precisely when claude crashes during startup —
exactly when the stderr tail is most useful for root-causing V8 aborts,
Bun panics, or missing native modules. cmd.Wait() before sampling Tail()
flushes os/exec's internal stderr copy goroutine, matching the
Wait→Tail synchronization contract spelled out in stderr_tail.go.

Adds TestClaudeExecuteSurfacesStderrWhenChildExitsEarly mirroring the
codex test: a fake claude binary drains stdin, writes a V8-abort line to
stderr, and exits 3. Locks in the contract that Result.Error carries the
stderr tail in the post-handshake failure path on the claude backend too.
2026-04-26 11:09:38 +08:00
Bohan Jiang
12e6ca9906 refactor(execenv): collapse codex plugin cache stale-link branches (#1697)
Merge the two symlink removal branches in exposeSharedCodexPluginCache —
they shared the same os.Remove + recreate path with only the error label
differing. The branch is now keyed off Lstat's ModeSymlink bit, with
Readlink reused only to fast-path an already-correct link. Behaviour is
unchanged; just less duplicated code.
2026-04-26 11:05:08 +08:00
jmoney8896
3c3e3bd330 fix(task): reconcile agent status when cancelling tasks by issue (#1587) (#1648)
CancelTasksForIssue silently dropped the list of affected tasks, so
whenever an issue transitioned to "cancelled" or "done" while a task was
still active (6 call sites in issue.go), the underlying agent was left
stuck at status="working" indefinitely and required a manual
`multica agent update <id> --status idle` to self-correct. This matches
the symptom reported in #1587: task rows move to "cancelled" via a
non-user-initiated path, agent status never reconciles.

Change CancelAgentTasksByIssue from :exec to :many (also tack on
completed_at = now() for consistency with CancelAgentTasksByIssueAndAgent),
then update CancelTasksForIssue to iterate the returned rows and call
ReconcileAgentStatus + broadcast task:cancelled per affected task —
mirroring the pattern already used by CancelTask and RerunIssue.

No test added; the change is small and mirrors well-covered paths.
Happy to add a mock-backed test in a follow-up if reviewers prefer.

Refs #1587
Refs #1149
2026-04-26 10:58:42 +08:00
Y. L.
25b393df17 fix(execenv): hydrate Codex skill sources (#1668)
Expose the shared Codex plugin cache inside each per-task CODEX_HOME before launch so plugin-provided skills are available on the first session.

Refresh agent-assigned workspace skills for both newly prepared and reused Codex environments, and cover plugin cache plus reuse behavior with focused execenv tests.
2026-04-26 10:57:51 +08:00
songlei
6f04a6d26b feat(agent): surface agent CLI stderr tail in failure messages (#1674)
Hoist the existing stderrTail ring-buffer (previously codex-only) into
a shared pkg/agent helper so every Backend that supervises a child CLI
can include the last ~2 KB of that CLI's stderr in Result.Error. Wire
the claude backend through the same path.

Motivation: claude on Windows occasionally exits with a non-zero status
after ~5–8 minutes of a single long-running tool_use, and right now the
daemon only reports "claude exited with error: exit status 3" /
"exit status 0x80000003" — useless for root-causing V8 aborts, Bun
panics, native-module OOMs, or any other CLI-side crash. With the tail
attached, the failure message carries the real signal (panic line, V8
assertion, stderr-printed HTTP error) all the way into the task row's
error field that users see in the API.

Renames withCodexStderr to withAgentStderr(msg, label, tail) so the
helper is self-documenting across providers.
2026-04-26 10:55:21 +08:00
Bohan Jiang
58547faf31 fix(server): validate assignee_id existence on issue create/update (#1694)
* fix(server): validate assignee_id existence on issue create/update

POST /api/issues and PUT /api/issues/:id silently accepted any
well-formed UUID as assignee_id (#1662). The new validateAssigneePair
helper consolidates the existing canAssignAgent check and adds:

- existence lookup against workspace members for assignee_type=member
- existence lookup against workspace agents for assignee_type=agent
- pair consistency: type and id must be both set or both null
- whitelist for assignee_type values (member|agent)

UpdateIssue and BatchUpdateIssues now run the same validator on the
post-merge assignee pair whenever the caller touches either field,
closing the parallel gap on the update path.

* fix(server): reject malformed assignee_id at handler entry

parseUUID silently returns an invalid pgtype.UUID for unparseable input
and validateAssigneePair treats (type unset + id invalid) as "no
assignee". Together they let `POST /api/issues` and `PUT /api/issues/:id`
silently drop a malformed assignee_id and return a successful response.

Reject the parse failure inline at every entry point — Create, Update,
and BatchUpdateIssues — so the validator never sees an unparseable id.
Adds two regression tests covering the create and update paths.
2026-04-26 10:35:47 +08:00
Magnus Handeland
9b55b2a9ce feat(cli): add --custom-env flag to agent create/update (#1518)
* feat(cli): add --custom-env to agent create/update

Adds a JSON-object flag on `multica agent create` and `multica agent
update` that writes the agent's `custom_env` map via the existing
handler API. Needed so runtime bearer tokens (e.g. SECOND_BRAIN_TOKEN)
can be provisioned from the CLI without falling back to curl or
admin-only UI access.

- `--custom-env '{"KEY":"value"}'` → sets the map.
- `--custom-env '{}'` or `--custom-env ''` → clears the map on update
  (server treats a non-nil empty map as "clear all entries").
- Omitted flag → no change.
- Help text flags the value as secret material and never logged.
- Table-driven tests cover the parser (valid, clear, invalid JSON,
  wrong shape) plus flag discoverability on both commands.

* feat(cli): add --custom-env-{stdin,file}; sanitize parse errors

Security review of the --custom-env flag (PR #1518) surfaced two issues:

1. Secrets on the command line leak via shell history and /proc/<pid>/cmdline
   regardless of CLI logging. Add --custom-env-stdin and --custom-env-file
   as mutually-exclusive alternatives, and update the --custom-env help
   text to warn about shell history / 'ps' exposure so the "never logged"
   claim is no longer misleading.

2. parseCustomEnv wrapped json.Unmarshal errors with %w; SyntaxError /
   UnmarshalTypeError can surface fragments of the (secret) input. Return
   a fixed, content-free message instead.

Refactor the body-assembly blocks in both agentCreateCmd and
agentUpdateCmd to go through a single resolveCustomEnv helper so the
three input channels behave identically. Tests cover every channel,
mutual exclusion, error sanitization, and help-text wording.

* fix(cli): require explicit '{}' to clear custom_env; sanitize --custom-args errors

Address PR #1518 review feedback from @Bohan-J:

1. parseCustomEnv now errors on empty/whitespace input. The clear signal
   is the explicit '{}' object only. The previous behavior silently wiped
   the secret map when an upstream pipe was empty (cat missing.json |
   ... --custom-env-stdin without set -o pipefail) or when --custom-env-file
   pointed at an empty file. resolveCustomEnv emits channel-specific error
   messages (e.g. "--custom-env-stdin: empty input; pass '{}' to clear").

2. Drop the '&& filePath != ""' guard so an explicit --custom-env-file ""
   surfaces an error instead of being silently ignored.

3. Rewrite TestAgentUpdateNoFieldsMentionsCustomEnv into
   TestAgentUpdateNoFieldsErrorMentionsAllCustomEnvFlags — the body now
   actually runs runAgentUpdate with no flags and asserts the resulting
   "no fields" error names all three --custom-env channels.

4. Extract parseCustomArgs helper. Replace the '%w'-wrapped json error
   with a content-free message, mirroring parseCustomEnv. Although
   custom_args is not a dedicated secret channel, callers regularly stuff
   sensitive values like "--api-key=..." into it, so json.Unmarshal must
   never echo input fragments. Adds TestParseCustomArgsErrorSanitization.

Also adds resolveCustomEnv subtests for stdin/file empty-input, empty
file contents, empty file path, and explicit '{}' positive cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Implementer (Multica Agent) <implementer@multica-agent.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:32:55 +08:00
Bohan Jiang
c7bac0aa6b docs(changelog): publish v0.2.16 release notes (#1695)
Covers everything between v0.2.15 (2026-04-22) and v0.2.16 (2026-04-24):
Chat V2, issue right-click context menu, in-app feedback + Help launcher,
Autopilot modal redesign, Skills page redesign, bilingual flat docs site
rewrite, plus the supporting agent / runtime / chat / desktop fixes.

Both en.ts and zh.ts updated.
2026-04-26 10:22:53 +08:00
Bohan Jiang
101601a4c3 fix(settings): render invite role label via roleConfig in members tab (#1693)
The invite-member role Select rendered the raw value ("member"/"admin")
in the trigger because Base UI's SelectValue defaults to the value, not
the item text. PR #1672 worked around it with `className="capitalize"`,
but this file already owns a roleConfig map with proper labels and the
codebase has an established render-prop pattern for SelectValue (see
trigger-config.tsx and runtime-local-skill-import-panel.tsx).

Use roleConfig[inviteRole].label inside SelectValue and reuse the same
labels for SelectItem children. Single source of truth for role display
names; future role additions or i18n won't depend on CSS capitalize.
2026-04-26 09:43:35 +08:00
Bohan Jiang
95912243bb test(daemon): cover cancelled classification in executeAndDrain (#1692)
Follow-up to #1686. Locks in two nits flagged during review:

1. agent.Result.Status doc comment now lists "cancelled" alongside the
   existing values, so the enum surface matches actual usage.
2. New TestExecuteAndDrain_ContextCancelled_ReportsCancelled exercises
   the path added in #1686: when the parent context is cancelled before
   the backend produces a Result, executeAndDrain must return
   Status="cancelled" (not "timeout"). A regression here would silently
   restore the misleading log line we just fixed.
2026-04-26 09:27:13 +08:00
Kagura
24e135541b fix(server): use resolved issue ID in DeleteIssue handler (#1680)
DeleteIssue passed the raw URL parameter through parseUUID(), which
returns a zero UUID for human-readable identifiers like "API-123".
This caused DELETE requests with identifier-style IDs to silently
succeed (204) without actually deleting the issue.

Use issue.ID from the already-resolved issue object instead, consistent
with BatchDeleteIssues and all other operations in the same handler.

Fixes #1661
2026-04-26 09:24:19 +08:00
Alex Fishlock
2df969cffc fix(daemon): report cancelled tasks as "cancelled", not "timeout" (#1686)
When the server cancels a task (e.g. assignee changes during execution,
explicit user cancel, or workspace_isolation check fail), the daemon's
cancellation poll fires runCancel() on the run context. The drainCtx
derived from runCtx then signals Done(), but executeAndDrain() was
returning Status: "timeout" regardless of *why* the context ended.

The "agent finished status=timeout" log line is then misleading — it
suggests an actual deadline timeout when really the task was cancelled
by upstream. We spent hours misdiagnosing a healthy handoff as a
broken timeout because of this.

Distinguish context.Canceled from context.DeadlineExceeded in
executeAndDrain, and add a "cancelled" case to runTask so the status
propagates through the existing log path.

No behaviour change for genuine timeouts; no behaviour change for
the cancelled-by-poll discard path in handleTask. Only the daemon
log line and TaskResult.Status get the more accurate label.
2026-04-26 09:23:32 +08:00
lmorgan-yozu
5eab1dbbe1 fix: handle relative attachment download URLs
Resolve server-relative attachment download URLs against the CLI server base URL while preserving signed absolute URL behavior.
2026-04-25 02:13:18 +08:00
Bohan Jiang
a89064d693 docs: clean up leftover .pi/agent/skills references (#1645)
PR #1632 updated the Pi project-level skill dir from
.pi/agent/skills/ to .pi/skills/, but missed two references:

- server/internal/daemon/execenv/runtime_config.go:20 — the comment
  block here lists project-level paths for every other provider, so
  using Pi's global path was inconsistent and misleading.
- docs/docs-rewrite-plan.md:88 — planning doc still listed the old
  path in the Skills row.

Follow-up to #1632.
2026-04-25 02:08:33 +08:00
etern
68a312c297 fix(runtimes): fix pi skills dir to: .pi/skills (#1632)
change .pi/agent/skills to .pi/skills

Pi loads skills from:

Global:
  ~/.pi/agent/skills/
  ~/.agents/skills/
Project:
  .pi/skills/
  .agents/skills/

- ref: https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/skills.md#locations
2026-04-25 02:06:25 +08:00
Bohan Jiang
683ff132ca fix(server/heartbeat): probe/claim split + slow-log + model-list running timeout (#1644)
Mitigates #1637 and the related model-discovery failure in MUL-1397 by bounding the /api/daemon/heartbeat hot path with an ack-safe probe/claim split, adding structured slow-log attribution, and closing the ModelListStore running-state gap. See PR description for details.
2026-04-25 02:06:00 +08:00
Truffle
93fe324bb9 fix(skills): fast-path root-level SKILL.md with frontmatter guard (#1625)
Closes the functional gap the reporter hit on alchaincyf/huashu-design
(skills.sh/alchaincyf/huashu-design/huashu-design) without expanding
candidatePaths unconditionally, which would let an unrelated root
SKILL.md hijack a different skill URL in a multi-skill repo.

Try SKILL.md at the repo root before falling into the recursive tree
fallback added in #1432. Verify the frontmatter name matches the
requested skill so only genuine single-skill repos take the fast path.
For those repos this also shaves the recursive tree API call.

Also clarifies the candidate-path comment so the root case is
explicit.
2026-04-25 01:40:23 +08:00
Bohan Jiang
74593fdb88 fix(daemon): use CREATE_NEW_CONSOLE to stop grandchild console popups on Windows (#1521) (#1643)
* fix(daemon): use CREATE_NEW_CONSOLE to stop grandchild console popups on Windows (#1521)

CREATE_NO_WINDOW strips the console entirely. When the agent CLI then
spawns a console-subsystem grandchild (bash, cmd, netstat, findstr,
timeout) without itself passing CREATE_NO_WINDOW, Windows allocates a
brand-new visible console window per invocation — trading one popup per
agent run for N popups per tool call.

Switch to CREATE_NEW_CONSOLE + HideWindow=true so the agent gets a
hidden console that grandchildren inherit. Stdio pipes still work via
STARTF_USESTDHANDLES; no changes needed at the 17 hideAgentWindow call
sites.

Add a Windows-only regression test asserting CREATE_NEW_CONSOLE is set
and CREATE_NO_WINDOW is not, per the #1474 Windows-test follow-up.

Root-cause diagnosis by @matrenitski (verified against the shipped
multica.exe and the Claude Code CLI it spawns) in issue #1521.

* test(agent): use CREATE_NEW_CONSOLE-compatible flag in preservation test

CREATE_NEW_PROCESS_GROUP is silently ignored by Windows when combined
with CREATE_NEW_CONSOLE, so asserting it 'survives' was only bitwise-true,
not semantically meaningful. Switch the example to
CREATE_UNICODE_ENVIRONMENT (documented compatible) and also assert a
non-flag field (NoInheritHandles) survives to exercise full struct
preservation.
2026-04-25 01:40:15 +08:00
Bohan Jiang
60fdc82824 fix(cli): resolve assignee by exact name or ShortID to avoid substring collisions (#1642)
`multica issue assign --to <name>` matched agent/member names with a plain
`strings.Contains` check, so an exact match on `reviewer` became ambiguous
whenever a longer agent like `peer-reviewer` also existed. There was also
no way to disambiguate by ID.

Rework `resolveAssignee` to bucket candidates by priority:
1. Full UUID or 8-char ShortID (matches `truncateID` output) — case-insensitive.
2. Case-insensitive exact name (with surrounding whitespace trimmed).
3. Substring fallback — preserves the existing partial-name UX.

The first non-empty bucket wins. Ambiguity inside a higher-priority bucket
still errors and short-circuits lower-priority matching.

All six call sites (`issue assign/update/create/list`, `issue subscriber`,
`project`) are fixed by this single change.

Fixes #1620
2026-04-25 01:05:29 +08:00
Naiyuan Qing
c3ae212b40 fix(markdown): treat CJK full-width punctuation as URL boundary (#1630)
linkify-it only recognizes ASCII characters as URL boundaries. In Chinese
or Japanese text a URL followed by "。" (or any other full-width
punctuation) was greedily swallowed into the URL along with everything up
to the next whitespace, producing hrefs like
`https://.../pull/1623。merge` that 404 when clicked.

Truncate the detected URL at the first CJK full-width punctuation
character and re-scan the tail, so adjacent URLs separated only by
full-width punctuation are still each linked individually. The
terminator character set mirrors the fix applied in mattermost/marked#22.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 18:47:47 +08:00
Joey
d17b2bfb8c feat(cli): 添加更新下载超时配置选项 (#1622)
- 在 update 命令中添加 --download-timeout 标志用于设置下载超时时间
- 实现 UpdateViaDownloadWithTimeout 函数支持自定义下载超时
- 添加 updateDownloadTimeoutOrDefault 辅助函数处理超时值验证
- 设置默认下载超时时间为 120 秒
- 添加 updateDownloadTimeoutOrDefault 函数的单元测试
- 验证超时参数必须大于零的错误处理逻辑
2026-04-24 17:05:23 +08:00
devv-eve
13d9d7df1b fix: pass autopilot run-only context to agents
Fix run-only autopilot tasks so agents receive autopilot context instead of empty issue instructions. Add regression coverage for run-only terminal event sync.
2026-04-24 16:36:04 +08:00
Naiyuan Qing
71b2032174 feat(skills): restore page description, link to docs, polish intro layout (#1618)
* feat(skills): restore page description, link to docs, polish intro layout

The previous card-layout refactor (#1614) dropped the page-top
description entirely; without it the page jumps straight from the
PageHeader to a brand-colored banner that explains *how sharing works*,
with nothing answering "what IS a skill?". Bring the description back,
add a docs entry point, and tighten the visual hierarchy so the intro
block reads as one coherent unit above the table card.

- Restore a one-line description as the page's primary intro:
  "Instructions any agent in this workspace can use." — uses "any agent
  ... can use" (capability, not factual usage) since skills must be
  manually attached to take effect.
- Add an inline "Learn more about Skills →" link mirroring the
  onboarding docs-link pattern (muted underline, new tab) — opens
  https://multica.ai/docs/skills.
- Visual hierarchy: description is text-base + text-foreground (primary),
  link is text-xs + text-muted-foreground (auxiliary). Same line, eye
  follows weight order.
- Banner padding bumped from px-3 py-2 to px-4 py-3 so it breathes and
  its inner text lands at the same x as the table content.
- Wrap description + banner in a shared `pl-4 space-y-3` so they read as
  one intro block, indented to align with the table card's content.
- Loading skeleton updated to mirror the new structure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(skills): keep docs link underline subtle, only animate text color on hover

The underline was inheriting text-decoration-color from the link's text,
so when hover bumped the text from muted to foreground the underline
got darker too — making the link feel more prominent on hover than at
rest, the opposite of what we want for a tertiary docs link.

Pin decoration-color to muted-foreground/30 explicitly so it stays
faint regardless of hover state. Only the text color transitions; the
underline stays as a constant low-key marker that the element is a link.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:35:33 +08:00
Naiyuan Qing
f7fe0829f2 refactor(skills): wrap list as card, use shared PageHeader, add scroll fade (#1614)
The skills page rolled its own HeroHeader instead of the shared PageHeader,
which meant no mobile sidebar trigger and visual drift from other list
pages. The table was also edge-to-edge inside the dashboard container, so
it felt "naked" compared to the rest of the product.

- Replace custom HeroHeader with shared PageHeader (gives mobile hamburger
  and h-12 chrome for free); move "New skill" into the PageHeader as the
  page-level action.
- Keep search + scope filters in a toolbar, but move that toolbar *inside*
  a bordered, rounded card together with the table, so the whole unit
  reads as a single scrollable surface with internal padding.
- Use the existing useScrollFade hook on the row list so the top/bottom
  edges fade while scrolling.
- Drop `divide-y` in favor of `border-b` per row — divide-y leaves the
  last row without a bottom rule, which looks unfinished when only a
  couple of skills exist and the scroll area is taller than the content.
- Drop the redundant description paragraph from the old hero; keep the
  "Shared with your workspace" banner above the card since it carries
  non-obvious UX context.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.2.16
2026-04-24 15:04:09 +08:00
LinYushen
9e1e3981fb fix(workspace): defense-in-depth owner check in DeleteWorkspace handler
Adds an owner check inside DeleteWorkspace as defense-in-depth and covers both router-level and direct handler paths.
2026-04-24 14:29:39 +08:00
Naiyuan Qing
c7e725ef66 feat: surface docs from onboarding + landing, unify Autopilot naming (#1613)
* docs(autopilot): rename Routines → Autopilots to match product UI

Unify naming between docs and product. Sidebar label, URL route,
CLI command, and onboarding copy all call this feature "Autopilot";
the docs were the only surface that diverged. Aligning the docs to
the product (rather than the reverse) because the 830+ code-side
references would be a much larger rename to propagate.

- Rename routines.mdx / routines.zh.mdx → autopilots.mdx / autopilots.zh.mdx
- Update meta.json / meta.zh.json index entries (routines → autopilots)
- Drop the reconciliation note ("docs say Routines, CLI says autopilot")
  that shipped in the original routines.mdx and the cli.mdx section header
- Update cross-references in cli, how-multica-works, tasks,
  assigning-issues, chat, mentioning-agents, daemon-runtimes (EN + ZH)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(onboarding): link to docs from key steps and starter tasks

Users who want to dig deeper now have a next hop from inside the flow
instead of having to dig through the help menu. Placed as secondary
links (muted, underline-offset-4) so they don't pull focus from the
primary CTA on each step.

Placement — one link per surface, placed in secondary regions:
- Welcome: "Learn how Multica works" below the subhead
- Questionnaire: "Learn how agents work" in the Why-we-ask aside
- Runtime aside (shared by desktop + web): "Learn about runtimes"
- Agent step: "Creating your first agent" in the About-agents aside
- StarterContentPrompt dialog: "Learn how Multica works"

Starter tasks (content/starter-content-templates.ts): added a single
"Learn about X" tail link per task, only on first occurrence of each
concept within a branch. 8 links on the agent-guided branch + 8 on
the self-serve branch + 1 on the welcome issue header (17 total).

URL scheme: absolute https://multica.ai/docs/{slug} throughout —
absolute so desktop (Electron) opens them in the system browser, and
the /en prefix is omitted because the docs middleware redirects it
away (English is the default, Chinese is /zh/).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(landing): add docs link to footer and how-it-works section

Docs were previously reachable only from the in-app help menu. Landing
now surfaces them in two places, both locale-aware (/docs for English,
/docs/zh for Chinese):

- Footer Resources group: Documentation link was pointing at the
  GitHub repo; replaced with the real docs URL
- How-It-Works section CTA row: added "Read the docs" between the
  primary CTA and the GitHub link, same ghost styling

Locale resolution: href is picked per-render based on the landing's
current locale (cookie-driven via useLocale). The docs app itself
does not auto-detect language, so we must pick the right path
explicitly when emitting the link.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(onboarding): clean up Autopilot rename leftovers and link formatting

- comments.mdx: "not routine updates" → "not day-to-day updates"
  (adjectival holdover now that the feature is renamed Autopilot;
  zeroes out remaining "routine" mentions in user-facing docs)
- starter-content-templates.ts: move the arrow inside the markdown
  link — "[text →](url)" instead of "→ [text](url)" — so the arrow
  is part of the clickable region. 17 occurrences.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(onboarding): drop docs link from welcome screen and starter-content dialog

"Learn how Multica works" was showing up too often in the first two
screens users see. Keep the link in the post-import welcome issue
header (where users actually have time to explore); remove it from
the two earlier surfaces where it competes with the primary CTA.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 14:27:53 +08:00
Naiyuan Qing
fe84e29b64 fix(ui): stop menu hover from overriding icon colors (#1612)
Menu primitives (context/dropdown/menubar/select/command) had rules like
`focus:**:text-accent-foreground` and `*:[svg]:text-destructive` that forced
descendant svg colors on focus, overriding icons that set their own color
(e.g. StatusIcon's `text-warning`). Remove them so icon color comes from
inheritance only: colored icons keep their color on hover, uncolored icons
still inherit the item's focus/destructive color as before.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 14:26:58 +08:00
Naiyuan Qing
4f40f70ea7 fix(skills): remove double-flicker on CreateSkillDialog close (#1610)
CreateSkillDialog used a controlled \`open\` prop while staying mounted,
so closing meant a data-open → data-closed flip on the already-mounted
Popup plus a tail re-render from \`useEffect([open])\` resetting \`method\`.
Visible as a double-blink: first the close animation, then a second
fade when the reset effect fired.

Align with the CreateIssue / CreateProject pattern: parent conditionally
renders the dialog and \`<Dialog open>\` is hard-coded. Close now unmounts
the component and Base UI's Portal owns the single exit animation. The
per-open method reset becomes unnecessary — fresh mount, fresh state.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 14:24:50 +08:00
LinYushen
99154d97b9 Restrict /health/realtime metrics exposure (MUL-1342) (#1608)
* Restrict /health/realtime metrics exposure (MUL-1342)

The realtime metrics endpoint was registered on the public router with
no authentication, exposing per-event/per-scope counters, redis.last_error,
and redis.node_id to anonymous callers. This enables information disclosure
and traffic profiling.

Move the handler behind a token + loopback policy:

- If REALTIME_METRICS_TOKEN is set, require Authorization: Bearer <token>
  using a constant-time compare. Reject other callers with 401 plus a
  WWW-Authenticate hint.
- If the env var is unset, only serve loopback callers and return 404 to
  remote clients so the endpoint is not enumerable. This keeps local dev
  workflows working without configuration.

The handler is extracted into health_realtime.go with focused unit tests
covering the token, loopback, and rejection paths. .env.example documents
the new variable.

Refs: https://github.com/multica-ai/multica/issues/1606

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fail closed for proxied /health/realtime requests (MUL-1342)

Addresses review on PR #1608: when the server runs behind a reverse
proxy (Caddy / Nginx -> localhost:8080), public callers reach the Go
handler with RemoteAddr=127.0.0.1, so the previous loopback shortcut
exposed the metrics surface in self-hosted deployments.

The no-token path now treats any forwarding header
(X-Forwarded-For / -Host / -Proto, X-Real-Ip, Forwarded) as a
'this request was proxied, can't attribute, fail closed' signal and
returns 404. Direct loopback callers without those headers still work
for local dev. Token-gated path is unchanged.

Tests cover all listed proxy headers (incl. multi-hop XFF chain and
RFC 7239 Forwarded) over both 127.0.0.1 and ::1, plus a regression
case ensuring an empty/whitespace forwarding header does not break
direct loopback access. .env.example updated to call out that proxied
deployments must configure REALTIME_METRICS_TOKEN.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: CC-Girl <cc-girl@multica.ai>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-24 14:04:10 +08:00
Naiyuan Qing
7067d8f125 refactor(skills): redesign list page and add skill detail page (#1607)
* feat(core): add skill detail path and query helpers

- paths.workspace(slug).skillDetail(id) → /:slug/skills/:id
- skillDetailOptions(wsId, skillId) for fetching a single skill
- selectSkillAssignments(agents) folds the cached agent list into
  Map<skillId, Agent[]>; returns a stable reference so consumers can
  memoize against agent-array identity without re-rendering on unrelated
  agent updates

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(views): add cross-platform openExternal helper

On Electron, route through window.desktopAPI.openExternal so the
http/https-only guard in the main process kicks in — direct window.open
inside Electron opens a new renderer window instead of handing the URL
to the OS shell. On web, fall back to window.open with noopener+noreferrer.
SSR-safe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(skills): extract edit-permission hook and origin helper

- use-can-edit-skill: mirrors the server's rule (admin/owner ∨ creator)
  so the UI can hide/disable actions instead of waiting for a 403. Takes
  wsId explicitly per the repo rule for workspace-aware hooks.
- lib/origin: discriminated view over Skill.config.origin (manual /
  runtime_local / clawhub / skills_sh) so consumers don't spread JSONB
  parsing across the UI tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(skills): rewrite skills list page and collapse import UI

- SkillsPage rewritten: new hero header, single table layout with
  columns (Name / Used by / Source · Added by / Updated), agent avatar
  stack per skill, filter tabs aligned with Issues/MyIssues header
  (Button variant=outline + Tooltip + bg-accent active state).
- CreateSkillDialog: dedicated dialog for the manual/import entry
  points, replaces the inline row-triggered dialog.
- runtime-local import: dialog variant deleted; panel is now the single
  entry point, embeddable inside CreateSkillDialog. Panel covered by a
  new test.
- Deleted runtime-local-skill-row (no longer needed — row rendering
  lives in SkillsPage directly) and the old skills-page.test.tsx
  (structure diverged beyond salvaging; will be re-added alongside the
  detail-page tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(skills): add skill detail page and wire routes on web and desktop

- SkillDetailPage: dedicated view for a single skill (name, description,
  origin, assignments, file listing). Uses skillDetailOptions and the
  new origin / use-can-edit-skill helpers.
- apps/web: /:workspaceSlug/skills/:id Next.js route.
- apps/desktop: /:slug/skills/:id added to the memory router under
  WorkspaceRouteLayout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(skills): bump runtime-local-skill-import-panel timeouts for CI

The test chains a five-step async cascade (runtime list → setSelectedRuntimeId
effect → skills query → auto-select effect → row render). Comfortable on
local (~600ms) but tight against RTL's 1 s default on CI where jsdom +
Vitest import takes ~100s. Bump findByText and the two waitFor calls to
5 s each — no production behaviour change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 13:51:58 +08:00
devv-eve
9ed1fa95fc feat(server): add readiness health endpoints (#1605)
* feat(server): add readiness health endpoints

* fix(server): cache readiness checks

* fix(server): raise readiness cache ttl

---------

Co-authored-by: Eve <eve@multica.ai>
2026-04-24 13:50:24 +08:00
Naiyuan Qing
147fb2ee66 fix(autopilot): confirm before deleting autopilot or trigger (#1604)
Destructive actions in the autopilot detail page fired immediately on
click. Wrap "Delete autopilot" and per-trigger delete with AlertDialog
confirmation, matching the existing issue-delete pattern.

Also fix a latent bug in trigger deletion where the success toast was
shown synchronously after mutate(), so failures still reported success —
switch to mutateAsync + try/catch.
2026-04-24 13:11:52 +08:00
L.Amar
9c177562e2 fix(daemon/repocache): make bare repo cache keys collision-resistant 2026-04-24 13:04:08 +08:00
Naiyuan Qing
5bab95ad26 fix(issues): unify board card hover and active visual (#1603)
Hover and popup-open states now share the same bg-accent + border-accent
treatment. Drop the shadow-md hover (invisible in dark mode) and the
multi-property transition in favor of a single transition-colors.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 12:55:30 +08:00
Naiyuan Qing
0bd6ba9354 fix(issues): cleaner board card hover with shadow elevation (#1600)
Replace translucent tinted hover (border-accent/50 + bg-accent/20) with
a single-dimension shadow lift. The previous overlay was visually weak
because --accent is nearly identical to --card, so a 20% tint rendered
as almost no change. Active (popup-open) state now uses solid bg-accent
so hover and active are distinguished by different dimensions —
elevation vs color — instead of competing on the same axis.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 12:15:07 +08:00
Naiyuan Qing
40cea8454d feat(autopilot): redesign modal — simpler schema, consistent schedule UI (#1595)
Drop priority and project_id from autopilot. project_id was never exposed
in the UI and priority duplicated the agent's own task queue priority.

Redesign the create/edit modal as a Runbook (left) + Configuration (right)
layout. Rework the Schedule section around a single visual shell so every
picker aligns pixel-for-pixel on the same row:

- TimeInput (new): segmented HH:MM control adapted from openstatusHQ/time-picker,
  driven by keyboard (ArrowUp/Down to step, ArrowLeft/Right to jump segment,
  digit typing with a 2s two-digit window). Replaces <input type="time">,
  whose native UI broke the design system. Supports a minuteOnly variant
  for hourly schedules.
- TimezonePicker (new): searchable Popover with a fixed-width left check
  slot so rows stay aligned and GMT offsets never collide with the selected
  indicator.
- Runbook editor now lives in a bordered card, giving the placeholder an
  input surface instead of bare document flow.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 11:05:33 +08:00
Naiyuan Qing
d54daa62c5 feat(issues): right-click context menu + unified issue actions (#1594)
* feat(issues): add right-click context menu on list rows and board cards

Extract the detail page's ⋯ dropdown (~180 lines of inline JSX) into a
shared `useIssueActions` hook plus two thin wrappers so the same action
set (status / priority / assignee / due date / sub-issue ops / pin / copy
link / delete) can be mounted as both a DropdownMenu and a Base UI
ContextMenu. Right-click on any list row or board card now opens the
full action menu without entering the detail page.

Shell-level modals replace the detail-page-local state for set-parent /
add-child / delete-confirm / backlog-agent-hint, so any trigger (detail
page, list, board) can open them through `useModalStore`. Detail page
detects its own deletion via a query-transition effect, avoiding the
need to smuggle callbacks through the store.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(issues): hover and active styling on list rows and board cards

Mirror the sidebar's same-color/different-intensity pattern for the new
right-click context menu states. Base UI adds `data-popup-open` to the
ContextMenuTrigger when the menu is open; `hover:not-data-[popup-open]`
suppresses hover feedback on the already-active item.

List rows apply the pattern directly to background color (`accent/60`
hover, `accent` active). Board cards additionally modulate the card's
border and a lighter background tint (`accent/20` hover, `accent/40`
active) so the card's own bg/border/shadow identity stays intact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(modals): show target issue banner in SetParent/AddChild pickers

When triggered from an issue's action menu, the IssuePickerModal now
displays a banner at the top showing "Setting parent of" / "Adding
sub-issue to" followed by the originating issue's status, identifier,
and title. Previously the operation target was only implied by the
modal's sr-only title.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(modals): create-issue gains ⋯ overflow menu with parent issue linkage

Add a dropdown-menu with "Set parent issue..." / "Remove parent" at the
end of the property pill row. The ⋯ button is always the last DOM child
of the row so it stays at the tail even when the row wraps to multiple
lines. Menu state reflects current selection — unset shows a single
"Set parent…" entry, set shows the current identifier plus a separate
Remove option.

When a parent is set (either via the new menu or via `data.parent_issue_id`
from a "Create sub-issue" trigger), a chip appears in the pill row
showing "Sub-issue of {identifier}" with the same click-to-change /
click-×-to-clear semantics. This replaces the old header breadcrumb
disclosure that was neither editable nor visible in the form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(issues): group relationship actions under "More" submenu

Nest Create sub-issue / Set parent issue / Add sub-issue inside a
`More >` submenu in the issue actions menu (both Dropdown and
Context variants). Top-level keeps Status/Priority/Assignee/Due date
category submenus plus Pin and Copy link; the relationship ops are
lower-frequency and will grow with future relation types (blocks,
duplicates, related) that fit the same category.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(modals): create-issue adds Add sub-issue with deferred linking

The create modal's ⋯ menu gains an "Add sub-issue..." entry that queues
existing issues as children of the new one. Picked issues appear as
chips in the pill row (downward arrow, distinct from the upward parent
chip), each individually removable.

Linking is deferred because the new issue's ID doesn't exist at pick
time. Once createIssueMutation resolves, we run updateIssueMutation
for every queued child in parallel and surface any partial failures
via toast — the new issue itself is already committed and never rolls
back. Parent and child pickers exclude each other so a single issue
can't occupy both relations simultaneously.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* polish(issues): add MoreHorizontal icon to "More" submenu trigger

The "More" label was visually misaligned because every other top-level
entry has a leading icon. Use MoreHorizontal (same icon as the outer ⋯
trigger — semantically "more options, nested") and drop the `inset`
padding hack.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* revert(modals): drop target-issue banner from IssuePickerModal

The banner sat directly above the search input and rendered the target
issue with bolder styling than the "Setting parent of" / "Adding sub-issue
to" caption, which made it read like a pre-selected search result rather
than a context label. Users opening the modal from a menu item already
carry the context, so the extra chrome was redundant.

Remove the contextIssue / contextLabel API from IssuePickerModal and
drop the now-unused issueDetailOptions query in SetParentIssueModal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* polish(modals): exclude current parent from create-issue parent picker

Re-opening the parent picker to change the already-set parent used to
show that parent in the results — picking it was a silent no-op. Mirror
the child picker's exclude-list construction so the current parent is
always filtered out.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 10:48:46 +08:00
Naiyuan Qing
8c2e08418f feat(docs-site): rewrite docs as bilingual flat content tree (#1591)
* chore(docs-site): add @multica/ui bridge and dev:docs script

Link @multica/ui as a workspace dep of @multica/docs so the docs app can
consume the shared design tokens (tokens.css, base.css) via a relative
import — same pattern the web and desktop apps use. Add a top-level
pnpm dev:docs script for a one-command docs dev server (port 4000).

Preparation for the docs site rewrite tracked in docs/docs-outline.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(docs-site): apply Multica tokens and pure-sans typography

Replace Fumadocs' neutral color preset with a @theme inline bridge that
maps the --color-fd-* chrome tokens to Multica's --background / --foreground
/ --border / --sidebar-* etc. Sidebar, nav, cards now pick up Multica's
cool-gray palette automatically, and switching Multica's .dark flips
Fumadocs chrome with it.

Typography: pure sans (36px / weight 600 / tight tracking h1, h2+h3 tuned
to match), landing continuity without serif display.

Code blocks: pinned to near-black (oklch(0.12 0.01 250)) regardless of
page theme so they read as a continuation of the landing hero surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(plan): add rewrite plan and outline tracker

Two planning documents for the docs site rewrite:

- docs/docs-rewrite-plan.md — strategic rationale (positioning, reader
  personas, design principles, visual direction, phase breakdown).
- docs/docs-outline.md — execution tracker. 25 v1 pages with per-page
  entries (source files, audience, what-to-write, what-not-to-write,
  ⚠️ verify-before-drafting). Workflow: claim via Owner + Status,
  read source, verify checklist, draft, review, ship.

Language: zh only for v1. Outline is the source of truth for scope and
status; the earlier "EN first, ZH as Phase 10" line in rewrite-plan.md
is superseded.

Welcome (§1.1) is claimed under this tracker and currently in 👀 review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(docs-site): write first Welcome page (zh) — §1.1

Implements §1.1 Welcome per docs/docs-outline.md. Chinese-first (per
outline language decision); terms translated to their clearest Chinese
equivalents (issue → 任务, agent → 智能体, daemon → 守护进程, etc.),
product proper nouns and commands kept in English.

Voice: reference-style, not marketing. Follows google-gemini/docs-writer
skill rules (BLUF opener, second-person, active voice, no hype, overview
prose before every list).

Content:
- Opens by describing Multica as a 任务协作 platform and how humans + AI
  智能体 share the same 工作区
- Two interaction modes: 分配任务 and 聊天
- 智能体在哪里运行: local daemon (today), cloud runtime (soon, waitlist).
  10 providers listed from source (server/pkg/agent/*.go).
- Three usage paths split into back-end (Cloud / Self-host) and client
  (Desktop) choices — Desktop bundles CLI and auto-starts daemon.
- Status: 👀 In review.

Also simplifies content/docs/meta.json to just ["index"] (placeholder
page entries removed; IA skeleton will be populated in Phase 2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(docs-site): wire up client-side Mermaid rendering

Add a <Mermaid> React component under apps/docs/components/ that dynamic-
imports the mermaid package in useEffect and renders the resulting SVG.
Deps added: mermaid@^11.14.0 and next-themes@^0.4.6 (transitively present
via fumadocs-ui but needs explicit declaration to be importable).

Design choices:
- Client-side render (not build-time). No Playwright / browser automation
  in CI. Mermaid bundle (~400 KB) is loaded only on pages that use the
  component, thanks to the dynamic import.
- Theme flips automatically — useTheme() from next-themes re-invokes
  mermaid.initialize() with the correct theme on .dark toggle.
- SSR safe: the component returns a "Rendering diagram…" placeholder on
  the server; the SVG appears after hydration.
- securityLevel "strict" — diagrams render as static SVG with no inline
  script or event handlers.

Usage in mdx (explicit import, same pattern as Cards/Callout):

  import { Mermaid } from "@/components/mermaid";

  <Mermaid chart={`
    graph LR
      User --> Server
  `} />

Verified by a scratch /app/mermaid-test/ route that compiled to 4665
modules and returned HTTP 200 (cleanup done pre-commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(docs-site): adopt v2 editorial palette and typography

Replace the Linear/Vercel-style cool-gray token override with a warm
editorial palette (bg matches landing #f7f7f5, brand-color primary via
Multica's existing --brand hue 255) and wire Source Serif 4 for heading
typography. Italic is avoided sitewide — Chinese italic renders as a
synthetic slant against upright-designed glyphs and reads as broken;
emphasis is carried by serif/sans contrast, brand color, and weight.

Sidebar adopts the product app's active-fill pattern (solid
sidebar-accent background, no ::before mark). Code blocks drop the
always-dark hero treatment and follow page theme so the reading column
stays coherent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(docs-site): add editorial MDX components

New components/editorial.tsx exposes Byline, NumberedCards/NumberedCard,
and NumberedSteps/Step — the "wow moment" pieces from v2-editorial
(ruled-divider bylines, No. 01 serif card numbering, large serif step
counters). All escape prose via not-prose so they run their own type
scale.

DocsHero is rewritten as an editorial showpiece: title accepts ReactNode
so callers can pass a brand-color em accent, eyebrow becomes a small
uppercase sans label, lede uses serif at 20px.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(docs-site): rewrite welcome page as editorial showpiece

Welcome page now opens with an editorial hero (eyebrow + serif h1 with
brand-color em accent on "共处一方。" + serif lede), a ruled byline
strip carrying the section / updated / read-time metadata, and then
flows into prose.

The three deployment paths switch from fumadocs's <Cards> to
<NumberedCards> so each gets a No. 01/02/03 label, and the "next steps"
list becomes a <NumberedSteps> block with large serif counters. These
are the highest-impact visual moments on the page; the rest of the
guide pages still get the global editorial chrome without needing
per-page code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(docs-site): add bilingual flat content tree with i18n routing

Restructures the docs site from nested topic folders (cli/, getting-started/,
developers/, guides/) into a flat content tree, and adds Chinese alongside
English. The old nested structure forced contributors to think about both
the topic AND the user-journey grouping; the flat tree lets a single
meta.json control reading order with separator labels, and lets the same
slug serve both languages via the `foo.zh.mdx` parser convention.

Routing
- New `app/[lang]/` segment hosts layout, home, slug page, and not-found
- Self-contained basePath-aware middleware (fumadocs's built-in middleware
  isn't basePath-aware, so its rewrite/redirect targets break under /docs)
- `hideLocale: 'default-locale'` keeps English URLs prefix-less; Chinese
  lives under /docs/zh/
- Sitemap excluded from middleware matcher so crawlers don't get rewritten
  into a non-existent locale-prefixed sitemap route
- Default-language redirect preserves search string (UTM safety)
- Home page declares its own generateStaticParams (Next layout params
  don't cascade) so /docs/ and /docs/zh are SSG, not dynamic per request

SEO
- New app/sitemap.ts emits hreflang alternates for every page
- absoluteDocsUrl normalizes the home `/` so canonical URLs don't carry a
  trailing slash that mismatches the page's own canonical link
- apps/web/app/robots.ts now advertises the docs sitemap

Search
- CJK tokenizer registered for the zh locale (Orama's English regex strips
  Han characters; without this Chinese search either returns empty or
  throws)

Chrome
- Custom DocsSettings replaces fumadocs's default icon-only sidebar footer
  with two labelled buttons (language + theme), matching the editorial
  design language

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 10:30:54 +08:00
Jiayuan Zhang
71cc646951 fix(chat): prevent UI flicker when streaming response finalizes (#1583)
The live timeline was rendered in a separate <div> from the persisted
messages list. When the streamed task finished and its ChatMessage
landed, the live <div> unmounted and a new <MessageBubble> mounted —
two different DOM elements showing the same content. useAutoScroll's
ResizeObserver + MutationObserver fired on both the unmount and the
mount, causing the visible jump-then-re-render.

Merge the two paths: inject a synthetic assistant message with the
pending task_id while streaming, and key every assistant bubble by
task_id. When the real message arrives (same task_id), React preserves
the DOM element across the invalidate → refetch window — no remount,
no double scroll, no flicker.

Co-authored-by: Lambda <f252c2c5-7d1d-4f3c-b394-a61abfe673fc@users.noreply.multica.ai>
2026-04-24 02:01:12 +08:00
Jiayuan Zhang
bb767e0ea6 fix(chat): prevent chatbox jump when sending first message (#1582)
The ChatInput wrapper toggled between pb-8 (empty state) and pb-4
(has messages), causing a 16px vertical jump the moment hasMessages
flipped. EmptyState already centers itself inside flex-1, so the
extra padding wasn't needed — collapse to a single pb-4.

Co-authored-by: Lambda <f252c2c5-7d1d-4f3c-b394-a61abfe673fc@users.noreply.multica.ai>
2026-04-24 02:00:12 +08:00
Jiayuan Zhang
35aca57939 feat(chat): Chat V2 — sidebar entry + main-area page (#1580)
* feat(chat): Chat V2 — sidebar entry + main-area page

Replace the floating drawer + FAB with a first-class workspace route
`/:slug/chat`. Sidebar gets a single `Chat` entry under Inbox with an
unread dot; session history lives inside the Chat tab via a popover
rather than leaking into the global sidebar (keeps Multica's "nouns in
the nav" semantic — Inbox / Issues / Projects are work objects, Chat is
a tool).

- Add `paths.workspace(slug).chat()` + update link-handler route set.
- New `ChatPage` view with PageHeader, history popover, centered
  messages/composer column, and empty-state starter prompts.
- Delete `ChatWindow`, `ChatFab`, resize helpers, and standalone
  `ChatSessionHistory` (history now embedded in the popover).
- Drop `isOpen`/`toggle`/`showHistory`/resize fields from `useChatStore`
  — the page is a route now, not an overlay.
- Wire the new `/chat` route on web (App Router) and desktop
  (react-router + tab-store icon mapping).

Addresses MUL-1322.

* fix(chat): align composer width with message column

The ChatPage wrapper added px-4 on top of ChatInput's own px-5, making
the composer 32px narrower than the messages column. Drop the outer
px-4 so both share the same max-w-3xl outer + px-5 inner padding
provided by ChatMessageList / ChatInput.

* fix(chat): taller default composer (~3 lines visible, 8 max)

min-h 4rem → 7rem, max-h 10rem → 15rem. Empty state previously
showed only 1 text row after pb-9 for the action bar; raise the
floor so there's visible writing room and lift the ceiling so a
longer draft can grow before scrolling kicks in.

* fix(chat): restore anchor + in-flight indicator + cold-start session restore

Three issues surfaced by review:

1. ContextAnchorButton always disabled on /:slug/chat — useRouteAnchorCandidate
   only matches issue/project/inbox pathnames, so moving chat to its own route
   dropped 'bring the page I was on into the conversation'. Track the last
   anchor-eligible location globally (new useAnchorTracker mounted in AppSidebar
   + lastAnchorLocation on useChatStore) and substitute it when on /chat.

2. No global 'Multica is working' cue after ChatFab deletion. Subscribe the
   sidebar Chat entry to pendingChatTasksOptions and swap the unread dot for a
   spinner while any chat task is in flight.

3. ChatPage restore effect latched didRestoreRef before the sessions query
   resolved, so cold-start direct nav to /chat landed on the empty state even
   when the server had an active session. Wait for isSuccess before locking
   the ref.

* fix(chat): clear lastAnchorLocation on workspace rehydration

The pathname captured in workspace A would otherwise be reused against
workspace B's wsId, triggering a cross-workspace issue/project fetch
and silently leaking anchor context into chat messages.

---------

Co-authored-by: Lambda <f252c2c5-7d1d-4f3c-b394-a61abfe673fc@users.noreply.multica.ai>
2026-04-24 01:46:37 +08:00
Bohan Jiang
e0e91fc792 feat(daemon): harden agent mention-loop instructions (#1581)
* feat(daemon): harden agent mention-loop instructions

Two agents that mention each other via `mention://agent/<id>` can fall into
an infinite reply loop — each says "I'm done" in prose but keeps
`@mentioning` the other, which re-enqueues their run. Adding hard caps on
agent-to-agent turns conflicts with Multica's design principle of giving
agents the same authorship freedom as humans, so this change hardens the
instructions that the harness injects instead.

- Replace the terse "mentions are actions" blurb with a full Mentions
  protocol: `side-effecting` warning, explicit "when NOT to mention"
  (replying to another agent, sign-offs, thanks) and "when a mention IS
  appropriate" (human escalation, first-time delegation, user asked).
- Add a pre-workflow decision step for comment-triggered runs: decide
  whether a reply is warranted at all, decide whether to include any
  `@mention`, and clarify that the post-a-comment rule is mandatory *if*
  you reply — silence is a valid exit for agent-to-agent threads.
- Thread the triggering comment's author kind + display name
  (`TriggerAuthorType` / `TriggerAuthorName`) from the claim endpoint
  through the daemon task type, per-turn prompt, and CLAUDE.md workflow.
  When the author is another agent, both surfaces now name that agent
  and warn against sign-off mentions.
- Soften the old closing line that told agents to `always` use the
  mention format — the word generalized to member/agent mentions and
  encouraged the very behavior that causes loops.

Refs GH#1576, MUL-1323.

* fix(daemon): remove MUST-respond conflict and sanitize trigger author name

Addresses two blocking points on PR #1581:

1. buildCommentPrompt told the agent "You MUST respond to THIS comment"
   and unconditionally appended the reply command — directly conflicting
   with the new agent-to-agent silence-as-valid-exit workflow. Models
   were likely to keep following the older must-reply rule and fall back
   into the loop this PR is trying to close.

   Rewrite the header as "Focus on THIS comment — do not confuse it
   with previous ones" (keeps the anti-stale-comment signal) and change
   BuildCommentReplyInstructions to open with "If you decide to reply,
   post it by running exactly this command" so the reply command is
   available but conditional across both prompt surfaces.

2. Raw agent/user display names were being embedded directly into the
   high-priority prompt and CLAUDE.md via TriggerAuthorName. Agent and
   member names are only validated as non-empty at write time, so a
   name containing newlines, backticks, or fake mention markup would
   turn the field into a cross-agent prompt-injection surface.

   Add execenv.SanitizePromptField — strip control runes, collapse
   whitespace, drop markdown structural characters (backtick, asterisk,
   brackets, pipe, angle brackets, hash, backslash), truncate to 64
   runes — and apply it at both embed sites (per-turn prompt and
   CLAUDE.md). Defense-in-depth at the consumption layer so this works
   for already-stored names without a migration.

Tests: TestSanitizePromptField covers the policy; TestBuildPromptSanitizesAgentName
plants an attack payload in TriggerAuthorName and checks the rendered prompt
does not leak the newline-anchored injection or the fake mention markup.
TestBuildPromptCommentTriggered*{,ByMember} updated to lock in the
conditional reply-command framing.

* refactor(daemon): trim redundant CLAUDE.md preamble and drop name sanitizer

Per PR #1581 feedback:

1. Remove the `if ctx.TriggerAuthorType == "agent"` preamble block in
   runtime_config.go. It duplicated what workflow steps 4 and 5 already
   say ("Decide whether a reply is warranted", "Never @mention the
   agent you are replying to as a thank-you or sign-off"), so the
   signal lands the same without the extra ~7 lines of CLAUDE.md. The
   per-turn prompt preamble in prompt.go stays — that surface has no
   numbered workflow below it and would otherwise lose the
   silence-as-exit signal.

2. Delete execenv.SanitizePromptField + its test. Workspace agents are
   created by trusted team members, so the cross-agent name-injection
   surface it defended isn't realistic in the current trust model.

3. Drop TriggerAuthorType/Name from execenv.TaskContextForEnv and stop
   populating them in daemon.go — they're no longer read by the
   execenv package. The same fields on daemon.Task stay because
   prompt.go still needs them to label the triggering author in the
   per-turn prompt.

Tests simplified to match the leaner shape: CLAUDE.md regression
guards now assert that the anti-loop phrases live in the numbered
workflow, and the sanitizer-specific tests are removed.
2026-04-24 01:39:12 +08:00
Jiayuan Zhang
977b0c0558 feat(agents): show profile card on agent avatar hover (#1577)
* feat(agents): show profile card on agent avatar hover

Hovering an agent avatar now opens a preview card with name, status,
runtime mode + connectivity, model, skills, and owner. Wired through
the shared ActorAvatar wrapper so every render site gets it; opt-out
via disableHoverCard in pickers and the agent's own detail header
where the card would be redundant or interfere with click selection.

* fix(agents): keyboard-focusable hover card + opt out on settings avatar

- Make the agent profile-card hover trigger focusable (tabIndex=0 with
  visible focus ring), so keyboard users can open the card. Drops
  cursor-default so the trigger inherits the parent control's cursor
  instead of fighting it.
- Disable the hover card on the agent settings avatar — it's a
  click-to-upload target on the agent's own settings page, where the
  card would be redundant and the trigger conflicted with the upload
  affordance.

* fix(agents): scope hover-card tab stop to standalone avatars only

Detect a focusable ancestor (link/button/role=button/tabindex>=0) at
mount and only flip the agent profile-card trigger to tabIndex=0 when
none exists. Avatars rendered inside an existing focusable parent (issue
list rows wrapped in AppLink, button-style cards, etc.) keep the trigger
unfocusable so they don't add redundant nested tab stops or bloat
keyboard navigation. Standalone avatars (e.g. comment author, issue
detail meta) remain keyboard-accessible with a focus-visible ring.

---------

Co-authored-by: Lambda <f252c2c5-7d1d-4f3c-b394-a61abfe673fc@users.noreply.multica.ai>
2026-04-24 00:53:55 +08:00
Black
17136742b9 fix(runtimes): fix dark mode chart visibility and invalid CSS color syntax (#1573)
All chart components used `hsl(var(--chart-X))` but `--chart-X` holds a
full oklch value, not bare HSL components — making the expression invalid
CSS. Browsers silently fell back to black, so bars/areas/heatmap cells were
invisible against the dark background.

- Replace `hsl(var(--chart-X))` with `var(--color-chart-X)` across all
  runtime chart components and the landing feature section
- Fix heatmap opacity using `color-mix(in oklch, ...)` instead of the
  invalid `hsl(var(--chart-3) / 0.3)` syntax; switch to foreground color
  so cells blend with the neutral theme in both light and dark mode
- Raise dark-mode chart-2 through chart-5 lightness values so they
  contrast clearly against the dark background

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 00:47:41 +08:00
Jiayuan Zhang
5e51f5b356 feat(desktop): add right-click context menu with clipboard actions (#1575)
Co-authored-by: Lambda <f252c2c5-7d1d-4f3c-b394-a61abfe673fc@users.noreply.multica.ai>
2026-04-24 00:11:16 +08:00
Jiayuan Zhang
13daede63e docs: remove Star History chart from README (#1574)
Co-authored-by: Lambda <f252c2c5-7d1d-4f3c-b394-a61abfe673fc@users.noreply.multica.ai>
2026-04-24 00:09:09 +08:00
Bohan Jiang
6107211a6e docs(selfhost): correct WebSocket guidance for LAN access (#1567)
The previous note claimed the frontend's auto-derived WebSocket URL
worked on LAN without extra configuration. It does not: Next.js
`rewrites()` only proxy HTTP requests, so the `Upgrade` handshake
required for WebSocket never reaches the Go backend, and real-time
features (chat streaming, live issue updates, notifications) silently
fail when accessing the app via a non-localhost host.

Replace the incorrect sentence with a dedicated subsection that points
users at the reverse-proxy recipe (already documented above, includes
the correct /ws Upgrade headers) and, for setups without a proxy,
documents the build-time NEXT_PUBLIC_WS_URL + selfhost.build.yml
override path.

Refs: GH #1522
2026-04-23 18:25:02 +08:00