mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-09 01:46:46 +08:00
* fix(daemon): give environment preparation failures their own failure reason A failed execenv.Prepare / Reuse is a filesystem error on the daemon's own host, but handleTask routed it through taskfailure.Classify — a classifier written to read agent and provider output — so a full disk, a denied permission or a directory held open by another process all landed in agent_error.*: the catchall today, provider_server_error on the report that opened the issue and sent hours of diagnosis at an LLM vendor. The daemon now tags those errors structurally (environmentSetupError) and reports the new platform-side reason environment_prepare_failed. The four existing sentinels still win: task identity, the prepare budget, skill downloads and the OpenClaw CLI deadline each name a specific cause inside the same phase. Installed daemons update on their own cadence, and they report a non-empty reason that FailTask deliberately leaves alone, so NormalizeDaemonReason upgrades any agent_error.* value whose error text opens with the prepare / reuse wrapper. That witness proves the run died before an agent existed, so every label in that namespace is wrong by construction. Not retryable and resume-safe: a full disk reproduces identically on the next attempt, and no session was touched. Closes #7913 Co-authored-by: multica-agent <[email protected]> * fix(mobile): parse failure_reason as an open string, and match copy to real scope Review follow-up on #7915. AgentTaskSchema pinned failure_reason to the six pre-MUL-1949 coarse values with `.catch("")`, so every refined reason was erased to undefined before any surface read it. run-row's badge map was widened in MUL-5370 and has been unreachable ever since: agent_error.*, skill_bundle_unavailable and the new environment_prepare_failed all rendered a bare "Failed". The field is an open string on the wire (packages/core/types/agent.ts) and in the chat message schema beside it; the task schema now matches, and the badge map moved to lib/run-failure-badge.ts so mobile's node-only vitest lane can cover the lookup a component test cannot reach. execenv.Prepare is not only filesystem work — it also writes and validates the per-provider local config (Codex home, Hermes overlay, Cursor MCP, and an OpenClaw config it fails closed on), so a malformed local config lands on this reason too. The Go docs, the chat copy in four locales and the docs table said "disk, permission, directory in use", which would send the reader past half the real causes — the same misdirection this reason exists to end. They now point at the raw error first and name the config case, with regression tests for a non-filesystem preparation failure on both the daemon and the legacy wire path. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]>
55 lines
2.2 KiB
TypeScript
55 lines
2.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { runFailureBadgeLabel } from "@/lib/run-failure-badge";
|
|
import { AgentTaskSchema } from "./schemas";
|
|
|
|
function parseFailedTask(failure_reason: unknown) {
|
|
return AgentTaskSchema.parse({
|
|
id: "task-1",
|
|
status: "failed",
|
|
failure_reason,
|
|
});
|
|
}
|
|
|
|
describe("AgentTaskSchema failure_reason (#7913 regression)", () => {
|
|
// The field was a closed six-value enum with `.catch("")`, so every refined
|
|
// reason the backend has written since MUL-1949 was erased to undefined
|
|
// before any surface could read it. The badge map was widened in MUL-5370
|
|
// and has been unreachable ever since — the parser, not the map, was the
|
|
// reason a failed run said only "Failed".
|
|
it("keeps a refined reason the backend classifies today", () => {
|
|
expect(parseFailedTask("agent_error.provider_network").failure_reason).toBe(
|
|
"agent_error.provider_network",
|
|
);
|
|
expect(parseFailedTask("environment_prepare_failed").failure_reason).toBe(
|
|
"environment_prepare_failed",
|
|
);
|
|
});
|
|
|
|
it("keeps a reason newer than this build", () => {
|
|
// The taxonomy grows on the backend's cadence and installed builds lag it.
|
|
// Dropping an unknown value here would re-create the dead-map bug the next
|
|
// time a reason lands, which is the whole point of parsing it open.
|
|
expect(parseFailedTask("some_future_reason").failure_reason).toBe(
|
|
"some_future_reason",
|
|
);
|
|
});
|
|
|
|
it("still normalizes the not-failed sentinel to undefined", () => {
|
|
// Go's `omitempty` sends "" on a task that did not fail. Downstream truthy
|
|
// checks depend on that becoming undefined rather than an empty string.
|
|
expect(parseFailedTask("").failure_reason).toBeUndefined();
|
|
expect(parseFailedTask(undefined).failure_reason).toBeUndefined();
|
|
expect(parseFailedTask(42).failure_reason).toBeUndefined();
|
|
});
|
|
|
|
it("hands the run row a value its badge map can name", () => {
|
|
// The end-to-end shape of the bug: parse, then look up. Either half alone
|
|
// passed while the run row still rendered a bare "Failed".
|
|
const task = parseFailedTask("environment_prepare_failed");
|
|
expect(runFailureBadgeLabel(task.failure_reason)).toBe(
|
|
"Environment setup failed",
|
|
);
|
|
});
|
|
});
|