Skip to content

v0.9.2

Choose a tag to compare

@github-actions github-actions released this 04 Aug 21:52
· 1 commit to 3c3b7dca14ceaa86f2e4bf1a49b89ca74ac2b0ea since this release

0.9.2 (2026-08-04)

⚠️ Breaking

  • The HTTP API moved to /api/v1. Every route answers under /api/v1/… (project-scoped:
    /api/v1/p/<projectId>/…) and the WebSocket bus is /api/v1/ws; the unversioned /api/*
    spelling is gone. The bundled cockpit ships in lockstep, so a normal upgrade needs nothing from
    you — this only matters if you script the API directly, where the fix is adding /v1.
    GET /api/v1/health is still the CORS-open discovery endpoint, historical run transcripts keep
    rendering (old image URLs are upgraded when read), and saved bookmarklets are unaffected.
    Versioning is what lets the typed client describe the whole surface and makes a future v2 an
    additive mount rather than an edit to every route.

✨ Features

  • The two mixed-format routes do real HTTP content negotiation. GET /api/v1/repo/commit/:sha
    (legacy text blob or structured commit payload) and GET /api/v1/runs/:id/files (JSON listing or
    an image's raw bytes) now honour the request's Accept header, answer Vary: Accept, and set a
    Content-Type confirming what they actually sent. Purely additive: the ?structured=/?raw=
    flags still decide whenever the request carries one, */* (what fetch and curl send) is read
    as "no preference" and keeps each route's existing default, so every current caller's answer is
    byte-identical. What is new is that a client that really does ask — an <img>, a browser
    navigation — gets the other representation without the flag, under the same allowlist, size cap
    and sandbox CSP as before.
  • Finished tasks now carry a read/unread marker (#767). A done or failed run you have not
    opened since it finished reads as unread — its row is promoted (brighter, semibold) and wears a
    small trailing violet dot — while everything you have already seen dims back. The Tasks nav item
    shows how many are unread, opening a task's thread clears it, and a "Mark all read" sweep clears
    the lot. Unread is a deliberately separate channel from the status dot, which keeps saying
    done/failed, so "what happened" and "have I seen it" never collapse into one signal.

🔧 Changed

  • Every mutating route is now visible to the typed client, POST /api/v1/todos/:id/start included.
    Its body used to be parsed inside the handler to keep "unknown id 404s before the body is
    validated"; a small existence guard registered before the body validator keeps that status
    order while the body becomes part of the route type. A bodyless POST still 201s and a malformed
    one still 400s.
  • Validation errors (400 {error}) are worded differently and now name the field. Two causes:
    zod 4 rewrote its default messages (RequiredInvalid input: expected string, received undefined), and each issue is now prefixed with its path — task: must be at most 100000 characters where it used to be task must be at most 100000 characters for a handful of fields
    and an unattributed sentence for the rest. The { error: string } shape and the 400 status are
    unchanged
    , and the message was never a pinned contract (BACKWARD_COMPATIBILITY.md §2 pins the
    shape, not the text) — but a script matching on the exact wording will need updating, and the
    cockpit shows the new text verbatim in its toasts.
  • Every mutating route now validates its body as route middleware rather than inside the handler,
    and the query string / path params of 17 more routes are validated too. Behaviour is unchanged
    by design, including the tolerant cases (a body sent without a JSON content-type, a malformed
    body, and a repeated query key such as ?refresh=1&refresh=1, which still takes the first
    value). The point is that the typed client can now check request bodies, params and queries at
    compile time.

🐛 Fixes

  • 🐛 Running the test suite no longer wipes your project registry. A merge-write resolved
    ~/.cezar/config.json twice — once to read, once to write, after the await — and
    cezarHomeDir() re-reads CEZ_HOME on every call, so a test that lost its sandbox pin
    mid-flight (a timeout was enough) read the temp home and wrote the real one, replacing every
    project with the fixture's. The path is now resolved once per merge-write, the whole server
    suite runs with CEZ_HOME pinned to a per-worker sandbox, and a write into the real ~/.cezar
    from a vitest process is refused outright. The same one-path fix lands in the ui-state.json twin.
  • 🐛 The registry survives a lost config file. Every merge-write that leaves projects behind
    also writes ~/.cezar/config.json.bak, and cezar restores from that snapshot when the config
    file is missing, empty, or corrupt. Removing ~/.cezar still resets cezar completely; removing
    only config.json no longer loses the project list. A config that parses and is simply empty is
    left alone — that is a user who removed their last project, not a lost registry.
  • 🐛 Structured questions render as a form, not raw JSON (#757). When an agent asked a
    structured question, the Ask card could fall back to printing the raw JSON payload; it now renders
    the real question with its options, and long question text wraps instead of overflowing.
  • 🐛 Subagent sessions render like the main thread (#756). A subagent's transcript now goes
    through the same session renderer as the top-level thread, so its messages, tools and reasoning
    look identical instead of a stripped-down variant.
  • 🐛 The task diff stat stops counting a repointed HEAD's branch (#751). When a task's worktree
    HEAD was repointed onto another branch, the ± diff stat folded in that branch's whole history; it
    is now anchored at HEAD so it counts only the task's own changes, and the Changes tab says so when
    a repointed HEAD has narrowed what it shows.

Published packages

Package Version
@open-mercato/cezar 0.9.2
@open-mercato/cezar-api-client 0.9.2
cezar-cli 0.9.2

Install

npx cezar-cli@0.9.2