Skip to content

v0.14.0

Choose a tag to compare

@frapetti-dev frapetti-dev released this 02 Oct 08:50
· 68 commits to main since this release
9a8319f

Added

  • Subagent gate (omp only; pi behavior is unchanged): omp runs subagents in-process with no UI, so every ask inside a subagent used to degrade to deny. New config keys subagentGate ("off" default | "normal" | "auto") and subagentAskTimeoutMs (positive integer ms, default 60000) route subagent asks to the root session's UI. "normal" shows the usual confirmation dialog on the root UI, prefixed with a [subagent <id> (<name>)] label; if nobody answers before subagentAskTimeoutMs (counted from enqueue, queue wait included) or the root has no UI, the ask is resolved without a human. "auto" never prompts and always resolves without a human. "off" (the default) makes the gate inert in subagents: no rule, floor or classifier check runs on a subagent's tool calls — opt in with "normal" or "auto" to gate them. Resolving without a human consults classifierFallbackModel only for asks the classifier raised, and only an explicit allow from it permits the call — anything else denies, with block tag subagent-auto; protected-path / .omp asks and asks that exist only because of autoDeny: false always deny (ADR-0002: no path plaintext reaches notifications or the agent), and a demoted first-layer deny is never auto-allowed (ADR-0004). A cancelled subagent run closes its pending root dialog and blocks with subagent-cancelled without consulting the second model. Root dialogs are now serialized process-wide (omp queues confirm but not custom). With audit: true, subagent asks record subagent: { id, name, resolution: "human" | "timeout" | "auto" }, and second-model resolutions record fallback.triggeredBy: "subagent-ask". Set omp's extensionHandlers.toolCallTimeoutMs to at least subagentAskTimeoutMs + 60000 — a subagent waiting on the root UI does not pause its handler budget.

  • The first-run config template pre-fills a starter tools allowlist (ask, todo, wait, task, yield, think, checkpoint, rewind, recall, reflect): omp tools with no side effect of their own, or whose effects are gated elsewhere, skip the classifier from the first session after the initial run. Tools that read paths outside the file-tool family (glob, ast_grep, lsp), send data off-machine (web_search), persist memory, execute code or mutate state are deliberately left out. A pre-filled user declaration, not a built-in allowlist; existing configs are never rewritten.

  • gateOmpDir (default true): forced .omp gate. Any file-tool path or bash command that touches a .omp directory (path segment .omp, matched on the lexical and symlink-resolved forms; bash also matches .omp as a bare word, e.g. cd .omp) is a terminal ask — non-interactive sessions degrade to deny. Pipeline position: after the built-in floor and user deny, before denyPaths and user allow (an allow regex cannot skip it). The ask reason carries no path (UI-only detail). grep/find/ls are checked on their own target only. Set false to disable; /verdict now has a gateOmpDir on/off switch (project files can also unset it to inherit the global value).

  • /verdict [user|local] command: edit the list keys of pi-verdict.json (allow, deny, denyPaths, tools, rules) from the TUI — add, edit, and remove entries. user targets <agentDir>/config/pi-verdict.json, local the project config (<project>/.pi/pi-verdict.json, .omp/… on omp); a bare call asks which. Regex entries (allow/deny) are validated before saving. Each save rewrites only the edited key (other keys, key order, and _hint are preserved), reloads the rules into the running session immediately without clearing shadow-cache or fallback session stats, and never auto-trusts a project. The first project-level edit of a key offers to start from a copy of the global list (a project list replaces, not extends, the global one); × Unset removes the override so the project inherits the global list again. Other scalar keys (builtinDenyFloor, classifierModel, toggleShortcut, …) are out of scope. Requires an interactive UI.

  • Rich approve dialog in the interactive TUI: the code under review (bash command, write content, edit newText blocks) is rendered with the chat's Markdown renderer as a syntax-highlighted fenced block (capped at 40 lines / 4000 characters; control and bidi characters shown as \uXXXX escapes). When the ask comes straight from jev, a colored bar graphs its allow/ask/deny distribution with a confidence legend, plus a concern: line. RPC mode and hosts without ui.custom keep the plain-text confirm unchanged.

  • jev backend: each decisions request now also carries a typed concern question (deletion, write outside the project, network, package install, environment change, credentials, untrusted code execution, other, none); the answer is appended to the reason as — concern: <label> (cosmetic — a missing or malformed answer is ignored, only the verdict stays fail-closed).

  • EXPLAIN-GATE role and two new options on the interactive ask dialog (rich TUI dialog only; RPC mode and hosts without ui.custom keep the plain Yes/No confirm unchanged). Explain… takes an optional free-text question and calls the EXPLAIN-GATE role — a model call (session model by default) whose system prompt is fixed and whose default task is Explain what this action does and why the gate held it for confirmation.; a non-empty question replaces the default task. The model sees the transcript (same sanitized window as the classifier), the full code under review and the gate's stated reason; its answer is rendered in the re-opened dialog, labelled advisory, and never reaches the agent, the verdict, the audit log or the classifier. It can be asked repeatedly before deciding. No, with explanation… declines and forwards the user's text (single line, sanitized, ≤1000 characters) to the agent in the block reason (user declined, saying: "…"); plain No and Escape are unchanged, and Escape in either follow-up prompt returns to the dialog. Explain is not offered for protected-path asks (denyPaths hits and the .omp gate): their path plaintext is UI-only and must not reach a model provider (ADR-0002); No, with explanation… is available there. New config keys explainGateModel (provider/id[:thinking], default: session model; unavailable → session model with a one-time warning) and explainGatePrompt (replaces the default task); both can be set per project.

Changed

  • BREAKING: trustedProjects in pi-verdict.json is replaced by an interactive project trust prompt plus a gate-owned trust file, <agentDir>/config/pi-verdict-trust.json ({ "trusted": [...], "untrusted": [...] }). A project config (<dir>/.pi/pi-verdict.json, .omp/… on omp) is applied only when its project root is trusted; an interactive session starting in an undecided project asks Trust / Not now / Never. Headless sessions and subagents never prompt and ignore undecided project configs. Existing trustedProjects entries are no longer read, so affected projects prompt once.

Fixed

  • jev adapter on pi 0.84: host detection no longer relies on registerProvider.length (pi's wrapper is now (providerOrName, config), arity 2, so pi was routed into the omp registration branch and every jev classifier call failed closed with No API provider registered for api: jev-decisions). omp is now recognized by its API object carrying logger and typebox; behavior on omp is unchanged.