12 Commits
Author SHA1 Message Date
e8fdcfd226 MUL-6623 feat(agent): add host-local Devin CLI over ACP (#7485)
* feat(agent): add host-local Devin CLI over ACP

Spawn `devin acp` via the shared ACP client. Local CLI only — not
cloud Devin VMs/Playbooks/Secrets. Model via `devin acp --model`.
Catalog from `devin models list --format json` (families/variants).

* fix: skip Devin ACP handshake when browser auth is also offered

* fix: close dim case string so Devin compiles

Missing quote after dim broke runtime_config.go and every Go CI job.

* fix: stop runtimeConfigPath recursion on Devin

Devin is a builtin whose ProtocolFamily is itself. Recursing on that
overflowed the stack and killed backend-tests.

* test(agent): cover Devin model failures and final-answer delivery

Co-authored-by: multica-agent <[email protected]>

* fix(agent): honor Devin model aliases and stored login (MUL-6623)

Co-authored-by: multica-agent <[email protected]>

* fix(agent): drain Kiro panic details before terminating runtime (MUL-6623)

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-26 15:11:05 +08:00
88f6d6272b MUL-8057: feat(agent): run Meta Muse Code as an MSP runtime (#9309)
* feat(agent): run Meta Muse Code as an MSP runtime

Muse Code speaks MSP (Muse Session Protocol) over `muse serve`: JSON-RPC
2.0 on stdio, like ACP, but with its own method vocabulary. A session opens
with session/start or session/resume, work is submitted with turn/start, and
assistant text, reasoning and tool calls arrive as transcript items.

The adapter maps those items onto the Backend contract, reads the terminal
turn outcome, and reports a notFound resume as ResumeRejected so the daemon
retries on a fresh session instead of losing continuity silently.

Approval is a session property, not a launch flag: `muse serve` has no
--yolo, and the CLI documents --yolo as TUI-only. A fresh session is created
with approvalMode=allowAll and a resumed session re-asserts it with
session/setApprovalMode, which is what lets a claimed task run without
waiting for a human. The container and per-task worktree remain the
isolation boundary, as they are for every other backend.

commandId must be UUIDv7 — Muse derives a turn id from it and rejects
anything else — so the adapter mints time-ordered ids rather than v4.

The model catalog comes from the session-free model/list query and the pick
is applied through session/start modelId.

* feat(runtime-profiles): allow muse as a protocol family

A custom runtime profile may only be based on a protocol family the daemon
supports, and the database enforces the same list. Widening both keeps the
create flow, the server validation and the CHECK constraint in lockstep with
agent.SupportedTypes.

* fix(muse): complete runtime integration and lifecycle handling (MUL-8057)

Co-authored-by: multica-agent <[email protected]>

* docs(muse): document runtime setup and support (MUL-8057)

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: yushen <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-19 13:45:42 +08:00
mikyan fe59262188 feat(agent): add Huawei Cloud CodeArts support (#6985)
* feat(agent): add Huawei Cloud CodeArts support

* fix(migrations): resolve CodeArts prefix collision

* fix(agent): address CodeArts review feedback

* fix(agent): tighten CodeArts integration scope

* fix(agent): resolve CodeArts follow-up review blockers

* fix(agent): redact CodeArts launch arguments

* docs: sync supported runtime counts

* fix(agent): own CodeArts model probe process tree
2026-08-31 12:30:59 +08:00
8a5a6adbf7 feat(agent): add ZeroClaw as a native ACP runtime (MUL-6511) (#7351)
* feat(agent): add ZeroClaw as a generic ACP agent runtime

Add ZeroClaw (a Rust-based, single-binary generic agent CLI) as a
first-party runtime, driven over the standard ACP JSON-RPC transport via
`zeroclaw acp`, mirroring the Dim/QwenPaw/Grok ACP backend shape. Only the
binary, launch args, and blocked-flags policy are ZeroClaw-specific; the
backend reuses the shared hermesClient ACP transport and deliverable
tracker.

No provider-specific quirks (permission-preset injection, minimum-version
gate, or a separate authenticate step) are assumed without evidence — those
were verified against real Dim/Grok binaries and would need the same
hands-on verification against a real ZeroClaw binary before being added
here.

Backend: SupportedTypes/New/launchHeaders wiring, model discovery via
session/new's advertised catalog, daemon probe with
MULTICA_ZEROCLAW_PATH/MODEL overrides, AGENTS.md context delivery,
default-command-name registration, metrics label, and a migration widening
the runtime_profile.protocol_family whitelist.

Frontend: provider union, MCP-config support flag, and a placeholder
provider-logo mark (no official ZeroClaw brand asset exists yet).

Tests mirror the existing ACP backend conventions: fresh-session,
resume-via-session/load, resume-not-found, transient-load-error, blocked
launch args, timeout, and the shared ACP deliverable-boundary regression
suite.

* fix(agent): renumber ZeroClaw's migration to 378 to avoid a same-day collision

Both this branch and a concurrently-implemented Junie runtime branch
independently picked migration 377 off the same origin/main tip.
Renumbering ZeroClaw to 378 up front avoids opening two PRs from the
same author that claim the same migration number.

* fix(agent): align the ZeroClaw ACP backend with the real 0.8.4 runtime

The backend was written against an assumed vanilla ACP handshake and never
checked against a ZeroClaw binary. Driving a real zeroclaw 0.8.4 over stdio
shows five of those assumptions are wrong, and the test fake encoded the same
assumptions, so the suite stayed green against a contract the runtime does not
have.

- Drop the session/set_model request. It is not in ZeroClaw's dispatch table
  (-32601) and no ACP handler reads a model param at all, so it could never
  succeed; because its failure was fatal, any agent with a model configured
  failed before session/prompt was reached. The model comes from the ZeroClaw
  agent profile, so opt zeroclaw out of ModelSelectionSupported and stop
  reading MULTICA_ZEROCLAW_MODEL rather than leaving the UI offering an
  override that is silently ignored.

- Label token usage from initialize's _meta.zeroclaw.defaultModel, the only
  model identity the ACP surface exposes. session/new returns just
  {sessionId, workspaceDir}, so the two extractACPCurrentModelID reads were
  dead code and the model-catalog discovery they fed could only ever return an
  empty fallback while paying an ACP subprocess per catalog read. Return an
  empty catalog directly, as qwenpaw/mcode do.

- Resume through session/resume instead of session/load. Both restore the
  transcript into the agent, but load also replays every retained message back
  as session/update notifications, which fed the previous answer into this
  turn's deliverable. Add the acceptNotification turn gate every other ACP
  backend carries, so out-of-turn chunks are dropped even if a runtime flushes
  them early (#1997).

- Send agentAlias on session/new when the operator names one. ZeroClaw
  auto-selects an agent only when its config holds exactly one entry, so
  multi-agent installs failed with -32602 and had no way out: `zeroclaw acp`
  has no --agent flag, and passing one kills the process at argument parsing.
  The alias is consumed from custom_args and travels as a param; it is omitted
  when unset, because a hardcoded guess breaks the single-agent case that
  works today. The refusal now names both remedies.

- Stop advertising MCP support. No ZeroClaw handler reads params.mcpServers;
  acp_enable_mcp gates that agent's own mcp_bundles, not the client list, so
  MCP is operator-side only. Hide the MCP tab and warn on a stale mcp_config
  instead of failing the task.

Also teach isACPSessionNotFound about ZeroClaw's custom -32000
SESSION_NOT_FOUND, without which a stale session id was retried forever
instead of restarting fresh. The wording check still decides, so a transient
error under the same code stays non-rejecting.

Tests now encode the observed handshake — _meta default model, session/new
without a catalog, -32000 for an unknown session, -32601 for set_model — and
cover the replayed-history, alias and MCP paths.

* fix(agent): make ZeroClaw integration merge-safe

Co-authored-by: multica-agent <[email protected]>

* fix(agent): tighten ZeroClaw ACP fallbacks

Co-authored-by: multica-agent <[email protected]>

* fix(agent): preserve ZeroClaw retry semantics

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-24 14:39:08 +08:00
e1be1625cb MUL-5959: feat(agent): add Dim (DimCode) ACP runtime (#6675)
* feat(agent): add Dim (DimCode) ACP runtime

Add Dim (dimcode, the `dim` CLI) as a first-party agent runtime, driven
over the ACP (Agent Client Protocol) transport via `dim acp`.

Key integration points:
- New dim backend (pkg/agent/dim.go): spawns `dim acp`, performs the
  ACP initialize/session/new handshake, and raises the runtime's
  hardcoded read-only permission preset to full-access (plus agent mode)
  via session/set_config_option before the first prompt — without it every
  file write is denied by a capability rule. Model override uses
  session/set_model; the model catalog is read from session/new.
- Session resume is intentionally skipped: Dim binds sessions to the
  creating process, so a later process's session/load is rejected with
  "held by another process" even after a clean session/close. The backend
  always starts a fresh session and reports ResumeRejected so the daemon
  classifies the run correctly. A best-effort session/close keeps Dim's
  own session list free of orphaned entries.
- Registration: SupportedTypes whitelist, New() factory, launch header,
  daemon probe (MULTICA_DIM_PATH / MULTICA_DIM_MODEL), default agent
  command name, protocol_family CHECK migration (255), model discovery,
  MCP config support, metrics label, and runtime display.
- Docs/UI: provider logo, landing i18n (en/zh/ja/ko), providers and
  install-agent-runtime docs, README, CLI_AND_DAEMON, SELF_HOSTING.
- Tests: dim backend unit tests (fresh session + resume behavior), shared
  ACP deliverable case, logo and MCP-support tests.

* feat(views): use official DimCode app icon for Dim provider logo

Replace the placeholder inline SVG with the official DimCode desktop
client app icon (from /opt/DimAgent/resources/build/icon.png, resized
to 128px), imported as a static asset the same way the Qwen Code mark
is handled.

* test(agent): add real dim ACP smoke tests (agentintegration)

Two gated tests behind MULTICA_RUN_REAL_AGENT_SMOKE=1:

- TestDimRealACPSmoke: full end-to-end against real `dim acp` —
  initialize, session/new, set_config_option (permission/mode), prompt,
  and assert completed output.
- TestDimRealResumeRejected: pass a fake ResumeSessionID, assert the
  backend starts a fresh session (no session/load), and reports
  ResumeRejected=true so the daemon classifies the run correctly.

Verified against dimcode 0.3.2 and 0.3.8.

* fix: renumber dim migration 265 → 271 (upstream added 265-270)

* fix: renumber dim migration to 272 (upstream added 271)

* fix(agent): dim cross-run resume via session/load + review fixes

Address review feedback on the Dim ACP runtime:

- #1 Deliver runtime brief to Dim: add dim to the AGENTS.md provider
  list (verified dim 0.3.8+ reads AGENTS.md from the session cwd).
- #2 Cross-run session continuity: dim 0.3.10+ releases its per-process
  session lock ~5s after the owning process exits, so resume now goes
  through the standard ACP session/load (same as traecli/kiro/grok)
  instead of always starting fresh. A loaded session retains its
  permission/mode, so set_config_option runs only on fresh sessions.
  Add a two-run regression (TestDimRealCrossRunResume) proving run B
  recalls context established only in run A.
- #3 Process lifecycle races: the deferred cleanup now cancels the run
  context before cmd.Wait (a child ignoring stdin EOF no longer hangs
  Result delivery) with a bounded force-kill fallback; the success path
  waits for the final prompt notification with a grace window instead
  of a non-blocking read that could miss the last message.
- #4 Isolate model discovery by executable path (discoveryCacheKey).
- #6 Real smoke test now writes a sentinel file to prove full-access is
  effective; a best-effort session/close is sent before teardown.
- #7 gofmt.

* test(agent): add dim process-lifecycle regressions (review #3)

- TestDimCleanupKillsHangingChild: a child that ignores stdin EOF and
  SIGTERM after an early failure is force-killed within
  dimProcessWaitTimeout, so Result still closes.
- TestDimPromptMissingNotificationStillCompletes: when session/prompt
  returns without stopReason (onPromptDone never fires), the bounded
  final-notification wait falls through and Result completes instead of
  hanging.

* fix(migrations): renumber dim runtime_profile migration 272 → 273 (upstream added 272)

* fix(agent): review round 2 — process tree, quiescence, fail-closed, version check

Address all remaining blockers from the second review:

- #1 Require dim >= 0.3.10 (version check at initialize); bounded retry
  on session/load when the lock is not yet released instead of silently
  starting fresh; integration test no longer sleeps before resume.
- #2 Process-tree cleanup: configureProcessGroup + signalProcessGroup +
  waitProcessGroupGone; cmd.Cancel returns nil so we own all signalling;
  regression asserts the descendant PID is gone.
- #3 Notification quiescence: onActivity + waitForACPNotificationQuiescence
  (same as hermes); Dim equivalent of the late-final-notification test.
- #4 Permission fail-closed: set_config_option runs on BOTH fresh and
  resumed sessions; on config failure, session/close is sent before
  returning so a partially configured session is not resumed; regression.
- #5 Migration renumbered 273 → 274 (upstream added 273).
- #6 AGENTS.md mapping regression, two-executable cache isolation test,
  session/close unit assertion, smoke test now executes a command.
- #7 README.md + CLI_AND_DAEMON.md updated with 22 CLIs including Dim.

* chore: remove temporary review actions doc (not for PR)

* fix(migrations): renumber dim migration 274 → 310 (upstream added 274-309)

* review: fix retry break scope, dedup isACPSessionNotFound comment, update stale notes

* fix(agent): pass *exec.Cmd to signalProcessGroup/waitProcessGroupGone (upstream signature change)

* fix(agent): close session on set_model failure + resume regression test

Audit-found gaps from self-review:
- set_model failure now sends session/close before returning (same as
  set_config_option failure) — reviewer #4 said 'permission, mode, or
  model'.
- Add TestDimConfigFailThenResumeReestablishes: run A config fails →
  session/close sent → run B resumes and re-applies set_config_option
  (fail-closed), completing successfully.
- Fix two stale comments that contradicted the code (config block now
  runs on both fresh and resumed sessions).

* fix(agent): add WaitDelay, use labeled break in retry loop

Self-audit improvements:
- Add cmd.WaitDelay=10s for consistency with claude.go (hard backstop
  if a process somehow survives SIGKILL).
- Use labeled break (break loadRetry) so runCtx cancellation during the
  retry delay exits the for loop directly, not just the select.
- Update PR description: migration 273→310, set_config_option now
  re-applied on both fresh and resumed sessions.

* fix(migrations): renumber dim migration 310 → 313 (upstream added 310-312)

* fix(migrations): remove stale 313 dim migration (replaced by 314 after upstream added dsh at 313)

* fix: DSH compatibility + thinking levels + reviewer round 3

Systematic fix of all 39 items from the compatibility gap analysis:

Rebase + migration:
- Rebase to latest upstream/main (resolves all DSH conflicts)
- Migration 314_runtime_profile_add_dim (whitelist includes both dsh + dim)
- Remove stale 313 dim migration

DSH + Dim coexistence (provider lists, counts, docs):
- SupportedTypes, config.go, metrics labels: both dsh + dim
- i18n (4 files): count 23, lists include DSH + Dim + Oh-My-Pi
- README.md/README.zh.md: count 23
- CLI_AND_DAEMON.md: 23-row table
- environment-variables.mdx (4 langs): MULTICA_DIM_PATH/MODEL callout
- display.ts, mcp-support.ts, types/agent.ts, provider-logo.tsx: both dsh+dim

Thinking levels (was MISSING — dim supports thought_level):
- dim.go: call applyACPEffortOption after set_model
- thinking.go: add dim to acpCatalogThinkingProviders
- dim.go: retain sessionResult for effort option

Reviewer round 3:
- Version check fail-closed: empty/malformed → reject (not allow)
- Cache test through ListModels call site with fake executables
- set_model failure regression test
- Test companions: sidecar_manifest_test, runtime_config_test add dim

Code quality:
- agent.go: fix unreachable duplicate return (rebase artifact)
- dim.go: labeled break in retry loop, WaitDelay, session/close on set_model fail

* fix: deliverable test fake version + provider-logo rebase artifact

* fix(migrations): renumber dim migration 314 → 315 (upstream added 314_workspace_mcp_config)

* fix(migrations): renumber dim migration 315 → 319 (upstream added 315-318)

* fix: Windows process tree, MinVersions, retry tests, migration 327

Address all 5 remaining blockers from review round 4:

1. Windows process-tree ownership: use startOwnedProcessTree instead of
   cmd.Start(), releaseProcessGroup in cleanup defer. This attaches the
   child to a Job Object on Windows (no-op on Unix), ensuring descendants
   are captured and terminated.

2. Migration renumbered 319 → 327 (upstream added 319-326).

3. Add dim: 0.3.10 to MinVersions in version.go so the daemon registers
   old Dim binaries as offline and refuses triggers (defense in depth on
   top of the ACP agentInfo.version check in dim.go).

4. Session-lock retry contract tests: success after bounded retries,
   exhaustion without falling through to session/new. Fake script
   supports DIM_LOAD_HELD_N (held for N calls then succeed) and
   DIM_LOAD_HELD_ALWAYS (always held).

5. README.md:206 20→23 runtimes. PR description updated.

* test: add missing reviewer-requested regressions (round 4)

- Windows Job Object regression: dim_windows_test.go proves the Dim
  backend captures and terminates descendants via startOwnedProcessTree
  (Windows-only build tag; companion to TestStartOwnedProcessTree).
- MinVersions registration: add dim test cases to TestCheckMinVersion
  (0.3.10 ok, 0.3.9 rejected, invalid rejected).
- Retry cancellation: TestDimSessionLoadRetryCancelled cancels the
  context during the retry delay, asserts no session/new fallback.

* fix: sync upstream Command signature changes + restore dim additions

Upstream changed ListModels/discoverXxxModels/detectCLIVersion to accept
Command (struct with Path+Prefix) instead of string. The previous rebase
kept our old-signature versions, causing widespread compile failures.

Restored all affected files from upstream, then re-applied dim-specific
additions:
- agent.go: dim in SupportedTypes, New(), launchHeaders
- hermes.go: isACPHeldByProcess + -32002 in isACPSessionNotFound
- thinking.go: dim in acpCatalogThinkingProviders
- models.go: dim case in ListModels + discoverDimModels
- version.go: dim in MinVersions
- All dim test files preserved (dim_test.go, dim_integration_test.go,
  dim_windows_test.go, models_test.go dim cache test)

* fix: rebase to latest main, migration 327→341, resolve DetectVersion conflict

* fix: remove unused os/exec import in dim_windows_test.go

* fix(migrations): renumber dim migration 341 → 342 (upstream added 341)

* fix: use Command.exec instead of exec.CommandContext (upstream GH #7046)

Upstream's TestOnlyLaunchGoSpawnsRuntimeProcesses requires all backends
to build processes through Command.exec (launch.go), so a custom runtime's
fixed_args are carried into the subprocess. Dim was the only backend still
using exec.CommandContext directly.

* fix: launch prefix policy + remove empty test files + deterministic retry tests

Address all 4 findings from review round 5:

1. Add "dim": dimBlockedArgs to launchPrefixBlockedArgs so protocol-breaking
   flags (--help/--auth-setup/--remote) are filtered from fixed_args.
2. Remove 77 zero-byte *_test.go files accidentally added at repo root.
3. Make retry tests deterministic: TestDimSessionLoadRetryCancelled waits
   for the first session/load request before cancelling (no fixed sleep);
   TestDimSessionLoadRetryExhausted asserts exactly 4 load attempts.
4. PR description will be updated separately.

* fix: rebase to latest main, migration 342→343 (upstream added mcode at 342)

* chore: trigger CI

* chore: refresh PR

* fix: restore mcode that was lost during merge conflict resolution

Merge took 'ours' side which predated mcode. Restore mcode in:
- SupportedTypes, New() factory, launchHeaders
- agent_supported_types_test.go want map
- metrics/labels.go
- config.go defaultAgentCommandNames
- migration 343 up/down whitelists

* fix: restore mcode probe in agents_probe.go + agent-cli-command-names.txt

* fix: restore all remaining mcode references lost during merge

Files fixed:
- README.md: mcode row in runtimes table
- mcp-support.test.ts: mcode assertion
- config.go: mcode in Agents comment + error message
- runtime_config.go: mcode in AGENTS.md case
- version.go: mcode in MinVersions
- version_test.go: mcode test cases

Verified: no file has fewer mcode references than upstream.

* fix: restore mcode in environment-variables docs + CLI_AND_DAEMON ACP list

- environment-variables.{mdx,ja,ko,zh}: restored 'MiniMax Code' in the
  QwenPaw model-variable sentence
- CLI_AND_DAEMON.md: added MiniMax Code to ACP-family list + loadSession
  fallback description

Verified: every changed file now has >= mcode references vs upstream.

* fix(migrations): renumber dim migration 343 → 344 (upstream added 343)

* fix(migrations): renumber dim migration 344 → 348 (upstream added 344-347 plugin migrations)

* fix(migrations): update down migration comment 344 → 348

* chore: retrigger CI (flaky TestOpenclawDiscoveryCacheConcurrentPreparations — upstream test, passes locally x5)

* fix(migrations): renumber dim migration 348 → 352 (upstream added 348-351)

* fix: Dim launch-prefix regression test + version.go comment repair (review #6)

- Add TestDimLaunchPrefixFiltersBlockedFlags: proves allowed prefix
  reaches command before acp, and --help/--auth-setup/--remote/-h are
  stripped from Dim's launch prefix.
- Add dim to TestLaunchPrefixReachesACPFamilies family list.
- Repair version.go: dim's cross-run session/load comment was appended
  to mcode entry during a rebase conflict; restore two independent
  accurate comments.

* fix(migrations): update down migration comment 348 → 352

* chore: retrigger CI (backend-tests stuck 50+ min)

* fix(migrations): renumber dim migration 352 → 362 (upstream added 352-361)

* fix: use logAgentCommand for dim argv logging (upstream redaction requirement)

Upstream's TestOnlyLaunchGoLogsAgentCommandArgs enforces that all
runtime argv logging goes through Config.logAgentCommand so sensitive
args are redacted. dim.go was still using Logger.Info directly.

* fix(migrations): renumber dim migration to 370

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-20 12:54:46 +08:00
de902a4aab MUL-6273: feat(agent): add MiniMax Code ACP runtime (#7035)
* feat(agent): add MiniMax Code ACP runtime

* fix(agent): terminate mcode process trees

* fix(agent): address mcode review blockers

* test(agent): keep mcode model contract API-stable

* fix(migrations): resolve mcode prefix collision

* fix(agent): launch mcode through runtime command

Assisted-by: MiniMax Code

* fix(migrations): renumber mcode migration

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-18 12:44:27 +08:00
Jiayuan Zhang 4f10a944b1 feat(runtime): add DeepSeek Harness support (#6923)
* feat(runtime): add DeepSeek Harness support

* docs(runtime): reflect public DSH release
2026-08-13 23:18:17 +08:00
feigandBohan-J 3dd128532c feat(agent): add oh-my-pi (omp) as a supported agent runtime (#6514)
* feat(agent): add oh-my-pi (omp) as a supported agent runtime

oh-my-pi (omp, https://omp.sh) is a separate CLI that is a drop-in fork
of pi and speaks the same JSON event protocol. It is registered as an
independent provider so a host with both pi and omp installed gets two
runtimes instead of one.

omp is a runtime identity on the pi protocol, NOT a new protocol family.
It is declared in a single BuiltinRuntimes descriptor
(server/pkg/agent/builtin_runtimes.go) that carries id, protocol_family,
default_command, env_prefix, display_name, skills_dir, user_skills_dir,
launch_header, backend overrides, and model_discovery strategy. Every
consumer (agents_probe.go, config.go, daemon.go, execenv/context.go,
local_skills.go, agent.go New/LaunchHeader, models.go ListModels) derives
from this descriptor — no per-file hardcoded omp special cases.

NewRuntime() is the typed entry point for runtime identities: it resolves
id → descriptor → protocol family backend → applies overrides through a
backendOverrideApplicator interface (piBackend implements it). New()
delegates to NewRuntime() for built-in runtime identities, keeping New()
meaning exactly one thing: the protocol-family factory.

Model discovery: parseOmpModels reads the real {"models":[...]} wrapper
with separate provider/id/selector/name fields. Model.ID is the selector
(provider/id), matching parsePiModels' convention so buildPiArgs emits
both --provider and --model — not just --model. A regression test
(TestOmpSelectorSurvivesToBuildPiArgs) pins this end-to-end.

Frontend: provider-logo.tsx maps omp to PiLogo. The transcript dialog uses
the shared providerDisplayName helper. Landing copy updated to 21 tools
in all 4 locales.

Tests (omp_test.go): descriptor dispatch, NewRuntime entry point, binary
resolution, omp-labeled errors, event-stream completion, pi+omp
side-by-side registration, selector→buildPiArgs regression, model parser
coverage (real JSON/empty wrapper/invalid stderr/duplicate), and a guard
that every descriptor field is non-empty and consumed.

Closes #3989

* fix(views): keep Claude Code label in the transcript run details

Switching the provider row to the shared runtime formatter renamed every
Claude run: the daemon has no display-name override for claude, so the
shared formatter answers "Claude", and the legacy claude-code value
title-cased into "Claude-code". Both read as "Claude Code" before.

Keep those two aliases in a table local to this view and defer everything
else to the shared formatter, so the row stays in lockstep with the
runtime list (#5260) without renaming Claude runs.

* test(daemon): assert pi and omp both reach the register payload

The existing omp test stopped at probeAgentCLIs, so nothing covered the
part a user actually sees: whether both runtimes are registered with the
server, each under its own display name. Discovery finding two entries and
New() building two backends both stop short of version detection and the
registration payload.

Add a daemon-level test that runs real discovery off a fake PATH, drives
syncWorkspacesFromAPI, and asserts the payload carries type=pi and
type=omp with names "Pi" and "Oh-My-Pi". Rename the discovery test to
say what it covers, and record the registered display name in the batch
fixture so the name can be asserted.

* docs(agent): correct descriptor comments that describe the old behaviour

Three doc comments still described behaviour the fail-closed rework
replaced:

- ModelDiscovery claimed a nil strategy falls back to the family's
  discovery; ListModels deliberately returns an empty catalog instead,
  because omp exits non-zero on pi's --list-models.
- backendOverrideApplicator claimed backends without it are returned
  unchanged; NewRuntime returns an error rather than dropping the
  descriptor's executable and label.
- ProtocolFamily credited New() with applying the ID-specific defaults;
  that moved to NewRuntime.

Also repair the truncated first sentence of the NewRuntime doc, and fix
discoverOmpModels still calling the output a JSON array when
parseOmpModels right below it documents the {"models":[...]} wrapper.

* style(server): gofmt the files this branch touched

Four files were left unformatted: the descriptor literal and the omp test
struct lost their key alignment, execenv/context.go had its new import out
of order, and local_skills.go kept the old indentation after its switch
moved inside an if/else. Whitespace only — no behaviour change.

---------

Co-authored-by: Bohan-J <[email protected]>
2026-08-10 16:34:07 +08:00
b98f760f1f MUL-5604: 支持 Reasonix runtime (#6370)
* feat(agent): add Reasonix runtime

Co-authored-by: multica-agent <[email protected]>

* fix(agent): make Reasonix sandbox host-adaptive

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-05 14:19:28 +08:00
6b5d26b235 feat: add QwenPaw ACP backend support (MUL-5355) (#5986)
* feat: add QwenPaw ACP backend support

Add qwenpaw as a supported agent backend. QwenPaw runs via `qwenpaw acp`
over stdio using the ACP (Agent Client Protocol) JSON-RPC 2.0, reusing the
hermesClient transport layer.

Changes:
- New backend: pkg/agent/qwenpaw.go (ACP stdio via hermesClient)
- Register in agent.SupportedTypes, New(), launchHeaders
- Daemon config: probe qwenpaw binary, QWENPAW_ARGS env var
- Display name 'QwenPaw', inline system prompt support
- Runtime config: AGENTS.md injection, .qwenpaw/skills/ discovery
- User-level skills via QWENPAW_HOME env var
- DB migration 224: add qwenpaw to protocol_family whitelist

* fix(ci): add qwenpaw to CLI guard names and rename migration 231

- Add 'qwenpaw' to scripts/agent-cli-command-names.txt (TestAgentCLIGuardCoversDefaultCommands)
- Rename migration to 231 to avoid collision with existing migrations (TestMigrationNumericPrefixesStayUniqueAfterLegacySet)

* fix: address Bohan-J review comments on qwenpaw backend (PR #5986)

Blocking fixes:
1. config.go: Add qwenpaw CLI probe so daemon discovers the binary
2. models.go: Add qwenpaw case returning empty list (same as qwen)
3. qwenpaw.go: Fix resume path — use session/load (QwenPaw implements
   load_session, not session/resume), use resolveResumedSessionID,
   only set resumeRejected on session-not-found errors, fail the run
   on set_model failure instead of silently continuing
4. daemon.go: Add qwenpaw→"QwenPaw" to runtimeDisplayNameOverrides
5. agent.go: Update error message to list qwenpaw

Non-blocking fixes:
6. qwenpaw_test.go: Add 10 tests covering session/new, session/load,
   session/load not-found, set_model failure, ListModels, blocked args,
   protocol verification (session/load not session/resume), timeout,
   usage tracking, and backend construction
7. sidecar_manifest_test.go: Add qwenpaw to allFileBasedProviders

* fix: address Bohan-J second-round review on qwenpaw backend (PR #5986)

Blocking fixes:
1. qwenpaw.go: Send qwenpaw.coding_project_dir inside _meta, not as
   top-level parameter — ACP Pydantic model ignores extra root-level
   keys (model_config has no extra='allow'), only merges field_meta
   into kwargs
2. qwenpaw.go: Handle set_model returning result:null (not RPC error)
   — real QwenPaw server swallows exceptions and returns None, which
   serialises as JSON null; json.RawMessage('null') is non-nil so
   bytes.Equal check is needed
3. context.go: Workspace skills are at <workDir>/skills, not
   <workDir>/.qwenpaw/skills (confirmed against QwenPaw v2.0.1)
4. local_skills.go: QwenPaw resolves global root from
   QWENPAW_WORKING_DIR -> COPAW_WORKING_DIR -> ~/.copaw -> ~/.qwenpaw,
   not from QWENPAW_HOME

Test additions:
5. TestQwenpawSetModelReturnsNull — regression test for result:null
6. TestQwenpawSessionLoadTransientError — transient error does not
   set ResumeRejected=true
7. TestQwenpawSessionNewSendsCodingProjectDir — verifies _meta format
8. TestQwenpawSessionLoadSendsCodingProjectDir — same for session/load
9. TestQwenpawTimeout — deterministic sync via signal file

All 15 Qwenpaw tests pass. Verified against real qwenpaw v2.0.1 binary
(end-to-end: prompt, session ID, resume, system prompt).

* fix: rebase against upstream/main and add per-task qwenpaw workspace/agent isolation

- Rebase add-qwenpaw-backend branch on upstream/main (88 commits ahead)
- Resolve conflict in config.go: keep refactored probe() with shell
  resolution fallback, which already includes qwenpaw probe
- Add --workspace and --agent CLI args to qwenpaw acp for per-task
  skill isolation and agent identity isolation
- Mark --workspace and --agent as blocked in qwenpawBlockedArgs so
  user custom_args cannot override them
- Add deriveQwenpawAgentID() to produce deterministic agent IDs
  from task's issue ID and agent ID
- Wire QwenpawWorkspace and QwenpawAgentID through ExecOptions
- Add prepareQwenpawWorkspace() in execenv to materialize bound
  skills into a per-task workspace directory

* fix: address Bohan-J third-round review on qwenpaw backend (PR #5986)

Three blocking issues resolved:

1. Agent ID registration — remove session/set_model and --agent entirely.
   The simpler route: QwenPaw model override is declared unsupported,
   eliminating the need for agent profile registration in QwenPaw config.

2. Skill revocation — prepareQwenpawWorkspace now does os.RemoveAll
   on the skill_pool dir and manifest before rebuilding, making it
   idempotent. A->empty (revoke all), A->B (replace), and A->A
   (repeated reuse) all work correctly. Added 3 new unit tests.

3. Skill root path — changed 'skills' to 'skill_pool' in both
   prepareQwenpawWorkspace and skillsDirPath to match QwenPaw's
   store.py get_workspace_skills_dir.

Also cleaned up: removed deriveQwenpawAgentID function and its test,
removed QwenpawAgentID from ExecOptions, removed --agent from
qwenpawBlockedArgs, removed session/set_model test cases.

* fix: add missing qwenpaw probe() call in agents_probe.go

The TestDefaultAgentCommandNamesCoversAllProbes test found only 17
probe() calls in agents_probe.go but defaultAgentCommandNames has 18
entries. The probe() call for qwenpaw was missing, causing the
backend CI test failure.

Adding the probe ensures GUI-launched daemons can resolve qwenpaw
via the login shell fallback, matching all other providers.

* fix: sync models.go with upstream/main (remove qwenpaw from ListModels)

* fix: CI failures — migrate prefix 235→236 + update test

- migration prefix 235 was reused by upstream 235_chat_message_quick_actions;
  renamed our qwenpaw migration from 235 to 236
- TestQwenpawListModels called len() on Catalog struct (compile error);
  fixed to expect error for unknown provider type

* fix: bump qwenpaw migration prefix 236->241 (upstream took 236)

* fix: qwenpaw local skill root — 'skills' → 'skill_pool' (MUL-5355)

Bohan-J third-round review blocker #3: local_skills.go still scanned
<QWENPAW_HOME>/skills but the QwenPaw shared skill pool is
<QWENPAW_HOME>/skill_pool (store.py get_workspace_skills_dir).

Verified no other qwenpaw paths in the codebase assume the wrong layout.

* fix: bump qwenpaw migration prefix 241->242 (upstream took 241)

lint test fails with: migration prefix 241 is reused by
[241_comment_parent_lookup_index 241_runtime_profile_add_qwenpaw].
Upstream added 241_comment_parent_lookup_index; bump our migration.

* feat: add QwenPaw integration test and version declaration (MUL-5355)

- New: server/pkg/agent/qwenpaw_integration_test.go with three
  agentintegration build-tagged tests:
    TestQwenpawRealACPSmoke — full end-to-end ACP smoke test with
    session/new → session/prompt and session/load resume validation.
    TestQwenpawRealWorkspaceSmoke — validates skill_pool workspace
    flag handling and per-task skill isolation.
    validateQwenpawVersion — attempt version detection via qwenpaw
    --version, pip show qwenpaw, and python import.

- Document QwenPaw v2.0.1 as the supported baseline version in
  qwenpaw.go package comment, noting the contract details:
    _meta qwenpaw.coding_project_dir for Coding Mode,
    session/set_model NOT supported,
    skill_pool workspace layout.

- This test suite is gated by MULTICA_RUN_REAL_AGENT_SMOKE=1 and
  requires qwenpaw on PATH, matching the pattern used by grok,
  cursor, and traeecli integration tests.

* fix: bump qwenpaw migration prefix 242->243 (upstream took 242 for qoderclicn)

Upstream added 242_runtime_profile_add_qoderclicn in the same rebase
window, colliding with our 242_runtime_profile_add_qwenpaw. The
migration lint test TestMigrationNumericPrefixesStayUniqueAfterLegacySet
catches duplicate prefixes after the legacy range.

Also add 'qoderclicn' to our migration's CHECK constraint so it doesn't
regress the whitelist added by upstream's 242.

* temp: stub out integration test to isolate CI failure

* fix: restore upstream probeAgentCLIs() call in config.go (rebase regression)

Rebase conflict resolution accidentally reverted upstream's MUL-5439
refactor (extracting probe logic to agents_probe.go) back to the old
inline probe block. This also dropped qoderclicn detection and
duplicated the probe logic already in agents_probe.go.

Restore the single 'agents := probeAgentCLIs()' call — qwenpaw is
already probed in agents_probe.go.

* fix: bump qwenpaw migration prefix 243->251 (upstream took 243-250)

Upstream added migrations 243-250 since our last rebase. Bump to 251,
the next available prefix.

* feat: add QwenPaw integration test (agentintegration build tag)

TestQwenpawRealACPSmoke drives the real qwenpaw acp binary end-to-end:
  - session/new + session/prompt produces 'pong'
  - session/load resume with ResumeSessionID works
  - --workspace flag is forwarded correctly

Gated by MULTICA_RUN_REAL_AGENT_SMOKE=1, matching grok/cursor pattern.
Validated against QwenPaw v2.0.1.

* fix: workspace skills dir 'skills' not 'skill_pool' + add skill loading integration test

Two fixes:

1. qwenpaw_workspace.go: write skills to <workspace>/skills/ instead of
   <workspace>/skill_pool/. QwenPaw's get_workspace_skills_dir() looks
   for workspace skills at <workspace>/skills/ (store.py:65-67), not
   skill_pool (which is the shared pool at WORKING_DIR/skill_pool).
   Verified against real qwenpaw acp — skills in skill_pool/ were never
   discovered.

2. Add TestQwenpawRealWorkspaceSkill integration test that proves a
   bound skill is actually loaded and effective: writes a skill that
   overrides the agent's response, sends an unrelated prompt, and
   asserts the skill's marker text appears in the output. This
   addresses R3 review feedback: 'please also add a test that exercises
   an actually-bound skill'.

All three integration tests pass against QwenPaw v2.0.1:
  - TestQwenpawRealACPSmoke (session/new + prompt + session/load resume)
  - TestQwenpawRealWorkspaceSkill (skill discovery + effectiveness)

* feat: add ACP model discovery for qwenpaw via session/new models field

QwenPaw v2.0.1+ now includes a 'models' field (SessionModelState) in
the session/new response, added by agentscope-ai/QwenPaw#6531. This
lets ACP clients discover available models without session/set_model.

- ListModels for qwenpaw now uses discoverACPModels (same pattern as
  traecli/grok/kiro) to spin up 'qwenpaw acp', call session/new, and
  parse the models catalog from the response.
- discoverQwenpawModels mirrors discoverTraecliModels — ACP-native,
  no auth selection needed.
- Model override via session/set_model remains unsupported: it
  persists to agent.json at the agent scope (not session-scoped),
  so calling it would mutate the user's shared agent config. The
  model picker shows available models for display/selection, but
  the daemon does not send set_model.
- Updated TestQwenpawListModels to verify qwenpaw is a recognized
  type (not 'unknown agent type' error).

* fix: address Bohan-J Review 5 — ModelSelectionSupported=false, version bump to v2.1.0-beta.1, remove debug files

- ModelSelectionSupported('qwenpaw') now returns false with rationale
  (session/set_model persists to agent scope, not session scope)
- Add TestQwenpawModelSelectionUnsupported regression test
- Update version references from v2.0.1 to v2.1.0-beta.1 (includes
  agentscope-ai/QwenPaw#6531 — models field in session/new response)
- Remove check_ci.py, jobs.json, runs.json debug artifacts

* fix: bump qwenpaw migration prefix 251->253 (upstream took 251)

Upstream added 251_agent_runtime_unbind. Bump to 253, the next
available prefix after 252_agent_builder_draft.

* fix: address Bohan-J Review 6 — drop unused discovery, always attribute to unknown

- ListModels for qwenpaw returns empty catalog without spawning ACP
  subprocess (model selection is unsupported, so no consumer exists)
- Usage attribution always uses 'unknown' instead of opts.Model
  (the backend never sends opts.Model to QwenPaw)
- Add TestQwenpawUsageModelIgnored regression test
- Fix stale v2.0.1 comment in TestQwenpawListModels

* chore(agent): clean up qwenpaw model-discovery leftovers

Follow-up nits from review 7 on PR #5986:

- Drop discoverQwenpawModels: it lost its only caller when ListModels
  started returning an empty catalog for qwenpaw.
- Correct the version contract in qwenpaw.go. The execution path needs
  only the ACP surface present in v2.0.1 (current stable); the models
  field on session/new landed in v2.1.0-beta.1 but has no consumer now
  that model selection is unsupported.
- Make TestQwenpawListModels actually guard the no-subprocess promise.
  It pointed at a nonexistent path, which the old discovery helper also
  answered with an empty catalog, so it passed either way. It now uses
  an executable fake that records invocation; verified it fails if a
  discovery path is reintroduced.
- gofmt agent.go (ExecOptions alignment broke when QwenpawWorkspace was
  added) and restore the trailing newline in qwenpaw_test.go.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: niudakok <[email protected]>
Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-04 18:47:10 +08:00
e4b6f7a31b MUL-5581: add Qoder CN CLI runtime (#6232)
* feat(agent): add Qoder CN CLI runtime

Co-authored-by: multica-agent <[email protected]>

* fix(agent): address Qoder CN review nits

Co-authored-by: multica-agent <[email protected]>

* fix(agent): defer Qoder CN version gate

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-07-31 16:01:53 +08:00
YYClaw 36533bbc2b fix(test): prevent agent CLI execution in default tests (#5789) 2026-07-23 01:59:06 +08:00