Skip to content

v0.4.0 — eliminate stderr-as-correctness + structured envelope for result

Choose a tag to compare

@linxule linxule released this 23 May 16:47
· 42 commits to main since this release

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

  • RuntimeError gains optional details field (JSON-serializable). formatError keeps human stderr readable; callers reading the structured payload now have a place to retrieve full context.
  • summarizeKimiAvailabilityWarning maps every classification kind explicitly. Previously response_timeout / max_steps_reached / initialize_timeout fell 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.jsonl for later reconciliation. A spawn worker can no longer leave a job in running without 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.details with stall_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 systemMessage explaining 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_DUPLICATES throws INVALID_ENV at startup. Absent → default (presence is the optional signal); present-but-malformed → loud failure.
  • setup strict arg parsing: unknown flags now throw INVALID_ARGS instead of being silently ignored by argv.includes(...).
  • result --json (new): `companion.sh result --json` emits a structured envelope:
    {
      \"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
    }
    Default `result` stays raw prose passthrough (no breaking change). `status` was already JSON by default — intentionally left unchanged.

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.