Skip to content

pi-warden 1.4.0

Choose a tag to compare

@github-actions github-actions released this 08 Oct 13:33
· 10 commits to main since this release
66f78d0

pi-warden 1.4.0 is the first release since 1.1.1. Versions 1.1.2, 1.2.0, 1.3.0 and 1.3.1 were not released on their own, so their notes are below too.

Heads-up: the new cause-check guard (1.3.0) is on by default. Set cause.enabled to false in your config to turn it off.

1.4.0

Added

  • When the session runs in a herdr pane, a warden confirm dialog now reports through herdr's herdr:blocked channel while it waits, so the pane's sidebar shows blocked instead of working. Interactive sessions only; no emission on hold-steers or headless runs. (Guards)

1.3.1

Fixed

  • The commands that send their own prompt to the agent (/warden init, /warden rules tune, /warden audit, /warden index) no longer swallow it. Each set its busy flag before sendUserMessage, and the busy-flag guard on the input event saw the command's own prompt (which arrives with source: "extension") and answered "handled", so no run ever started and the command reported the file (or report, or index) was never created — every time, deterministically. The guard now waits only for operator input (interactive/rpc); warden's own prompt passes and, like any user prompt, invalidates in-flight conscience assessments.
  • Those commands also no longer report before the run they injected ends. Pi's extension-facing sendUserMessage is fire-and-forget, so the await ctx.waitForIdle() that followed it could return before the injected run even flipped the session busy — the command reported while the agent was still working, or its status message was steered into the next run instead of recorded. Each command now waits in two phases: first for before_agent_start carrying the prompt it sent (30 seconds; on a miss — a preflight failure or another extension handling the input — it clears its flag and reports right away instead of blocking operator input), then for agent_end of that run with no cap (pi emits it on an abort too, so Esc still ends the wait), and finally for the host to go idle so the status message is recorded rather than steered.

Tests

  • A real-session e2e runs /warden init the way Pi does (the shipped entry in a real agent session with a scripted model): the injected prompt reaches the model, the agent's write call lands, and "pi-warden.md created" is recorded only after the run ended. Two unit tests pin the guard (extension input passes while a flag is set, operator input waits) and the wait (the command does not report before the run starts or ends). The session-test write stub now really writes, relative to the session directory, so the e2e can prove the file on disk.

1.3.0

Added

  • The cause-check guard (on by default, cause.enabled): when the final reply of a run states a likely cause, or asks a person to check something the agent could inspect itself, and no tool result in that run checked it, pi-warden sends the reply back with one follow-up turn: check the cause with its own tools (a query, a log, a file, or a command) and give the evidence, or say plainly that the cause is unverified and why it cannot check it. It is the sibling of the done-check. An offline pre-filter (causePreFilter) fires on causal and hand-off wording, so a reply about results or next steps spends no request; the guard asks Jev whether the reply states a cause or hands off a check, whether the run checked it, and, when the project has an earlier unchecked cause inside the window, which earlier cause the reply repeats. The request carries the task, the final message, the last 40 of the run's tool calls, and up to five earlier redacted cause sentences with their keys and dates. Each unchecked cause is stored per project for cause.windowDays (default 7) as the redacted sentence that states it; a later reply whose cause Jev matches to an earlier one gets a stronger steer that names the earlier date, and a reply that hands a check to a person gets a steer that says so. When the done-check steers the same reply, the cause-check records its decision in the trace and sends no steer, so one reply gets at most one end-of-run steer. The repeat history lives in a causes table in the same owner-only database as the hold log, and every request, stored summary, and trace line passes through redact().

Docs

  • docs/guards.md describes the new cause-check; docs/configuration.md lists its keys, widget template, and tokens; docs/data-handling.md and the consent notice name the cause-check request and its stored repeat history; README.md lists the guard next to the done-check.
  • eval/reports/2026-10-07-cause-check/ measures the offline pre-filter on recorded sessions: the totals, the flagged count, and how many of a 20-reply sample were real unchecked causes.

1.2.0

Added

  • The action guard reads a script a command runs from disk and applies the same pattern floor and ask gate to its body: bash FILE, ./FILE, source FILE and . FILE, npm/pnpm/yarn/bun scripts from package.json (with preNAME and postNAME), make TARGET or the default target, and node/python/python3/ruby/perl/deno run FILE. A body is read only when it resolves, after symlinks, inside the project root or a temp root, as a regular file of at most 64 KB, and one level deep. A body hit keeps the id and severity it has when typed directly and its label names the source ((via cleanup.sh), (via npm run clean)); action.exemptRules and user commandRules apply to it; a body hit makes the ask gate ask with runs a script with <shape>; and the body lines that hit ride the request to Jev as script_lines, redacted and bounded to five lines and 600 characters.

  • /warden status shows the build that runs: the pi-warden version, the checkout commit when the package is a git tree, the dist/extension.js build time, and the loaded pi-typesafe version. Session start warns once when a src/ file is newer than the build (pi-warden runs an old build: run npm run build) and once when the loaded pi-typesafe version is outside the range in package.json.

