v0.4.0 — eliminate stderr-as-correctness + structured envelope for result
Summary
Continues the meta-principle that drove v0.3.6 and v0.3.7: stderr is invisible to LLM callers; anything load-bearing goes in stdout (structured), exit codes, or persisted SQLite state. v0.4.0 closes the stderr-to-details migration identified in the post-v0.3.6 audit plus introduces the first opt-in structured-output surface.
Sub-batches
A — Foundation
RuntimeErrorgains optionaldetailsfield (JSON-serializable).formatErrorkeeps human stderr readable; callers reading the structured payload now have a place to retrieve full context.summarizeKimiAvailabilityWarningmaps every classification kind explicitly. Previouslyresponse_timeout/max_steps_reached/initialize_timeoutfell through to a generic "failed unexpectedly" message; review-gate fail-open paths couldn't distinguish them from real outages.
B — Persist failure state
- Background-spawn state-failure now retries the SQLite write 3× with exponential backoff; if still failing, appends to
stuck-jobs.jsonlfor later reconciliation. A spawn worker can no longer leave a job inrunningwithout telemetry. - Rescue artifact-write failure now includes the raw Kimi output in
error.details.rawOutput. The agent caller reading the structured payload recovers the lost result; stderr dump preserved for humans.
C — Stderr-to-details migration
- Think-stall/loop diagnostics populate
error.detailswithstall_kind,duplicates_seen, payload-shape metadata. Callers reading structured errors now know why the stall fired — not just that it fired. - review-gate visible skip: when the transcript lacks an assistant message or is unreadable, the Stop hook returns
systemMessageexplaining the skip rather than silently allowing stop. Fail-open invariant preserved (AGENTS.md:55); user now sees what happened. SQLite persistence of skips deferred to a follow-up.
D — Config validation + setup + result --json
- Numeric env-var validation: malformed
KIMI_PLUGIN_CC_THINK_STALL_MS/THINK_LOOP_DUPLICATESthrowsINVALID_ENVat startup. Absent → default (presence is the optional signal); present-but-malformed → loud failure. setupstrict arg parsing: unknown flags now throwINVALID_ARGSinstead of being silently ignored byargv.includes(...).result --json(new): `companion.sh result --json` emits a structured envelope:Default `result` stays raw prose passthrough (no breaking change). `status` was already JSON by default — intentionally left unchanged.{ \"job_id\": \"uuid\", \"kind\": \"review\" | \"challenge\" | \"ask\" | \"rescue\" | \"review_gate\", \"status\": \"completed\" | \"failed\" | \"cancelled\" | \"running\", \"summary\": \"...\", \"error\": { \"code\": \"...\", \"stage\": \"...\", \"message\": \"...\", \"details\": {...} } | null, \"artifact_path\": \"/path/to/artifact.md\" | null, \"body\": \"<full markdown contents of the artifact>\" | null, \"created_at\": \"iso8601\", \"completed_at\": \"iso8601\" | null }
Agent files
`kimi-ask`, `kimi-review`, `kimi-challenge`, `kimi-rescue` each gain a one-line note pointing wrappers at `/kimi:result --json` for downstream automation.
Test coverage
265 tests pass (245 → 265, +20 new tests) plus a new `setup-command.test.ts` suite. Extended `errors`, `kimi-errors`, `kimi-launch`, `job-commands`, `rescue-command`, `parsing`, `review-gate-hook`, `think-stall-guard` suites.
How it landed
Triangulated audit (Codex, Kimi, Claude code-reviewer) on v0.3.6 surfaced 14+ findings; v0.3.7 shipped the 7 unambiguous bug fixes; v0.4.0 covers the harder 7 stderr-to-details items + the first structured-output surface. Implementation delegated to Codex with a per-item spec. One spec deviation caught mid-run (`--json` on status was redundant — status was already JSON-by-default); spec revised and Codex resumed cleanly. TypeScript exhaustiveness error on dead union members fixed by the main thread during the final release flow.