Commit Graph
377 Commits
Author SHA1 Message Date
Jiayuan Zhangandmultica-agent 231dedb552 MUL-8034: chore: clean historical comments across the codebase (#9274)
* chore: clean historical comments (MUL-8034)

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

* docs: preserve pending Composio expiry contract

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

---------

Co-authored-by: multica-agent <[email protected]>
2026-10-15 21:54:33 +08:00
74ff8aabd7 MUL-7983: fix(agent): handle CodeBuddy startup errors without aborting active runs (#9214)
* fix(agent): fail codebuddy runs that emit a top-level error event

CodeBuddy's startup-failure shape is one top-level {"type":"error"}
line followed by a process that stays alive and silent: with an
inherited SERVER__PORT (a user-scope config shared by several tools),
a second concurrent run of the same agent fails to listen, prints
{"type":"error","error":"listen EADDRINUSE: ..."} and never emits
anything again. The parser had no branch for that event type, so the
run sat at "waiting for agent response" until the idle watchdog
stopped it - 19 of 48 runs hung this way on the reporter's machine.

- type=error is now fatal when no terminal result has been seen: the
  CLI's own message becomes the run error and the hung process is torn
  down immediately instead of idling. A result already seen keeps
  governing the outcome, so a late informational error line cannot
  flip a finished run.
- buildCodebuddyEnv now also forces SERVER__PORT=0 (OS-assigned port
  per run, so concurrent runs never share one) and
  CODEBUDDY_BASH_AUTO_BACKGROUND_DISABLED=1 (CodeBuddy's PowerShell
  tool moves timed-out commands to the background and only honors that
  variable). Both are appended last like the existing background-task
  disable so a caller-provided value cannot outvote them.

Fixes #9200

Generated-by: GLM-5.3-Flash (ZCode)

* fix(agent): keep CodeBuddy running after recoverable errors

MUL-7983

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

---------

Co-authored-by: Yushen <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-13 13:54:24 +08:00
Multica Eveandmultica-agent b115ec80f1 MUL-7851: fix(daemon): don't count host sleep as idle-watchdog silence (#9201)
* fix(daemon): don't count host sleep as agent silence in idle watchdog (#9024)

The idle watchdog stamped activity in wall-clock unix nanos, so a laptop
that slept past the idle window woke up with every in-flight run already
over budget and force-stopped them all on the first tick.

Detect host suspend from the wall-clock gap between watchdog ticks and
credit that time back to lastActivityAt before each check, clamped to now.
Works the same on macOS/Linux (monotonic clock pauses in sleep) and
Windows (monotonic clock keeps counting), and covers the tool-in-flight
budget too since both read the same stamp.

MUL-7851

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

* fix(daemon): measure idle watchdog on an awake-only clock (#9024)

Review on #9201 showed the tick-gap heuristic still charges sleep to the
agent: a sleep shorter than one tick is never credited, and when the
overdue tick fires on wake (Windows) only gap−tick is credited.

Replace it with pkg/awakeclock, a clock that only advances while the host
is awake: Go's monotonic clock on Linux/macOS (already stops in suspend),
minus host sleep on Windows (GetTickCount64 − QueryUnbiasedInterruptTime).
lastActivityAt, the drain loop stamp, and Cursor's native tool activity
(Session.ToolActivity now returns an awakeclock reading) all share it.

Regression tests cover both review boundaries plus a long sleep, each with
a wall-clock control that proves the scenario reproduces the bug.

MUL-7851

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

---------

Co-authored-by: multica-agent <[email protected]>
2026-10-12 17:19:58 +08:00
8a2f518d54 fix(agent): MUL-7781 / MUL-7793 interrupt Codex turns after repeated Guardian 429s (#8934)
* fix(agent): stop Codex turns after repeated Guardian 429s

* fix(agent): recognize Codex Guardian rate-limit events

Use the app-server wire method and preserve semantic activity. Distinguish reviewer failures from policy denials, deduplicate recent reviews, and explain manual recovery after interruption.

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

---------

Co-authored-by: Yin <[email protected]>
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-12 14:18:02 +08:00
92d0b6666d MUL-7812: fix(agent): deliver MCP servers to Pi tasks (#9063)
* fix(agent): deliver MCP servers to Pi tasks

Pi skipped untrusted project config in JSON mode, and Multica never wrote .pi/mcp.json, so composed MCP servers never reached the runtime.

Co-authored-by: Cursor <[email protected]>

* fix(agent): hand Pi its MCP servers through a temporary extension

Writing .pi/mcp.json and passing --approve trusted the whole project directory and failed when that file already existed. An explicit --extension loads the servers without project trust.

Co-authored-by: Cursor <[email protected]>

* docs: mark Pi as reading agent MCP configuration

The provider comparison still said Pi skipped the MCP field. Pi now receives that configuration.

Co-authored-by: Cursor <[email protected]>

* fix(agent): harden Pi MCP registration

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

* fix(agent): allow empty Pi MCP configuration

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

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: Yushen <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-12 13:26:00 +08:00
ZIceandmultica-agent 00e24a8ede fix(agent): create fresh OMP sessions without resume (#9131)
Co-authored-by: multica-agent <[email protected]>
2026-10-08 18:43:01 -07:00
小七 5063fc9079 MUL-7869: fix(agent): fail kimi runs whose wire log records a failed turn (#9057)
* fix(agent): fail kimi runs whose wire log records a failed turn

kimi's ACP adapter can answer session/prompt successfully while its own
turn failed: the session wire log carries a
{"type":"agent.turn.ended","outcome":"failed",...} record (e.g. a bare
"OAuthConnectionError: OAuth request to https://auth.kimi.com/api/oauth/
token failed" that matches none of the stderr sniffer's terminal
signatures). The run then kept status=completed and published the
intermediate narration as a successfully delivered result, so the daemon
sent no failure signal and retry/continuation policy never saw the
failure (#9054).

Fix: after the run, scan the session wire logs - the same source,
turn-boundary rules, and tolerant-line policy as the usage scan
(scanKimiSessionUsage) - for the main agent's failed agent.turn.ended
record, and promote completed->failed carrying kimi's sanitized
diagnostic. Only the structured outcome verdict counts, so conversation
text echoing failure words cannot flip a healthy run; a delegated
agent's failed turn stays that agent's own task path; timeout, abort,
and already-failed runs keep their own terminal semantics.

Complementary to #8573's zero-output guard: that incident shape had 26
tool calls and nonempty narration, which a zero-output guard
deliberately preserves.

Fixes #9054

Generated-by: GLM-5.3-Flash (ZCode)

* fix(kimi): sanitize the wire diagnostic and scan past oversized records

Review fixes for #9057 (CHANGES_REQUESTED, both repros offline with a
fake ACP; no live provider requests):

P1: kimiWireTurnFailure copied the record's errorMessage into the run's
diagnostic unsanitized. The failure path persists Result.Error, and the
shared redact.Text does not redact a quoted JSON credential shape
(request={"api_key":...}) — the sanitized diagnostic the PR described
was not actually provided. The diagnostic now goes through the same
sanitizeAgentDiagnostic pass as every child-process diagnostic before
it reaches Result.Error.

P2: the scan loop used newAgentStreamScanner, whose bufio.Scanner ends
its scan for good at a line beyond agentStreamMaxLineBytes, and the
loop never checked scanner.Err() — a valid but oversized context.append
record ahead of the terminal event hid the failed outcome and kept the
run completed. kimiWireTurnFailure now reads through
readAgentStreamLine, which consumes and discards an over-bound record
whole and keeps the later records readable, and ends the walk
explicitly on any other read failure (keeping what was read, the same
best-effort policy as a truncated tail).

Regressions, all red before the fix and green after:
- TestScanKimiMainTurnFailureSanitizesWireDiagnostic (P1)
- TestScanKimiMainTurnFailureReadsPastOversizedRecord (P2, scan level)
- TestKimiBackendWireTurnFailurePastOversizedRecord (P2, backend e2e
  with the fake ACP: red as got "completed" (error=""), the review's
  exact repro shape)
- TestReadAgentStreamLineSkipsOversizedAndContinues /
  TestReadAgentStreamLineReturnsUnterminatedTail (helper contract; the
  first caught a boundary miss where the cap was crossed only on the
  final buffer chunk)

Generated-by: GLM-5.3-Flash (ZCode)
2026-10-08 10:13:54 -07:00
bb14e8763c feat(issues): add multi_text and multi_url custom property types (MUL-7496) (#8559)
* feat(issues): add multi_text and multi_url custom property types

Workspaces could only hold list-shaped values as preset option lists
(multi_select) or member references (multi_actor). There was no way to
keep a list of free-form strings or links on an issue, so teams with
"related docs / spec links / aliases" needs had to either cram them into
one text field (no structure, nothing clickable) or burn one select
option per link.

This adds two property types:

- multi_text: an ordered list of free-form text entries
- multi_url: an ordered list of http(s) URLs

Each entry is validated exactly like a single text / url value
(length caps, http(s)-only for URLs), duplicates are dropped, insertion
order is preserved, and values are capped at 20 entries to respect the
16KB properties-bag budget. Values ride the existing jsonb bag, so the
jsonb_path_ops GIN index, the @> containment filter, and the client
value schema all keep working unchanged.

Filtering matches any single list element exactly (equality) plus "No
value" — the containment form the filter compiler already emits for
array elements, so no new server predicate is needed. List types
deliberately get no sort order and cannot group, like the other
multi-value types.

On the web, list values render as chips; multi_url chips are clickable
and open in a new tab, and the editor popover offers a per-entry open
button, so several related documents or links stay directly reachable
from the issue. The filter menu reuses the scalar input (equality-only
plus "No value"). CLI: comma-separated --value form for both types,
element-equality --property filtering, and display_values rows.

Server-side allowlist widened by migration 500 (check constraint
re-added NOT VALID then validated, same pattern as migration 341).

* fix(properties): address list property review nits

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

* fix(properties): scope list drafts to the edited issue

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

* test(agent): avoid fork races when writing zeroclaw fixtures

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

---------

Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-07 13:33:02 +08:00
Xichang(Seacen) Zhao c4fea18618 fix(agent): keep an Antigravity answer a trailing transport error cut off (#8983)
antigravityCompletedDespiteTrailingNetworkError exists to preserve a finished
answer when agy fails a follow-up round trip after producing it, but it
recognised exactly one error spelling. agy also reports an exhausted retry as
`API error (attempt N): request failed: Post "...": EOF`, which fell through and
discarded a response already sitting in result.response with its agent_response
step at DONE.

Match the transport cause rather than one sentence. The classifier stays narrow
on purpose: reportTaskResult documents failing closed so a provider 429 or
out-of-credit run can never be shown as Completed, and quota, capacity,
overload, policy and auth rejections all arrive as an HTTP response rather than
a failed round trip. agy's own `request failed:` prefix is excluded too — only
one spelling has been observed and nothing rules out agy reusing it for a
status error.
2026-10-06 23:15:37 -06:00
Ray Walker a5b2c361a9 MUL-7855 fix(pi): fail a run whose agent_end reports an error (#9028)
* fix(pi): fail a run whose agent_end reports an error

Pi 0.74.0 and earlier report a failure raised outside a model stream,
such as a provider API key command that fails, only on agent_end. The
stream carries no turn_end, error or auto_retry_end event, and print
mode exits 0, so the adapter recorded the run as completed with no
output. Pi resolves a command-backed API key again on every model
request, so the same path can also end a run part-way, after tool work.

Decode agent_end and record its last assistant message's stopReason
"error" through the existing turn-error guard, with errorMessage as the
reason. An automatic retry or a later successful turn still clears it,
as with turn_end.

* fix(pi): fail a run whose event stream overflows the scanner

The pi backend never checked scanner.Err(). A line over the shared 32 MiB bound ended the read loop, and the run then either recorded completed (when the rest of the line fit in the pipe buffer and pi exited) or hung until its timeout, with pi blocked writing to the unread pipe. agent_end repeats every message of the run, so on a long run it is the line most likely to overflow, and it can carry the run's terminal state.

Close stdout on a scanner error so pi cannot block on the full pipe, and fail the run with "pi stdout read error", keeping any provider error pi already reported. This matches the other stream backends.
2026-10-03 20:40:06 -06:00
mrlonely b2561aad61 fix(codex): exclude historical turns from resume responses (#9020) 2026-10-02 07:25:26 -07:00
mrlonely ba30324e10 fix(codex): add GPT-6.1 Sol fallback and current model pricing (MUL-7834) (#8996) 2026-09-30 21:39:07 -07:00
Bohan Jiangandmultica-agent 138134d341 fix(agent): pass Hermes custom args before the acp subcommand (MUL-7748) (#8882)
* fix(agent): pass Hermes custom args before the acp subcommand

Hermes only accepts its global flags (--provider, --yolo, -m, ...) ahead
of a subcommand, so the daemon's `hermes acp <custom args>` exited with
an argparse usage error before the ACP handshake and every run of a
Hermes agent with custom_args failed. Assemble `<prefix> <custom args>
acp` instead, keep the profile resolver and overlay stripping on the
same argv, and drop a trailing custom arg left without its value so it
cannot capture `acp` and hang the task in interactive chat.

Fixes #8878 (MUL-7748)

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

* fix(agent): keep acp's own flags after the Hermes subcommand

Hermes' acp subparser declares --accept-hooks, --version, --check,
--setup, --setup-browser and --yes/-y, and argparse only accepts them
after the subcommand: moving them ahead of `acp` turned an agent with
custom_args ["--yes"], which launched before, into a usage error.
Lay custom args out around `acp` instead — global flags (with their
values) before it, acp's own flags after it — and map profile
stripping back through that layout so custom args keep their
configured order.

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

* fix(agent): resolve Hermes flag abbreviations like argparse

Hermes keeps argparse's allow_abbrev, so custom args must be classified
the way its parsers resolve them, not by string equality. Behind `acp`
a `--y` is `--yes`; moved in front, the root parser reads it as
`--yolo` and turns off dangerous-command approval, while `--ye` went
from valid to a usage error. Keep every token the acp subparser would
take as its own option (exact, abbreviated or ambiguous) behind the
subcommand, and treat abbreviated value flags (`--prov`) as value flags
when pairing and when guarding against a bare flag capturing `acp`.

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

---------

Co-authored-by: multica-agent <[email protected]>
2026-09-27 22:21:50 +08:00
Bohan Jiangandmultica-agent 12f8f3f311 feat(agent): list Claude Opus 5.5 and GPT-6 Sol/Luna in the fallback catalogs (#8840)
Since the fallback catalogs only feed the model picker (#8811), listing
current models there is cheap. Add the models the installed CLIs now
advertise but the static lists were missing:

- Claude: claude-opus-5-5 (Claude Code 2.1.280 list_models default)
- Codex: gpt-6-sol (low..ultra) and gpt-6-luna (low..max), both default
  medium, from codex-cli 0.155.1 `debug models`; live discovery labels
  them "GPT-6 Sol" / "GPT-6 Luna" like GPT-6 Astra

MUL-7691

Co-authored-by: multica-agent <[email protected]>
2026-09-25 14:43:00 +08:00
Bohan Jiangandmultica-agent 1aa815a738 fix(daemon): stop validating saved model settings against fallback catalogs (#8811)
When model discovery fails, providers substitute a static catalog so the
picker stays usable. The daemon then validated saved thinking levels and
service tiers against that stand-in, silently dropping values the static
list did not know about — which forced hand-maintained model and per-model
effort tables to chase every new release.

Only a verified catalog (discovered, non-empty) may now reject, rewrite, or
drop a saved model, thinking level, or service tier. Fallback and empty
catalogs pass the saved values through to the CLI for every provider. The
checks that describe the binary rather than its models stay: a Claude CLI
without --effort (or without the saved value in its --effort list) still
drops the level, and Codex's explicit standard tier still follows the CLI
version.

- Catalog.Verified / Catalog.CLIThinkingLevels; missingFromFallbackCatalog removed
- claudeModelEffortAllow removed: the fallback picker offers the --help superset
- ModelKnownIncompatibleWithProvider no longer consults static lists (prefix-only, same result)

MUL-7691

Co-authored-by: multica-agent <[email protected]>
2026-09-25 14:33:58 +08:00
Kevin Kwok ec70b224d7 MUL-7675: feat(grok): support live-run steering via Grok Build ACP (#8796)
* feat(grok): support steering active Grok Build runs

* test(grok): cover steering before first agent output

* fix(grok): bound interject acknowledgement time
2026-09-24 16:43:10 +08:00
54520de957 MUL-7646: fix(claude): report per-run usage for resumed sessions (#8792)
* fix(claude): report per-run usage for resumed sessions

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

* fix(claude): preserve usage when counters reset

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

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-24 13:44:51 +08:00
mrlonely 0516dd57c3 MUL-7625: feat(antigravity): stream live tool execution events (#8734)
* feat(antigravity): stream live tool execution events

* chore(i18n): remove obsolete French Antigravity live notice

* fix(antigravity): preserve tool events under backpressure

* fix(antigravity): retain tool errors in bounded previews

* fix(antigravity): normalize tool parameters for transcript UI

* fix(antigravity): move tool aliases and normalize file content
2026-09-23 19:02:44 +08:00
Yincan Huangandmultica-agent 7be175346e MUL-7613: fix(agent): refresh live model catalogs (#8722)
* fix(agent): refresh live model catalogs

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

* fix(agent): preserve live-only Codex settings on catalog fallback

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

* fix(agent): retain Codex explicit-standard CLI version gate

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

---------

Co-authored-by: multica-agent <[email protected]>
2026-09-23 16:57:36 +08:00
93ae552ba9 feat: add messages to active Claude Code and Codex runs (MUL-7565) (#8682)
* feat: add live run supplements (MUL-7565)

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

* fix: address MUL-7565 review feedback

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

* fix: resolve MUL-7565 supplement panel and receipt review

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

* refactor: remove MUL-7565 supplement ordinals

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

* fix: close supplement composer after run remount

* fix: exclude task supplements from completion reconciliation

* fix: build a persistent binary for local daemon launches

* feat: support Claude task supplements through SDK hooks

* refactor: simplify task supplement delivery

* test: remove redundant task supplement coverage

* fix: remove redundant supplement delivery notices

* refactor: extract local daemon launcher fix to its own PR

* fix: preserve ordinary comment size compatibility

* fix: accept empty task start acknowledgements

* fix: bound task start response decoding and retries

---------

Co-authored-by: Forge-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: yushen <[email protected]>
2026-09-23 16:10:07 +08:00
Bohan Jiangandmultica-agent f41fae6b08 fix(cursor): own Windows background shells by launch Job membership (MUL-7417) (#8638)
* fix(cursor): own Windows background shells by launch Job membership (MUL-7417)

captureCursorBackgroundProcess on Windows walked a machine-wide PID->PPID
snapshot to prove a reported shell descends from the launch and to find
its pre-existing children. Windows keeps a child's recorded PPID after
the parent exits and reuses the PID, so the walk could reach a process
owned by someone else; opening it failed with 'Access is denied' or
'not an owned descendant' and the whole capture aborted. The daemon then
fell back to the idle watchdog and a Cursor run that later went quiet
took the 10-minute watchdog path instead of completing. In CI this was
TestCursorBackgroundLifecycle/multiple failing about 2 runs in 43.

Take candidates from the launch's own Job Object instead
(QueryInformationJobObject / JobObjectBasicProcessIdList). Descendants
inherit the Job at creation and cannot leave it, so membership is the
ownership proof: the parent-chain walk goes away, the PPID snapshot is
used only to attach a member's pre-existing children, and a member that
exits or is reused between listing and open is skipped instead of
failing the capture. Creation ordering still tells a real child from
one that points at a reused parent PID.

Restore TestCaptureCursorBackgroundLateDescendant on Windows; it was
excluded for exactly this race.

ci.yml runs the Windows Cursor step with -count=20 on this PR only to
show the race is gone; it is reverted before merge.

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

* ci: restore -count=1 on the Windows Cursor background step

The -count=20 stress run on this PR passed (run 35580440594, windows-execenv);
the per-merge step goes back to a single pass.

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

* fix(cursor): complete the Job member list and refuse unopenable live members (MUL-7417)

Review follow-ups on #8638:

- cursorJobProcessIDs grew its buffer only on ERROR_MORE_DATA. The kernel
  can also return success with NumberOfProcessIdsInList short of
  NumberOfAssignedProcesses, which a Job over the initial capacity or one
  growing during the query would hit; the short list then passed for the
  membership and could reject the target or leave children out. Retry
  until the counts agree, fail explicitly if they never do, and make the
  initial capacity a package var so a three-process tree exercises the
  growth path in a test.

- The child walk skipped every open failure as 'exited or reused'. A live
  member whose DACL denies PROCESS_SET_QUOTA|PROCESS_TERMINATE also fails
  to open, and skipping it left the capture reporting success with that
  member outside the tool Job. Skip only PIDs proven to carry no member
  (handle outside the launch Job, or no longer listed after a re-query,
  confirmed by one more open); otherwise fail the capture so the caller
  takes its existing watchdog fallback. OpenProcess goes through a
  package var so a test can make a live member unopenable.

Windows-only tests cover both, plus the plain claim-and-terminate path
for a shell with a pre-existing child.

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

* test(cursor): name the Job list growth test so the Windows CI filter runs it

The windows-execenv step selects 'TestCursorBackground|TestCaptureCursorBackground';
TestCursorJobProcessIDsGrowsPastInitialCapacity matched neither and never ran.

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

---------

Co-authored-by: multica-agent <[email protected]>
2026-09-21 18:36:34 +08:00
Multica Eveandmultica-agent 8c9f865e3e MUL-7480: Revert comment steering feature (#8648)
* Revert "MUL-7480: Link delivered steers to final replies (#8623)"

This reverts commit 3de6275eb3.

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

* Revert "fix: preserve active task intent in steer frames (#8621)"

This reverts commit b866dacd1c.

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

* Revert "MUL-7480: Steer active agent turns from comments (#8605)"

This reverts commit 8ce795b007.

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

* chore(db): remove reverted comment steer schema

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

---------

Co-authored-by: Sol-Boy <[email protected]>
2026-09-21 18:29:50 +08:00
LinYushen 7d7e3e59a6 Revert "Revert "fix: preserve active task intent in steer frames (#8621)" (#8…" (#8646)
This reverts commit d993257f60.
2026-09-21 18:00:16 +08:00
LinYushen d993257f60 Revert "fix: preserve active task intent in steer frames (#8621)" (#8644)
This reverts commit b866dacd1c.
2026-09-21 17:58:43 +08:00
b866dacd1c fix: preserve active task intent in steer frames (#8621)
Co-authored-by: Forge-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-21 13:45:06 +08:00
8ce795b007 MUL-7480: Steer active agent turns from comments (#8605)
* feat: steer active agent turns from comments (MUL-7480)

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

* fix: make comment steering hint driven

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

* fix: harden active comment steering races

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

---------

Co-authored-by: Forge-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-20 19:00:53 +08:00
Bohan Jiang 80aa31c1ab MUL-7520 fix(agent): support OpenCode 2.x runtimes (#8596)
OpenCode 2.0 changed four things the daemon depends on. Two fail loudly —
`--dir` and `--variant` both exit 1 on an unrecognized flag, which is why every
task on a 2.x runtime died in ~130ms with empty output (GH #8586). Two are
silent: MCP config had no working channel left, and `opencode run` became a thin
client in front of a resident service, so signalling the client's process group
no longer stopped the run.

- `--dir` is dropped on 2.x; the workdir comes from cmd.Dir + PWD.
- The thinking level folds into the model string as `provider/model#variant`.
- Cancellation calls POST /api/session/<id>/interrupt in front of the existing
  SIGTERM→SIGKILL, which is the only thing that stops a 2.x run.
- Runs carrying Multica-managed MCP servers are refused with an error naming
  every source, rather than started without them: 2.x accepts MCP only through a
  file in the task workdir, where the agent's own commits would capture the
  credentials. Restoring MCP is tracked separately.

The 1.x path is unchanged, pinned by argv and MCP regression tests. Verified
against @opencode/cli 2.0.10 and opencode-ai 1.18.31 on macOS/arm64; Windows
cancellation is not yet verified end to end.
2026-09-20 17:10:32 +08:00
2aa20b0353 ZIC-245: fix custom Oh-My-Pi runtime profile identity and discovery (#8576)
* fix(runtimes): preserve custom profile base runtime identity

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

* fix(runtimes): pass both url params to the profile identity test

TestUpdateRuntimeProfile_RejectsIdentityChange nested two withURLParam
calls. That helper installs a fresh chi route context each time and does
not copy what is already there, so the outer call for profileId dropped
id: the handler answered 400 "workspace_id is required" and never
reached the immutability check the test exists to cover.

Use withURLParams, which carries existing params forward and is what the
rest of this file already uses.

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

* fix(runtimes): name a runtime target the same way on every surface

The base-runtime picker spelled Oh-My-Pi with an inline ternary, so it
was the only place that used the product name. The catalog row, both
detail headers, the base-runtime detail row and the locked chip in the
form kept rendering the stored id under `capitalize` — a user picked
"Oh-My-Pi" and the next screen, and the saved profile, said "Omp".

Move the name into runtimeTypeLabel next to the target whitelist and
read it from every surface. profileRuntimeType replaces the repeated
runtime_type ?? protocol_family fallback.

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

---------

Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Bohan-J <[email protected]>
2026-09-20 12:53:52 +08:00
83c04ceae0 test(codex): pin first-delta visibility contract (MUL-7465) (#8539)
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-18 16:10:19 +08:00
Bohan Jiangandmultica-agent bf7e6e50e7 MUL-5241 fix(agent): bound the hermes shutdown so an escaped pipe holder can't wedge a turn (#5878)
Owning the runtime process tree (#7522) made cmd.Cancel kill every descendant
in that tree, so the usual pipe holder dies with it and both readers reach EOF.
A descendant outside the tree does not: on POSIX because it called setsid and
left the process group, on Windows because startOwnedProcessTree failed open
and the child runs unowned, so the kill reaches the leader alone. The join
after the forced shutdown was unbounded, so the turn then hung forever: no
tool result, no reply, nothing until the user cancelled by hand.

Reap the process as part of that forced shutdown instead. cmd.Wait() returning
is what closes the parent ends of the pipes, so it frees a reader the kill
could not reach, and the 10s WaitDelay the claude, codearts and antigravity
backends already use bounds Wait itself once the context is cancelled. The
readers are still joined before the buffers are read, so providerErr.Finalize
keeps its documented precondition — a fully drained stderr pipe — instead of
racing the copier and splitting a provider error across its flush. The same
reap now runs in the deferred cleanup, where joining the readers before the
goroutine closes msgCh keeps a live reader from panicking in trySend.

What this buys is a bounded recovery, not a repaired turn: past the existing
2s drain grace the backend delivers from the buffers it has, so an exceptional
shutdown can report a turn without output the readers had not handed over yet.
It does not fix the Windows terminal tool — that belongs in hermes' own bash
startup probe (NousResearch/hermes-agent#73403, still open) — and it does not
reap the escaped process, which is out of the daemon's reach by definition.

Co-authored-by: multica-agent <[email protected]>
2026-09-18 16:07:19 +08:00
043821ab77 MUL-7465 fix(chat): stream Codex deltas end to end (#8536)
* fix(chat): stream first agent text sooner (MUL-7465)

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

* fix(codex): make delta streaming lossless (MUL-7465)

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

* fix(codex): retain mismatched pending deltas (MUL-7465)

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

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-18 15:49:42 +08:00
7f8e4980ab MUL-7467 fix(agent): fail silent Pi provider errors (#8535)
* fix(agent): bound silent Pi provider failures

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

* fix(agent): preserve Pi errors across cancellation

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

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-18 15:20:48 +08:00
mrlonely afedc6f76d fix(cursor): preserve resumed sessions on connect timeouts (#8527) 2026-09-18 12:50:06 +08:00
2e728fc6de MUL-7358 fix(agent): require OpenCode >= 1.1.54 so pre-fix builds cannot fill the host disk (#8510)
* fix(agent): require OpenCode >= 1.1.54 so old builds cannot fill the host disk

OpenCode <= 1.1.53 ignores TMPDIR, TMP and TEMP when its embedded Bun runtime
extracts a native module, writing into the shared system temp directory
whatever the daemon exports. Each successful run leaves one 4-8 MB module
behind under a fresh, non-content-addressed name, so nothing ever reuses or
overwrites it. Upstream fixed this in 1.1.54 (2026-02-10).

Reproduced on Linux against pinned builds, verifying each binary's
self-reported version: 1.1.49, 1.1.52 and 1.1.53 write into the shared /tmp
with the three variables pointed elsewhere; 1.1.54 and everything after honour
them. #8392 is what that costs a self-hosted daemon left on a pre-fix build —
~2,960 files, 11.16 GiB, root filesystem at 99%, and no error anywhere until
the disk runs out.

The per-task temp directory cannot contain a CLI that never reads the
variables, and deleting by filename in a shared /tmp is not safe — a module
another live process still has dlopen'ed looks identical. Refusing the CLI at
registration is the only place this can be stopped, and it turns a silent host
outage into "opencode version X is below minimum required 1.1.54 — please
upgrade".

Note this floor is unlike the others in the table: every existing entry names a
protocol the backend speaks through, so an older CLI cannot serve a task at
all. A pre-1.1.54 opencode serves tasks correctly; it damages the host while
doing it. The table comment now says which kind each entry is.

Refs #8392

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

* docs: list the OpenCode minimum runtime version

The install-agent-runtime callout enumerates the per-runtime floors the daemon
enforces at registration; OpenCode now has one, so it belongs in the list.
Updated in all four translations.

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

---------

Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-17 15:51:59 +08:00
4d4cae7753 MUL-7312 LARI-10: Desktop PATH fix + CodeBuddy Claude-parity stream-json guards (#8340)
* fix(desktop): do not prepend PATH fallbacks over login-shell Node

Prepending /usr/local/bin after fix-path let a stale system Node 12
shadow nvm/fnm, breaking shebang CLIs (CodeBuddy/OpenClaw) on --version.
Append only missing fallback dirs so recovered shell PATH keeps precedence.

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

* fix(agent): align CodeBuddy stream-json guards with Claude

Pass stderr into resume rejection, fail on prompt_too_long terminal_reason,
and force foreground when Bash requests run_in_background — the same
failure modes Claude already handles for Multica-managed headless runs.

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

* fix(agent): align CodeBuddy guards with real CLI protocol

Address review on #8340: force CODEBUDDY_CODE_DISABLE_BACKGROUND_TASKS,
fail on system/task_* background events, and drop Claude-only
prompt_too_long / async_launched handling that CodeBuddy 2.150.0 never emits.

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

* fix(agent): update CodeBuddy handleUser test after signature change

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

* fix(agent): force CodeBuddy background-disable env last for Windows

Append CODEBUDDY_CODE_DISABLE_BACKGROUND_TASKS=1 so os/exec's
case-insensitive last-wins dedup keeps it over lowercase custom_env.
Also extract Desktop PATH fallback helper with unit tests.

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

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-17 13:34:53 +08:00
importcpp 7e4758ac1a MUL-7445 fix(qwen): exclude resumed history from run usage 2026-09-16 19:27:59 +08:00
mrlonely 726d489497 fix(agent): report Grok prompt budget stops as failures (#8481) 2026-09-16 16:29:17 +08:00
9954baab86 MUL-7397: fix(qwen): accumulate fallback usage by message (#8443)
* fix(qwen): accumulate fallback usage by message

* fix(qwen): count each usage message once

* test(service): scope cancelled recovery assertions to signals

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

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-16 15:20:06 +08:00
Bohan Jiang c923ace620 fix(agent): let omp (Oh-My-Pi) agents set a thinking level (MUL-7412) (#8464)
Agents on the built-in omp (Oh-My-Pi) runtime could not set a reasoning effort at
all. omp dispatches to the pi backend but was missing from providerThinkingEnums,
so the server rejected every value, and parseOmpModels discarded omp's per-model
effort catalog, leaving the picker hidden and the daemon's guard nothing to
validate against. `--thinking` is blocked in custom_args by design, so the only
workaround was folding the level into the model string (`devin/swe-2:max`).

What a catalog entry means is taken from can1357/oh-my-pi v18.2.0 rather than
inferred from pi, because two of pi's rules are inverted for omp:

- `models --json` emits `thinking` as an effort array or `null`, never an object.
- omp's Effort vocabulary excludes `off`, yet resolveThinkingLevelForModel returns
  `off` before clamping runs, so it is honoured for every reasoning model.
- `reasoning: true` with `thinking: null` means "reasons, no controllable dial";
  inferring levels there would offer efforts omp then clamps away.

So a reasoning model advertises `off` plus exactly the efforts the catalog listed,
a non-reasoning model gets no picker, and nothing is inferred. `auto` is
deliberately not exposed: omp keeps it as a session-level sentinel that is never
an Effort or ThinkingLevel.

An effort with no pinned model is refused rather than stored, because omp resolves
its own default role model at task time and clamps the level to what that model
supports — so a stored level would save cleanly, read back as set, and then run at
a different level. The refusal holds in all three layers: the inspector hides the
picker, CreateAgent/UpdateAgent return 400 (Update against the state the request
lands on, so clearing the model while an effort is carried over is caught too),
and ValidateThinkingLevelWith rejects before any catalog load, so a discovery
error cannot reach the daemon's fail-open path.

codex shares that execution constraint but stays out of the API-level refusal via
a separate narrower predicate: agents already hold an effort with an empty model,
and 400ing that pair would block unrelated edits to them. omp has no such history.

Closes #8458
2026-09-16 12:14:09 +08:00
d18cec01c3 MUL-7415 ci: retire the standalone Cursor lifecycle job (#8465)
* MUL-7415 ci: retire the standalone Cursor lifecycle job

cursor-background-lifecycle ran a macos-latest and a windows-latest runner
on the runtime scope, i.e. on nearly every backend change, to cover one
integration's background shell. Neither leg needed to be its own job.

Windows: windows-execenv is already a windows-latest job on the same
runtime scope testing ./pkg/agent process-tree ownership. The Cursor
patterns move there as a step, so a backend PR spins up one Windows
runner instead of two. The ./internal/daemon watchdog command does not
come along: every test it selected is untagged except
cursor_uncaptured_watchdog_test.go, which is `linux || darwin` and never
built on Windows, so the ubuntu backend suite already runs all of them
under -race.

macOS: nothing else in this workflow runs Go on macOS, and
cursor_background_process_darwin.go is 208 lines of XNU identity
signalling whose two tests are `//go:build darwin`. Deleting outright
would leave it with no coverage anywhere, so it becomes macos-runtime,
gated on `full` — the daily cron and workflow_dispatch that #8451 added
for exactly this trade. Its single-count finalization step is dropped as
dead: `full` is now always true here, so the -count=5 stress pass that
was already the full-run variant is the only one left.

The backend gate tracks both moves; ci-scope's production gate test
parses ci.yml, so it verifies the new needs/if/scope wiring directly.

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

* MUL-7415 test(agent): stop building the Cursor late-descendant test on Windows

TestCaptureCursorBackgroundLateDescendant failed twice in the last 43 main
pushes on windows-latest and zero times in 40 on ubuntu. The two Windows
failures reported different errors -- `Access is denied.` and `not an owned
descendant` -- from the same captureCursorBackgroundProcess call, which is
one race wearing two faces: cursorWindowsProcessParents snapshots every
PID->PPID pair on the machine, Windows keeps a child's recorded PPID after
the parent exits, and that PID is then free to be reused, so the ancestry
walk can reach a PID someone else now owns.

Narrowing the tag to `linux || darwin` keeps the Linux and Darwin arms,
which have never flaked and still cover the whole contract: a descendant
forked after capture is owned and terminated, the leader's death does not
orphan it, and an unrelated process is never signalled. Windows keeps
TestCaptureCursorBackgroundProcessRejectsForeignJob for Job membership.

This is deliberately a mute, not a fix. MUL-7417 carries the race itself
and restores the tag.

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

* MUL-7415 ci: run the Windows Cursor step last in windows-execenv

Folding the Cursor job into windows-execenv means Cursor's failures now
truncate windows-execenv's own steps, which the separate job could never
do. Eight Windows-only checks -- bounded output collection, the Pi session
lock, the OpenClaw npm shim, junction resolution -- sat after it and would
be skipped by a single Cursor failure.

Moving the step behind `Build Windows CLI helper entrypoint` keeps the
one-runner saving while leaving the pre-existing coverage first in line.
Also adds the -timeout=5m this job's other test steps carry.

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

---------

Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-16 01:13:55 +08:00
importcpp a843b44a48 MUL-7382 docs(agent): clarify token usage normalization contract (#8423)
Follow-up to the review notes on #8416. Comments only — token arithmetic, fields,
JSON tags and test assertions are unchanged.

State the actual rule on the shared TokenUsage contract: only a breakdown already
included in a total must not be added to that total again, rather than banning
provider breakdowns as a category. Keep the four-bucket mutual-exclusivity
sentence normative.

Explain why codexRawTokenUsage retains ReasoningOutputTokens — it mirrors the
provider payload and keeps resume-baseline subtraction symmetric — and record
that no downstream consumer reads that breakdown.

Restore the normative sentence on the usage contract test and scope its coverage
claim to the Codex and Claude cases it actually exercises.
2026-09-15 18:47:19 +08:00
02a8ff3e95 feat(maintenance): add resumable internal backfill API (MUL-7365) (#8436)
* feat(maintenance): add resumable internal API backfills (MUL-7365)

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

* fix(maintenance): ship bounded container operations driver

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

* test(cursor): synchronize ownership before finalization

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

---------

Co-authored-by: Lambda <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-15 17:47:54 +08:00
importcpp 20f20d6bc7 fix(qwen): exclude cache reads from plain input usage (#8440) 2026-09-15 15:54:31 +08:00
importcpp c4c8bab56c fix(opencode): include separate reasoning in output usage (#8431) 2026-09-15 13:59:20 +08:00
a00ebbb952 fix(codex): MUL-7381 synchronize codex thread ID access (#8428)
The task goroutine published c.threadID after thread/start or
thread/resume returned, while the stdout goroutine was already reading
the same field in isNotificationFromOtherThread. That filter runs ahead
of the current-turn gate, so the gate's state could not serialize the
read, and the app-server routinely emits a notification immediately
after the thread setup response.

Guard the field with its own RWMutex and route the write plus all three
reads through setThreadID/getThreadID, mirroring how turnID is already
handled. Behaviour is unchanged: the filter still compares a single
snapshot and keeps the existing no-current-thread, missing-threadId,
subthread and resume-history cases.

Fixes #8422

Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-15 12:56:07 +08:00
importcppandimportcpp cf52ba33cc fix(codex): MUL-7374 avoid double-counting reasoning output tokens (#8416)
Codex reports reasoning tokens as a breakdown inside `output_tokens`, but both the
app-server event path and the rollout JSONL fallback added them on top of the
output total, inflating recorded output usage and the usage-based cost estimate.

Consolidate both readers onto one shared conversion that keeps the provider's
inclusive output total, preserving cache-bucket exclusivity, resume baselines and
duplicate-snapshot handling. Add cross-provider contract tests covering both Codex
sources and both Claude result formats.

Verified against a live codex app-server session (codex-cli 0.144.6): the provider's
own totalTokens equals input + output with reasoning not added, and the recorded
buckets now sum to that total exactly. Previously persisted rows are not rewritten.

Co-authored-by: importcpp <[email protected]>
2026-09-15 01:30:58 +08:00
c26da76c42 fix(codebuddy): deduplicate fallback usage by response ID (#8395)
CodeBuddy is a Claude Code fork on the same stream-json protocol, and its
adapter carried the same usage-accounting defect #8382 fixed for Claude: one
response can emit several assistant events sharing a message ID and usage, and
the adapter summed every one of them. A run that ends without usable final
usage therefore reported inflated input and cache tokens.

Count each identified main-loop response once within an execution. Keep
forwarding every text and tool block, and keep the existing precedence of the
result's modelUsage over result-level usage over the fallback.

The fallback also stops summing the assistant output_tokens placeholder and
excludes nested-agent events, matching claude.go and the documented crash
recovery procedure. It recovers main-loop input/cache usage, not a full bill.

Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-14 18:24:10 +08:00
importcppandimportcpp e0b1f1cf10 MUL-7355 fix(claude): deduplicate fallback usage by response ID (#8382)
Count each main-loop response once when a Claude run ends without usable final usage. Claude can split one API response into several assistant events sharing a message ID and usage; summing them inflated input and cache tokens on the fallback path. Terminal modelUsage/usage precedence is unchanged, and all text and tool blocks are still forwarded.

The fallback no longer sums the assistant output_tokens placeholder and skips nested-agent events, following the documented per-step accounting and crash-recovery behavior. It recovers main-loop input/cache usage only, not a complete bill.

Co-authored-by: importcpp <[email protected]>
2026-09-14 17:02:15 +08:00
2295be9271 fix(test): widen the codex retry handshake budget so CI stops flaking (#8386)
TestCodexExecuteRetriesAfterSignaledProcessIsReaped spends the handshake
budget twice, and the two spends pull in opposite directions: the first
attempt only reaches the signal/reap path by burning the whole budget, while
the retry has to answer initialize inside that same budget from a cold sh
spawn. At 50ms only an idle machine satisfied the second spend, so under CI
contention the retry timed out too and the test reported the retry as
missing — it failed that way on main twice today (361edd90a, 8908fcfbc).

Reproduced in a CPU-constrained Linux container: 25/25 failures with the
same message CI prints, and a measured 100-309ms cold-spawn handshake
latency under that load. With a 2s budget the same loop passes 15/15. The
cost is one deliberate 2s wait on a Linux-only test.

Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-14 16:01:57 +08:00
686957e743 MUL-7271: test(server): cut slow Go test time further (follow-up to #8295) (#8318)
* test(server): reduce slow unit test waits

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

* test(server): cut slow Go unit test time further

Follow-up to the first pass on MUL-7271. Test-only changes plus a few
package-private seams whose production values are unchanged.

- Re-executed race-built test binaries no longer pay the race runtime's 1s
  atexit sleep (GORACE=atexit_sleep_ms=0 for child processes only).
- pkg/agent: event-driven or shorter test-only bounds for handshake, drain
  and idle graces; redundant timeout variants merged.
- daemon/execenv: git fixtures copied from a template repo built once; tests
  that re-ran the same worktree lifecycle folded into their supersets;
  ForkLock-guarded fake-CLI writes so parallel tests cannot hit ETXTBSY.
- handler/service: cap and paging tests shrink their caps instead of seeding
  thousands of rows; lock waits probe pg_locks instead of sleeping; one
  shared pgxpool per service test binary; cheaper cron and RSA fixtures.
- wecom: the phishing-link sweep reuses one parser and derives CRLF from LF
  with a sampled cross-check; fake sockets acknowledge instead of waiting out
  the ack timeout.
- migrate: the migration corpus is parsed once; isolated DB tests run in
  parallel.

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

* test(server): remove transport timeout unit tests

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

* test(server): restore two non-timeout tests and make a bound load-bearing

Follow-up to the review of #8318. All test-only; no production code changes.

- daemon: restore the three read-liveness cases that the transport-timeout
  sweep took with it. They assert that a ping, a pong and an application
  frame each EXTEND the read deadline, so a healthy wakeup socket survives
  without traffic of our own — the deadline never fires on the happy path,
  which is why they are not transport-timeout tests. Verified they fail
  (all three) when the extension calls are removed from wakeup.go, and pass
  in 0.57s together. The pure "times out without peer traffic" case stays
  deleted.

- pkg/agent: restore the parent-DEADLINE half of the opaque-env redaction
  pair. codex.go treats a parent deadline and a parent cancellation as one
  ending; only the Canceled arm was left covered. The fake now writes the
  secret to stderr before reading the handshake, so the check no longer
  races our own initialize write, and the budget is 2s against a measured
  430-465ms launch. Verified it fails when the !contextEnded guard is
  removed.

- pkg/agent: TestRunCollectQuietDoesNotWaitWhenTheAnswerIsIn had its bound
  widened to a literal 5s, which the shrunk 750ms drain grace can never
  reach — the test could no longer fail. It now raises the drain grace to
  10s for its own duration and asserts under 3s: healthy runs take
  0.19-0.44s, and a broken short-circuit takes 10.19s and fails.

- wecom: gofmt the import block in relay_settle_budget_test.go.

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

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: J <[email protected]>
2026-09-14 14:33:50 +08:00