Changed

  • The done-check nudge names one check command instead of "whatever exists": the last check that passed earlier in this session in this project, else the check then test script of the project's package.json run with the package manager its lock file names (pnpm-lock.yaml, yarn.lock, bun.lockb or bun.lock, else npm), else the same Makefile targets; with none of those it keeps the generic ask. The UI nudge names the last local URL a tool result printed this session (http://localhost:PORT, 127.0.0.1, [::1]) when there is one.
  • Done-check UI proof now counts only what shows the page to the agent: a successful tool result that contains an image, a page text snapshot (agent-browser snapshot, agent-browser get text, the MCP take_snapshot tool), a browser or device test run (playwright test, flutter test with integration_test or golden tests), or a read of an image file. A screenshot command that only writes a file (agent-browser screenshot PATH, idb screenshot, xcrun simctl io … screenshot, chrome --headless --screenshot), agent-browser open/close/click/fill/set/wait/eval, and MCP navigation tools no longer count; a saved screenshot counts once something reads it. The done.visualTools defaults match, and every config key still works.
  • pi-typesafe ^0.9.1 (was ^0.9.0).
  • npm run check builds before it tests, so the offline suite exercises a fresh dist/ (the entry Pi ships) instead of a stale one.

Fixed

  • The done-check counts a shell command as a check only when a check runner starts one of its shell segments, after cd DIR, environment assignments, and timeout/time/env/npx/pnpm exec/bunx/yarn/uv run/poetry run/python -m wrappers (a wrapper is stripped only when what follows does not start a runner, so yarn test and yarn run lint are checks). A runner name in an argument (grep -n jest package.json, which eslint, npm view vitest version) no longer counts as a check.
  • A check run counts as proof only when it really ran and its output agrees: a failure summary is a failed check even with exit code 0, and a visible failure stays a failed check also with a passing summary; a summary that reports zero tests (Tests: 0 total, [no tests to run], and similar) is never a check, unless a line of the output shows tests did run (cargo doc-tests after real results, a package a go test filter skipped); and when the shell can hide the exit code of any runner segment (a pipe right after its segment without set -o pipefail, or a ||, ;, or newline with more commands anywhere after it) the exit code is no proof — with no runner summary the run is not a check at all. Each run that names a runner but does not count gets one trace-only detail with the reason and no command; the reason names the word after the hiding separator only when it is a bare command name.
  • The done-check reads more runner summaries as proof: vitest, pytest without the === frame (-q), mypy, ruff (check, format, and --fix), black, prettier, biome, tsc pretty output, eslint, bun, mocha, playwright, flutter, deno, and cargo build, check and clippy; and the generic wrapper failure lines (make: *** … Error N, npm error Lifecycle script … failed, npm ERR! code ELIFECYCLE, pnpm and yarn Command failed with exit code N). The generic lines decide only when a runner starts the command, so a failed npm install is not a failed check. A failure marker anywhere in the tail wins over a passing one (black and ruff format N files would be reformatted beside N files would be left unchanged., any go FAIL line, any cargo test result: FAILED. after an ok, pytest 2 passed, 1 error, an indented or pkg:test:-prefixed vitest summary), a generic failure line overrides a passing summary in the same output, and the Finished line proves a cargo build, cargo check or cargo clippy segment only.
  • The done-check recognises more runner forms and reports: a runner started by a path (.venv/bin/pytest, node_modules/.bin/tsc), python3.12 -m and .venv/bin/python -m followed by a runner or unittest, wrappers with options (timeout -s KILL 60, uv run --with httpx, npx -y), package-manager options (pnpm -C web test, npm --prefix web test, yarn workspace api test), make -C web test, and bash -c "npm test"; the last exit code the agent printed with echo/printf ($?, or ${PIPESTATUS[0]} after a pipe); a silent runner (tsc, eslint, flake8, go vet, go build) named in its own segment whose output is empty behind a pipe to tail, head, tee, or cat; and a hidden runner is covered only by its own printed exit code, its own silent pass, or a summary verdict. A command is first stripped of data text and a backslash-newline continuation is joined, so a runner word in a commit message or heredoc body is not a check. A bare npm ci or bun ci installs and does not count, a runner sent to the background with & does not count, and a visible failure stays a failed check when another runner's exit code is hidden.
  • With pi-typesafe 0.9.1 the usage ledger no longer loses counts when several Pi sessions run at once, so the reported spend and the daily caps count every session.

Tests

  • A new offline session test loads pi-warden the way Pi does (package.json → pi.extensions → extensions/index.js → dist/extension.js) into a real agent session with a scripted model, and asserts on the session's messages: a held destructive bash call, a done-check nudge after an unverified completion, and the repeat note after the same failing read twice. It fails with run npm run build first when the shipped entry is missing.

Docs

  • docs/commands.md describes the /warden status build line; docs/ci-cd.md states the new npm run check order (typecheck, build, offline tests).

1.1.2

Fixed

  • The build removes dist/ outputs whose src/ source is gone. tsc never deletes an output whose source was removed, so stale files from other branches shipped with 1.1.1: 17 files (conscience-config, conscience-loader, conscience-policy, conscience-privacy, jev, laya-download, laya-judge, tool-recommendation, each as .js and .d.ts, plus extension.js.bak) that nothing imports. Users need to do nothing.