mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 09:26:50 +08:00
zai-beta
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
603519d158 |
MUL-6977: test: remove implementation-only coverage (#7932)
* test: remove implementation-only coverage Co-authored-by: multica-agent <[email protected]> * test: remove remaining implementation-only coverage Co-authored-by: multica-agent <[email protected]> * test: restore compatibility and fragment navigation coverage Co-authored-by: multica-agent <[email protected]> * test: preserve behavioral cleanup contracts Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
3c7221492b |
fix(agent): deliver the OpenCode prompt on stdin, not argv (MUL-5841) (#6552)
* fix(agent): deliver the OpenCode prompt on stdin, not argv (#6538) The daemon appended the whole task prompt as an argv element, so on Windows every OpenCode task whose command line cleared CreateProcess's 32,767 character lpCommandLine limit failed to start at all. Go surfaces the resulting ERROR_FILENAME_EXCED_RANGE (206) as "The filename or extension is too long", which reads as an exe-path problem and is not one. A workspace with a realistic set of models and skills clears that ceiling on the prompt alone (39,629 bytes in the report), leaving no usable in-app workaround. `opencode run` merges its variadic [message..] positional with whatever is piped in, so passing no positional makes the piped text the entire run message. That path is present in the reported v1.18.14 and back to v1.4.10. Write the prompt from its own goroutine, as the Pi and Cursor backends already do: a prompt larger than the pipe buffer blocks mid-write until OpenCode drains it, and OpenCode cannot drain while nobody consumes its stdout. OpenCode reads stdin to EOF, so stdin is closed exactly once as the end-of-prompt signal — including on the cancellation path, where closing it releases a writer still blocked on a full pipe. The existing process-group SIGTERM -> SIGKILL ordering from #4533 is unchanged. Keeping the prompt off argv also stops it being echoed into the "agent command" log line; prompt_bytes replaces it there. Scoped to OpenCode. DevEco, OpenClaw (#6032), Qwen, Copilot and Antigravity inline their prompts the same way, but each CLI's input contract needs verifying on its own before it moves. Co-authored-by: multica-agent <[email protected]> * fix(agent): do not fail a completed run on a benign prompt-write EPIPE The backend CI run for this PR failed in TestPiExecuteRetainsOnlyLastTurnOutput with "pi prompt write failed: write |1: broken pipe". The cause is a latent race shared by pi and the OpenCode code added in the previous commit, which copied pi's error handling. A child that exits without draining stdin closes the read end while the parent is still writing the prompt, so the parent's write returns EPIPE. Both backends converted that into a failed result even when the agent had already emitted a complete, successful stream — making the outcome depend on whether the write landed before or after the child exited, i.e. on machine load. OpenCode reads stdin to EOF before it does any work, so a run that produced a complete stream necessarily received the whole prompt; an EPIPE recorded after that only means the pipe closed on the way out. Report the write error only for a run that did NOT complete, where it can actually explain the failure, and append rather than overwrite so the stream's own diagnosis survives. cursor.go already guards this correctly by ignoring writeErr once a result was seen. Test fakes now drain stdin, matching what the real CLIs do. A fake that exits without reading is what manufactures the spurious EPIPE, and that is what broke CI — pi's piEventStreamScript is the fixture that failed. Adds a deterministic regression test: a child that emits a full successful stream and never reads a 1.2 MB prompt. Against the previous commit it reproduces the CI signature exactly (status "failed", "broken pipe"); with this change it passes. pi.go's production handling has the same defect and still needs the equivalent fix, but that belongs to whoever owns #6485 — this change keeps to OpenCode plus the test fixture that was breaking the build. Co-authored-by: multica-agent <[email protected]> * fix(agent): gate prompt-write suppression on a positive terminal signal Review caught a false-success path opened by the previous commit. It suppressed a failed prompt write whenever status was "completed", on the reasoning that a completed run must have received the whole prompt. That reasoning does not hold: processEvents starts finalStatus at "completed" and only fails closed on structural evidence — an open step, or a step awaiting a continuation. A child that emits NOTHING and exits 0 sets neither, so the default survives. The full path: the parent writes a large prompt, the child exits 0 without reading a byte, the write returns EPIPE, stdout hits EOF with no events, and the run is reported as completed with empty output and no error. The prompt never reached the agent and nothing said so. Reproduced deterministically before the fix: status="completed" output="" error="". Absence of a failure signal is not proof of success, so record the positive one instead. eventResult.sawTerminalSignal is set only when a step_finish closed the last step with no continuation pending, and the write error is suppressed only on that evidence. It is deliberately not the negation of noTerminalSignal: an empty stream sets neither, because there is nothing to fail closed on and nothing proving completion either. When the signal is absent the write error now also flips the default "completed" to "failed". Adds the empty-stream regression alongside the existing complete-stream one, so both directions of the branch are pinned. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
37e2b07094 |
MUL-5831 fix(agent): fail opencode runs that end on an empty step (#6545)
* fix(agent): fail opencode runs that end on an empty step (MUL-5831) `opencodeBackend.processEvents` required a positive terminal signal but only failed closed on two shapes: EOF while a step was still open, and a step that finished with reason "tool-calls" whose continuation never started. A final step that opens, emits only a reasoning part, and closes with reason "unknown" and an all-zero token block matches neither, so `opencode run` exited 0 and the daemon reported `completed` with an empty `agent_error` — a run with no deliverable that leaves the issue stalled until something re-wakes the agent. Zero input tokens means the provider round-trip never happened; that is a dead stream wearing a clean finish. Track whether each step produced anything at all — text, a tool call, or any non-zero token counter — and fail closed when the run ends on a step that produced none of them. The criterion is deliberately "this step produced nothing", not "the run produced no text", so a task whose only deliverable is a tool side effect stays green. The finish reason is not consulted: a missing or unrecognised reason must stay terminal for protocol compatibility, and voidness is orthogonal to it. The run now enters the existing failure path with a diagnosable error, so the task's retry rules apply instead of a silent false-green completion. Refs #6522 — this fixes the first of its three symptoms; see the PR body. Co-authored-by: multica-agent <[email protected]> * fix(agent): count every opencode usage field as round-trip evidence (MUL-5831) The void-step guard claimed "any non-zero token counter keeps the step productive", but only checked input, output and cache. OpenCode's protocol keeps reasoning and the aggregate total in fields of their own and reports cost as a sibling of the token block, and none of the three were parsed at all — so a real step that landed with reasoning or cost positive while input and output happened to be zero would be called void and fail a healthy run. Parse reasoning, total and cost, and move the check into stepReportedUsage so the criterion is stated once. Reasoning and total are read as evidence only: total is derived, and TokenUsage has no reasoning bucket, so folding either into the usage accumulator would change billing figures rather than fix a bug. Also renames the guard's error text to "no reported usage", which is what it now means. Co-authored-by: multica-agent <[email protected]> * fix(taskfailure): make opencode stream-ended failures retryable (MUL-5831) Turning a false-green completion red only helps if the failure then retries. It did not: none of the three errors the OpenCode terminal-signal guard raises was on the retry allowlist. The two "terminal signal" variants matched rule 13's "signal" substring by accident and landed in agent_error.process_failure; the new empty-step one fell through to agent_error.unknown. Either way the task died on the first attempt and max_attempts never applied. All three mean one thing — the provider stream died and `opencode run` still exited 0 — which is agent_error.provider_network by definition. Route them there on the shared "opencode stream ended" prefix. That bucket is retryable and resume-safe, so the retry child inherits the session and continues the truncated conversation instead of redoing the work already paid for. Covered end to end: the classifier cases, and a service-level test that walks each guard error through Classify into retryEligible — retrying with an attempt left, and still terminating at the ceiling so a deterministically broken provider cannot loop. Co-authored-by: multica-agent <[email protected]> * fix(taskfailure): upgrade legacy opencode stream-ended reasons (MUL-5831) Rule 7 only decides where these failures land when THIS server classifies them, and FailTask classifies only when the daemon sent no reason at all. An installed daemon predating that entry reports a non-empty agent_error.process_failure instead — its rule 13 matching the word "signal" in "terminal signal" — so the empty-reason branch skips it and the run stays off the retry allowlist. The fix reached only hosts that happened to update, which is the installed-daemon cadence problem NormalizeDaemonReason exists for. Upgrade process_failure / unknown / coarse agent_error to provider_network when the error opens with the guard's own prefix. Wider than the context-overflow rule above it on purpose: that one leaves refined reasons alone because a phrase somewhere inside an error blob says less than the bucket an earlier rule picked, whereas this witness is the guard's message from its first character, so the old bucket cannot be describing a better-identified cause. The retry test becomes a matrix over both wire shapes — current daemon (server classifies) and each legacy reason — since the previous version called Classify directly and never crossed the normalize boundary where the gap was. A negative case pins the prefix: an unrelated crash that merely mentions the phrase keeps the daemon's label. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
96d567d517 |
fix(agent): pair failed tool results for OpenCode (MUL-5802) (#6497)
Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
0066ab259e |
refactor(agent): drop unreachable inline system-prompt branches (MUL-5392) (#6050)
* refactor(agent): drop unreachable inline system-prompt branches (MUL-5392) The daemon only populates ExecOptions.SystemPrompt for openclaw, kimi and traecli (providerNeedsInlineSystemPrompt); every other backend receives the runtime brief as a per-task context file in the workdir. The inline branches in claude, codex, opencode and pi were therefore dead, and read as if they were the live delivery path. Probed each backend over its real launch path with a canary in the context file and no inline delivery — claude 2.1.220 (CLAUDE.md), codex 0.144.6 via the app-server, opencode 1.17.7, pi 0.67.2, hermes 0.18.2 via ACP — and all of them picked the brief up from disk, so the branches were removable with no behaviour change. An empty workdir returned no canary, confirming the probe could fail. opencode's branch was worse than dead: `opencode run` has no --prompt flag, so enabling inline delivery there would have made every opencode task exit 1 with a usage dump. The DevEco backend, forked from opencode, already documents this constraint; opencode itself never got the fix. Regression tests pin all three arg builders against re-adding the flag, and providerNeedsInlineSystemPrompt now documents what was verified and what is still unprobed (grok, qoder, codebuddy). Hermes and kiro are untouched: their exclusion is deliberate and already tested. Co-authored-by: multica-agent <[email protected]> * test(agent): pin codex developerInstructions contract, drop stale pi flag doc Review follow-up on MUL-5392. buildPiArgs' doc comment still advertised --append-system-prompt after the branch that emitted it was removed — exactly the stale-signal this PR set out to delete. The two codex sites fixed to a literal nil had no regression test, so restoring nilIfEmpty(opts.SystemPrompt) would still have gone green. Both thread/start and thread/resume now run with a canary SystemPrompt and assert developerInstructions comes through as an explicit null. Mutation-checked: reverting either site fails its test. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
6455d390e2 |
fix(agent): fail opencode runs whose stream ends without a terminal signal (#5238)
* fix(agent): fail opencode runs whose stream ends without a terminal signal * fix(agent): treat step_finish(reason "tool-calls") as non-terminal in the EOF guard Review follow-up: step bracketing alone left a false-green window. A tool step closes with step_finish(reason "tool-calls") before its continuation step_start, so a stream that dies in that gap had no open step and still reported "completed". Parse part.reason (live-probed on opencode 1.17.16: tool loop emits reasons "tool-calls" then "stop") and keep the run non-terminal after a "tool-calls" finish until the next step_start. Any other reason — including absent, for older opencode versions that predate the field — stays terminal so healthy runs on old protocols are not mass-failed. Tests: tool-calls finish then EOF now fails; the multi-step happy path uses the real wire shape ending in reason "stop"; a reason-less step_finish (legacy protocol) still completes. * fix(agent): keep stop tool steps pending continuation Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
e2720f7d33 |
feat: add opencode thinking variants
Adds OpenCode model variant discovery for thinking controls, passes saved thinking_level through opencode run --variant, and hardens verbose model parsing with fallback coverage. |
||
|
|
34f16e2c7a |
fix(opencode): deny interactive questions in daemon mode (#2878)
* fix(opencode): deny interactive questions in daemon mode * fix(opencode): avoid permission env ordering bypass |
||
|
|
9a577f3e11 |
fix(runtimes): anchor OpenCode skill + AGENTS.md discovery to task workdir (MUL-2416) (#2849)
* fix(runtimes): anchor OpenCode skill + AGENTS.md discovery to task workdir OpenCode resolves its project discovery root from `--dir` and `PWD` before falling back to `process.cwd()`. The daemon set `cmd.Dir = workDir` but never overrode the inherited `PWD`, so OpenCode walked from the daemon's shell directory and silently bypassed the per-task workdir — agents lost visibility into `.opencode/skills/` and `AGENTS.md`, falling back to whatever global skills the host had installed (MUL-2416). - Pass `opencode run --dir <workDir>` and override `PWD=<workDir>` in the child env so AGENTS.md walk-up + `.opencode/skills` project config scan both anchor on the task workdir. - Block `--dir` from custom args so user overrides cannot re-introduce the regression. - Plumb skill `description` from DB through service / daemon / execenv. `writeSkillFiles` synthesizes a YAML frontmatter block (`name`, optional `description`) when the stored content lacks one, since runtimes like OpenCode silently drop SKILL.md files without a parseable `name`. Existing frontmatter is preserved unchanged so upstream-imported skills (GitHub / ClawHub / Skills.sh) keep their hand-shaped metadata. Tests: - New fake-CLI test confirms argv carries `--dir <workDir>` and the child sees `PWD=<workDir>`. - New test confirms a user-supplied `--dir` in custom_args is dropped. - New execenv tests cover synthesized frontmatter and preservation of pre-existing frontmatter. Co-authored-by: multica-agent <[email protected]> * fix(runtimes): inject SKILL.md `name` when upstream frontmatter omits it Skills imported with frontmatter that sets `description` but leaves `name` implicit (relying on the directory slug, as common in GitHub/Skills.sh imports) still hit OpenCode's "no parseable name → drop" path because the DB Name fallback never made it into the SKILL.md body. ensureSkillFrontmatter now scans the existing block and, when name is missing or empty, prepends `name: <slug>` while preserving description, body, and any runtime-specific keys verbatim. Also tighten yamlEscapeInline to always double-quote so descriptions that look like YAML keywords (`null`, `true`, `[foo]`, `{x: y}`, `2024-01-01`) parse as strings rather than getting reinterpreted and rejected. Adds regression test for the nameless-frontmatter case and updates the existing OpenCode skill test for the always-quoted description format. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
8b340fcf21 |
fix(agent/opencode): bypass npm .cmd shim on Windows to preserve multi-line prompts (#1718)
* fix(agent/opencode): bypass npm .cmd shim on Windows to preserve multi-line prompts The npm-generated `opencode.cmd` shim forwards argv via Windows batch `%*`, which silently truncates positional arguments at the first newline. The daemon spawns OpenCode with a multi-line prompt (system prompt + user message), so on Windows the agent only ever sees the first line and responds generically as if it never received the user's message (reported in #1717 with native-binary repro confirming the same prompt arrives intact when cmd.exe is skipped). When `runtime.GOOS == "windows"` and `exec.LookPath` returns a `.cmd` shim, walk to the native binary that npm bundles next to the shim: <prefix>\opencode.cmd <prefix>\node_modules\opencode-ai\node_modules\opencode-windows-x64\bin\opencode.exe If the native binary is missing (unusual install layout), keep the original shim path so PATH lookup still wins. The resolver is a pure function with an injectable `statFn`, so layout assertions are testable on Linux: - shim resolves to the bundled native binary - missing native returns "" (caller keeps original path) - non-cmd paths (Linux/Mac binary, opencode.exe direct, empty) skip resolution - uppercase `.CMD` is accepted (PATHEXT entries can be either case) Closes the user-facing failure mode without restructuring exec resolution across the rest of the agent backends — the other shim-aware fixes can follow the same shape if/when they land in similar repros. * fix(agent/opencode): cover x64-baseline and arm64 npm package variants `npm install -g opencode-ai` ships three Windows platform packages (opencode-windows-x64, opencode-windows-x64-baseline for older CPUs without AVX2, opencode-windows-arm64 for Surface / Copilot+ PC) and installs whichever matches the host. The previous resolver only knew about opencode-windows-x64, so baseline-x64 and arm64 hosts would fall back to the .cmd shim and hit the multi-line prompt truncation again. Iterate the three package candidates in GOARCH-preferred order. ARM64 hosts try arm64 first; everything else tries x64, then baseline, then arm64 as a last resort. Cost is one extra statFn call per miss when the GOARCH-preferred package isn't installed. Surfaced by review on #1718. * test(agent): add Windows counterpart to writeTestExecutable writeTestExecutable in exec_fixture_unix_test.go is referenced by claude_test.go / codex_test.go / kimi_test.go, but the //go:build unix constraint meant `go test ./pkg/agent` failed to build on Windows. ETXTBSY is a Linux/Unix fork-exec race; Windows doesn't have that pathology, so a plain os.WriteFile is sufficient. Lifted from #1719 (Codex) with attribution. Surfaced by review on #1718. |
||
|
|
36db325d50 |
feat(daemon): add opencode as supported agent provider (#341)
* feat(daemon): add opencode as supported agent provider Add opencode backend alongside claude and codex. The backend spawns `opencode run --format json`, parses streaming JSON events (text, tool_use, error, step_start/finish), and supports --prompt for system prompts. Includes CLI detection, AGENTS.md runtime config, native skill discovery via .config/opencode/skills/, and 21 tests covering handlers, JSON parsing, and integration-level processEvents scenarios. * chore: add .tool-versions to gitignore |