- In `resolveApproval`, yolo mode now returns the user policy directly (`allow`/`prompt`/`deny`) and ignores tool `override` prompts.
- Updated approval-mode and approval unit tests to match the new behavior for critical bash patterns under yolo and auto-approve.
- Updated docs and settings metadata to describe yolo as user-policy-driven rather than override-driven.
- Added `ToolTier`, `ToolApproval`, and `ToolApprovalDecision` types and exported approval APIs.
- Updated approval-mode options from `auto|prompt|custom` to `always-ask|write|yolo` and defaulted mode to `yolo`.
- Changed approval resolution to apply per-tool decisions first, then mode-tier limits, with legacy-mode migration.
- Assigned read/write/exec `approval` and approval-detail prompts across built-in, custom, extension, and MCP tools.
- approval: user 'tool: deny' now wins over critical-pattern override
(the override only tightens allow->prompt; it must never re-arm a denied tool).
- approval: rename hindsight policy keys to match registered tool names
(recall/retain/reflect, not hindsight_recall/hindsight_retain).
- approval: head+tail truncation for bash/ssh command prompts so a
destructive suffix buried after a long benign preamble stays visible.
- task/executor: force tools.approvalMode='auto' in createSubagentSettings
so subagents (which have no UI) cannot deadlock on per-tool prompts;
the parent's approval of the task call is the authorization.
- docs/approval-mode: rewrite so every example surfaces tools.approvalMode
and explains that tools.approval is ignored outside 'custom' mode.
- Removed per-session run queues from JS and Python backends, allowing async cells on the same session id to interleave.
- Introduced `getEvalSessionId` on ToolSession so subagents spawned via `task` inherit the parent's executor id and share JS VM and Python kernel state.
- Switched JS runtime state from module-level fields to AsyncLocalStorage so concurrent runs route output and tool calls to their own context.
- Changed Python runner to an asyncio event loop with per-request tasks and ContextVar-based run id tracking for concurrent execution.
- Added mtime-based module cache eviction to preserve singleton state across re-imports of unchanged local files.
- Move settings.get("task.showResolvedModelBadge") out of appendAgentStats
and into its callers, passing the value as an explicit opts field.
This removes a hidden dependency on global state and makes the
function a pure formatter.
- Sanitize resolvedModel with replaceTabs() + truncateToWidth(30)
before rendering, consistent with the file's existing sanitization
patterns and AGENTS.md requirements.
- Unified output schema construction and validation by adding buildOutputValidator and using it in YieldTool and task executor.
- Added MAX_SCHEMA_RETRIES so YieldTool now retries schema failures three times with hints before overriding.
- Updated failure handling to use shared summarizeValidationFailure and formatters for required-field reporting.
- Added tests for output-schema-validator and YieldTool covering malformed schemas and nested-array retry edge cases.
Combined task.enableLsp with the parent session enableLsp gate before spawning subagents, so --no-lsp remains authoritative even when subagent LSP is enabled in settings.
Fixes#1385
Added task.enableLsp (boolean, default false) and routed both regular and isolated subagent dispatch through it. Keeps subagents cheap by default while letting users opt in to LSP-aware delegation. Updated regression tests to cover the default-off, opt-in, plan-mode, and isolated paths.
Fixes#1385
Subagents now inherit the parent session's enableLsp value, so a top-level --no-lsp invocation propagates into spawned tasks instead of falling back to the executor's default of true.
Fixes#1385
Removed the hardcoded subagent LSP disable flag so executor defaults and user settings control LSP availability. Passed the effective plan-mode agent definition into both regular and isolated subagent dispatch so plan-mode tool restrictions apply consistently.
Fixes#1385
Queued extension-delivered user messages when deliverAs is set and waited for session_start extension message sends before prompting subagents.
Fixes#1343
- Captured `tool_execution_update` snapshots for `task` calls into in-flight progress state for live nested rendering.
- Cleared in-flight task snapshots at task start and completion to prevent stale nested progress from persisting.
- Updated progress rendering to combine completed and in-flight task details through a dedicated nested task tree view.
The report_finding tool's priority is exposed as a string enum
("P0"-"P3") for ergonomics, but the reviewer agent and every
custom review agent declare priority as `type: number` in their
JTD output schema. The cast at executor.ts:1473 lied about the
runtime shape, so the auto-injected `findings[].priority` flowed
through as strings and every yield with at least one finding was
rejected with `findings.0.priority: expected number, received string`,
forcing the run into the schema_violation exit path.
Added `toReviewFinding(details)` in tools/review.ts that maps the
priority enum to its numeric ordinal via the existing PRIORITY_INFO
table and use it at the boundary in executor.ts. Render paths still
see the original `ReportFindingDetails` shape (string priority)
through normalizeReportFindings, so display formatting is unaffected.
Fixes#1350
- Added `retry.maxDelayMs` to the settings schema and interfaces, with a default cap for provider backoff delays.
- Updated session auto-retry logic to fail fast when a requested wait exceeds the cap without fallback, emitting terminal auto-retry failure state.
- Propagated retry state and failure data into task progress and rendering so children show retry/wait details and reminder prompts stop after terminal errors.
Adds optional autoloadSkills field to agent frontmatter that automatically loads listed skills when a sub-agent is spawned. Uses the same buildSkillPromptMessage + sendCustomMessage mechanism as interactive skill loading, queued via sendCustomMessage({ triggerTurn: false }) before the first session.prompt(task). No extra agent turns, no new injection path. Skills stay in listing for sub-resource access. Compaction behavior matches manual loading. Unknown skill names silently skipped.
Lore-id: f85fdbdc
Constraint: autoload must use buildSkillPromptMessage + sendCustomMessage, never modify systemPrompt or use contextFiles
Constraint: triggerTurn must be false to avoid extra agent turns
Rejected: append to systemPrompt | agent cannot distinguish skill content from own instructions
Rejected: contextFiles injection | agent sees opaque file blob, cannot discover sub-resources
Rejected: promptCustomMessage per skill | N extra agent turns with model inference
Directive: autoload skill names are resolved against parent session skill list at spawn time in task/index.ts
Tested: TypeScript compiles clean with tsc --noEmit
Tested: parseAgentFields parses array and CSV string frontmatter
Tested: parseAgentFields returns undefined for absent and empty fields
Not-tested: bun test cannot run locally due to missing pi_natives native addon (requires Rust toolchain)
Confidence: high
Scope-risk: moderate
Reversibility: clean
buildOutputValidator already exists in task/executor.ts but only ran on
the fallback JSON parse path. Subagents that called yield directly with
a data payload skipped validation entirely, so a schema-conforming-
empty-object (e.g. `{}` against a schema requiring `findings`) was
returned as a successful task.
finalizeSubprocessOutput now invokes the validator on every yield path
and on the fallback completion path. On failure the result carries
error="schema_violation", a typed message, the missing required field
list, and a truncated preview of the offending data. exitCode=1 and
isError=true so existing consumers in task/index.ts surface it as a
failed task without code changes.
Both empty-patch and whitespace-only-patch branches in the isolated-task
merge unconditionally appended "Applied patches: yes". The marker now
fires only when the combined patch is non-empty AND applyText succeeded.
No-op merges report "No changes to apply.". The renderer marker matcher
accepts the new string.
The "Started N background task jobs" announcement listed the per-task
label (e.g. 7-MyScout) without the underlying jobId, but JobTool.execute
looks up by jobId. Append (job: ${jobId}) to each task entry and accept
the task label as a fallback alias in JobTool.execute.
renderDescription filtered the listing only by disabledAgents. When a
subagent had parentSpawns empty the model still saw the full menu, tried
to spawn oracle, and got "Cannot spawn 'oracle'. Allowed: none". The
listing now intersects with the allowed-spawn set.
- Added a new `oracle.md` prompt defining a read-only diagnostic, architecture, and debugging advisor.
- Imported `oracle.md` into the agent prompt loader in `packages/coding-agent/src/task/agents.ts`.
- Included `oracle.md` in `EMBEDDED_AGENT_DEFS` so the new agent is available at runtime.
- Replaced all StringEnum(...) usages with z.enum([...]) across tools, examples, and tests.
- Removed StringEnum re-export from @oh-my-pi/pi-coding-agent public API.
- Condensed verbose tool parameter descriptions to minimal lowercase phrases.
- Renamed AuthCredentialStore to SqliteAuthCredentialStore at usage sites.
- Runtime-limit timeout is now tracked with a sticky runtimeLimitExceeded flag, so later caller aborts during teardown cannot downgrade the timeout state and let a late yield report success.
- Awaited setup operations before the first prompt are now raced against the subagent abort signal, including auth discovery, model refresh, model resolution, session open, session creation, extension session_start, prompt, and waitForIdle.
- Added defensive abort re-check after registering the abortSignal listener plus a checkAbort() immediately before await session.prompt(...), so a wall-clock timer that fires during pre-prompt setup is no longer lost between listener registration and the prompt call.
- Late yield events arriving after a wall-clock timeout no longer flip the result to success: a runtimeLimitExceeded flag derived from the internal abortReason forces wasAborted=true and exitCode=1 regardless of hasYield, while yield payloads remain captured in extractedToolData.
- Async task progress now copies contextTokens and contextWindow from the completed SingleResult onto AgentProgress, so backgrounded tasks still surface their context gauge to the UI.
- Replaced fromTypeBox conversion with a JSON-schema validator flow in ai tool handling and execution paths.
- Added recursive schema validation and expanded TypeBox checks for refs, enums, uniqueItems, and constraint keywords.
- Sanitized Azure/CCA tool schemas by dropping unsupported fields and rewriting oneOf tool branches as anyOf.
- Tightened argument and model-config validation, preserving unknown tool fields and adding apiKey plus compatibility flags.
- Added canonical `pi.zod` schema API exports and removed TypeBox package exports/imports.
- Migrated Tool schema typing from TypeBox to shared `TSchema`/Zod flow with legacy TypeBox compatibility.
- Updated AI provider adapters and MCP/agent builders to convert tool params through `toolWireSchema()`.
- Reworked schema validation from AJV to Zod-safe parsing with `fromTypeBox`, `toolWireSchema`, and meta schema checks.
- Added a task.maxRuntimeMs setting with a default disabled state for per-subagent runtime limits.
- Updated subprocess execution to enforce the configured wall-clock timeout, mark timeouts as aborts, and include timeout-specific abort messaging.
- Tracked per-turn context size and context window through task progress/results and updated UI renderers to display current context against window with cumulative tokens rendered separately.
- Task tool sessions now expose and forward parent OpenTelemetry config when creating subagent tasks.
- Subprocess execution now derives child telemetry from the parent config with the subagent identity and child session conversation handling.
- Subagent creation now records a handoff span using the resolved parent telemetry handle before running the child loop.
- Added a shared session command helper that aggregates extension, prompt, and skill slash commands.
- Updated ACP, extension UI, runtime-init, and task executor extension contexts to return session command data instead of empty arrays.
discoverAgents() called listClaudePluginRoots() unconditionally, so agents
from Claude Code marketplace plugins appeared in /agents and the Agent
Control Center even when claude-plugins was listed in disabledProviders.
Guard the listClaudePluginRoots() call with isProviderEnabled("claude-plugins"),
returning an empty roots array when the provider is disabled — matching how
filterProviders() handles every other capability's provider set.
Added regression test that verifies both the enabled path (agents visible)
and the disabled path (agents absent) using a real temp-directory plugin
registry fixture.
Fixes#1075
The async job completion path copied durationMs, tokens, and extractedToolData
from SingleResult back into the AgentProgress object, but missed cost.
Add progress.cost = singleResult?.usage?.cost.total ?? 0 to the same block.
Eliminates the three-way duplication of toolCount/tokens/cost stat
appending across renderAgentProgress (running), renderAgentProgress
(completed), and renderAgentResult.
renderAgentResult (rendered after the task tool resolves) was missing the
cost display present in renderAgentProgress. Read cost directly from
result.usage?.cost.total which is already available on SingleResult.
Token counter (token_total status-line segment, subagent progress tree,
session-observer stats line) previously included cacheRead in its cumulative
sum. With Anthropic prompt caching, cacheRead per turn equals the full cached
context, so summing across N turns gives N*context_size -- a session with a 1M
context and 5 turns showed ~5M tokens despite no compaction occurring.
Fix: display shows input + output + cacheWrite per turn. cacheWrite is kept
because each byte is written once; cacheRead re-reads the same context every
turn. Dedicated cache_read/cache_write status-line segments still show cache
activity; billing cost is unaffected.
Also adds per-subagent cost display (dollar amount, statusLineCost color)
accumulated incrementally from message_end events. Hidden when cost is zero
(subscription/OAuth providers). Brings token and cost display in line with
what Claude Code shows per-agent.
- Flattened newlines and tabs in summary explanations before generating the review preview text, and trimmed the extracted sentence before truncating.
- Sanitized finding titles during render by normalizing tabs/newlines to spaces after stripping priority prefixes.
- Updated the task tool output to list newly started background jobs by live task id with optional descriptions.
- Extended the async task prompt guidance to distinguish IRC-enabled versus standard coordination and cancellation behavior.
Adds `pi.on("credential_disabled", handler)` so extensions can react to
soft-disabled credentials (e.g. OAuth invalid_grant) without regex-matching
`agent_end` errorMessages.
`AuthStorage.onCredentialDisabled(listener)` returns an unsubscribe function;
multiple listeners fire for every event with per-listener exception isolation
and FIFO buffer-and-replay (cap 32) when none are attached. The constructor
option from #991 stays as sugar for an immediate permanent subscription.
`createAgentSession()` subscribes the per-session extension runner to
`modelRegistry.authStorage` immediately after resolution and unsubscribes on
dispose / startup failure. Events are forwarded via
`ExtensionRunner.emitCredentialDisabled(event)`, which buffers (cap 32,
drop-oldest) until `runner.initialize(...)` runs in the mode controller so
extension handlers see real UI/runtime context, not the constructor no-op
defaults.
Supersedes #997. Builds on #991.
Co-Authored-By: omp <noreply@oh-my-pi.dev>
- Added untracked worktree baseline capture via `untrackedPatch` and synthetic tree diffing.
- Added fallback-aware backend ordering by collecting host candidates and retrying alternates on unavailable PAL.
- Hardened overlay mount lifecycle by removing stale overlays and deleting upper/work dirs after unmount.
- Refined ZFS clone deletion to validate ownership and remove dataset+origin only when checks pass.
- Updated ProjFS integration to use extended-info callbacks and symlink metadata reads.
- Updated rcopy path handling to absolutize paths, and added `writeTree` plus combined shell timeout checks.
- Added unified isolation primitives (BackendKind, ProbeResult, IsoError) and resolve fallback selection logic.
- Added Diff, FileChange, and ChangeKind with default_diff choosing git mode or filesystem walk based on repository state.
- Added APFS/btrfs/zfs/reflink/overlayfs/projfs/rcopy/block-clone backends with platform-aware start, stop, and probe.
- Mapped backend operations to canonicalized paths, recursive clone helpers, rollback cleanup, and unavailable error mapping.
- Added unified native exports isoBackend/isoProbe/isoResolve/isoStart/isoStop/isoDiff and removed projfsOverlay APIs.
- Replaced task isolation resolver flow with ensureIsolation/cleanupIsolation and migrated mode handling to auto plus legacy-mode aliases.
- Added ownerId metadata to async jobs and to task/bash progress items from the session agent id.
- Extended async job registration and query methods with optional owner filters, and updated cancelAll to target matching owners.
- Updated session handoff and disposal so subagents inherit the parent manager, top-level sessions own it, and teardown cancels own jobs only.
- Added owner-aware async-job tests using hold/AbortSignal and scoped cancelAll assertions for running versus cancelled jobs.