Re-introduces the per-tool approval system from luzidd's commit 39124f3 (which
is no longer reachable from main) and improves it before re-landing.
What's restored:
- ApprovalPolicy (allow/deny/prompt) plus DEFAULT_APPROVAL_POLICIES.
- ACTION_EXCEPTIONS registry (LSP read-only, bash critical patterns).
- getApprovalPolicy() six-level resolution order.
- ExtensionToolWrapper.execute() gate before extension handlers.
- --auto-approve / --yolo CLI flag and tools.approval.<tool> user config.
- docs/approval-mode.md user guide.
What's improved over the original:
- Replaced unchecked 'as any' casts with typed unknown narrowing helpers.
- Validate userConfig values: invalid strings, numbers, etc. fall through to
the built-in default instead of being silently honoured (typo no longer
locks a tool out or grants implicit approval).
- Expanded CRITICAL_BASH_PATTERNS: chmod -R /, chown -R /, bash <(curl ...),
writes to /etc/passwd|shadow|sudoers, shutdown/reboot/halt/init 0,
kill -9 1, nc -e / nc -c reverse shells. Pattern shapes require a
command-position boundary so 'npm run reboot-tests' and 'echo "shutdown the
queue"' don't false-positive.
- Added DEBUG_READONLY_ACTIONS exception so DAP inspection actions (threads,
stack_trace, variables, scopes, read_memory, …) auto-allow while
execution-side actions (launch, attach, continue, evaluate, write_memory,
set_breakpoint, …) still prompt.
- formatApprovalPrompt: labels mcp__<server>__<tool> calls as MCP server
tools, surfaces ssh host + command, recognises the modern § hashline header
for edit, and truncates >240-char fields so a heredoc-sized body cannot
blow out the confirmation dialog.
- Test suite grown from 40 to 57 cases — new coverage for invalid user
config, the extended critical-bash patterns, benign-keyword negatives,
debug exceptions, MCP/ssh prompt formatting, and command truncation.
Verification:
- bun test packages/coding-agent/test/tools/approval.test.ts -> 57 pass
- bun x biome check . -> clean
- bun run check:ts across all 9 workspaces -> clean
- Replaced Bun.sleep with scheduler.wait for Node-compatible cancellable sleeps.
- Added module-level timestamp gate to skip yields within 50ms of the last one.
- Threaded AbortSignal through ExponentialYield.sleep to cancel losing timers in race.
- Added tests covering gate behaviour and stray-timer cancellation.
- Rejected negative values in addition to non-numeric ones, falling back to per-server config or default 30s.
- Emitted a logger warning when an invalid env value is ignored.
- Added tests covering negative and non-numeric rejection cases.
- Extended `buildWellKnownUrls` and `#resolveRegistrationEndpoint` to try `/.well-known//` as a third candidate after origin-root and path-prefixed forms.
- Fixed single-segment path handling so `/my-service` is treated as the gateway prefix rather than dropped.
- Fixed missing `await` on `#tryWellKnownForRegistration` that caused path-prefixed fallback to return an unresolved Promise.
- Added tests for single-segment prefix discovery and RFC 8414 path-ful issuer fallback.
- Adjusted hashline natural-order preview handling so op-insert and op-replace tokens now emit inline body content as payload when available.
- Added an inline-body presence check so those op tokens are skipped only if path or payload is missing for that line.
- Dropped the `fileType: natives.FileType.File` restriction so glob searches can return directories as well as files.
- Updated the find tool prompt to document directory results and trailing-slash output.
- Added tests verifying directory matches are included and emitted with a trailing `/`.
- Clamped `formatDuration` to return `0ms` for non-positive, NaN, or infinite inputs.
- Updated usage report rendering to suppress reset countdowns when `resetsAt` is absent or no longer in the future.
- Added unit tests for `formatDuration` covering clamped values and standard duration formatting.
- Migrated OAuth provider authentication from standalone `pi-ai` CLI to in-process `AuthStorage.login()` flow in coding-agent.
- Made provider argument optional for `login` and `logout` commands with interactive provider picker when omitted.
- Added `list` command to enumerate registered OAuth providers with optional `--json` output format.
- Removed `pi-ai` CLI binary and `bin` entry from @oh-my-pi/ai package; library API remains unchanged.
- Updated documentation and examples to reflect new `omp auth-broker` command interface and in-process OAuth flow.
Threaded cache freshness/authoritativeness through #loadCachedStandardProviderModels so dropProviderModels only fires when the cached Vertex project-catalog row is both fresh and authoritative. A stale or non-authoritative snapshot (e.g. after ADC discovery failure rewrote the row with authoritative=0) now keeps the bundled Gemini fallback in place, which would otherwise be the last working catalog in API-key-only environments.
Refs #1412
- Updated `TempDirGuard` creation in grep tests to include PID and an atomic sequence, preventing temp path collisions.
- Removed the `branch` filter from GitHub action run queries so results are matched by `head_sha` only.
- Adjusted run-watch calls to the simplified `fetchRunsForCommit` interface without the branch argument.
Added Google Vertex OpenAI-compatible model discovery with ADC auth and treated authoritative Vertex project catalogs as replacements for bundled Gemini fallbacks in the model registry.
Fixes#1412
Plan-mode subagents (and any subagent with an explicit `agent.tools` array)
were given the `yield` tool in the registry but not in
`agent.state.tools`. The session prompts and idle reminders still
demanded a `yield` call to terminate, so the model would reason
"there doesn't seem to be a yield tool available" and the turn went
nowhere.
`createTools` correctly appends `yield` to the registry when
`requireYieldTool: true`, but `createAgentSession` then derived the
active tool list from `options.toolNames` directly, dropping `yield`
again. Mirror the invariant already enforced in
`parseAgentFields` (discovery/helpers.ts): when `requireYieldTool` is
set and the caller passes an explicit list, append `yield` to it
before normalization.
Fixes#1408
- Extract resource_metadata URL from WWW-Authenticate and follow RFC 9728 chain
- Add buildWellKnownUrls with path-prefixed well-known fallback for gateways
- Fix resolveRegistrationEndpoint to try path-prefixed well-known (was missing await)
- Support relative Mcp-Auth-Server URL resolution against server URL
- Pass resourceMetadataUrl through all discoverOAuthEndpoints call sites
- Add comprehensive tests for path-prefixed, resource_metadata, and relative URL flows
- Replaced external watchdog timers with per-request SDK timeouts for first-event budget across OpenAI, Anthropic, and Azure providers.
- Keyed Python shared kernels by (sessionId, cwd) to prevent cross-directory state bleed.
- Deduplicated concurrent cold-start session acquisition for JS and Python executors.
- Moved `isOpenAIResponsesProgressEvent` to shared module and scoped display output routing per run for interleaved async cells.
- Refactored console-table tests to build explicit RuntimeHooks and pass them to JsRuntime.run.
- Refactored image coercion tests to pass explicit RuntimeHooks into JsRuntime.displayValue instead of constructor hooks.
- Removed deprecated MCP-specific type aliases and functions from tool-discovery module, consolidating to unified generic tool discovery API.
- Migrated session and SDK code to use generic filterBySource() and collectDiscoverableTools() instead of MCP-specific variants.
- Removed deprecated interface members including hasQueuedMessages(), FocusPane, AcpBuiltinCommandRuntime, and legacy settings methods.
- Updated test suites to use renamed generic discovery methods and removed back-compat test coverage for legacy MCP shapes.
- Added `cwd` and `env` optional parameters to kernel execution API for runtime working directory and environment variable control.
- Implemented runtime environment setup in Python runner with `_apply_request_runtime()` to apply cwd and env from request before code execution.
- Enhanced SIGINT handler management with `active_executions` counter and `_begin_exec_sigint()` / `_end_exec_sigint()` functions to prevent state mutation during concurrent execution.
- Changed `SearchRenderArgs.paths` parameter type from `string[]` to `string | string[]` to accept single string paths.
- Added comprehensive test coverage for kernel cwd updates, timeout interruption safety, and SystemExit handling in shared executor sessions.
- Updated type signature to show `paths` accepts `string | string[]` instead of only arrays.
- Clarified that single string paths are wrapped into a one-element list before resolution.
- Improved prompt instructions to explicitly show both string and array usage patterns.
- 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.
Converted CLI document file arguments through the Markit path before adding them to the initial prompt, preventing PDF bytes from being sent directly to local vision models. Added a regression test for PDF file arguments.\n\nFixes #1401
- Replaced per-line hash anchors with file-level hash validation in hashline format, changing anchor syntax from LINE+HASH to bare LINE numbers.
- Simplified hashline line separator from pipe (|) to colon (:) and replaced replace operator (->) with colon, added delete operator (!) for explicit line deletion.
- Implemented file-read snapshot caching with multi-snapshot ring buffer per path and file-hash-based recovery to detect and recover from stale edits.
- Refactored hashline grammar, parser, and execution to support file-level hash binding, anchor-scoped validation, and structural bracket warnings for delete operations.
- Updated documentation and test fixtures to reflect new hashline syntax with file hashes, colon separators, and delete operator throughout.
- Deleted the `packages/ai/src/utils/h2-fetch.ts` HTTP/2 retry wrapper and removed its public export from `packages/ai/src/index.ts`.
- Removed the `installH2Fetch` import and invocation from `packages/coding-agent/src/cli.ts`, so the CLI no longer patches `fetch` for HTTP/2 negotiation.
- Updated `packages/coding-agent/CHANGELOG.md` to align the vim-mode removal notes with the code changes.
- Removed vim edit mode and automatically map existing vim configurations to hashline mode.
- Deleted VimTool class, VimEngine implementation, and all vim-specific editing logic (2409 lines).
- Removed vim mode from EditMode union type, edit tool strategies, and configuration schemas.
- Deleted vim parser, command handler, buffer manager, and renderer modules.
- Updated documentation and tests to remove vim mode references and add deprecation mapping.
- Added a `console.table` helper in the JS prelude that forwards calls to the runtime `__omp_table__` hook.
- Implemented `__omp_table__` in the runtime to render tables through `node:console.Console` and emit text via `onText`.
- Added tests verifying `console.table` produced formatted table output and respected the optional columns filter.
- yieldIfDue() uses compensated sleep (sleepAtLeast): retries Bun.sleep()
until the requested wall-clock duration has elapsed. This is necessary
because napi callbacks (uv_async_send) can wake the event loop
prematurely, causing Bun.sleep(N) to return after only ~1-2ms.
- ExponentialYield for bash-executor: starts at 20ms, doubles to 10s.
Closes#1384
- Added helpers to synthesize RawSseEvent records for inbound, outbound, and malformed Codex WebSocket traffic.
- Passed onSseEvent through websocket transport and stream setup so frames are forwarded to the raw-SSE debug pipeline during streaming.
- Added a stream test that verifies outbound and inbound websocket frames are emitted with SSE-style raw lines for the debug viewer.
- BARE_IDENTIFIER_RE accepts $-prefixed names ($store, $count, RxJS/Svelte/
Angular). Word-boundary check now fires on those names, so searches no
longer return the offset inside a compound identifier like bar$store.
- applyWorkspaceEdit walks documentChanges in declared order, flushing
per-URI text edits immediately before any subsequent resource op for the
same URI. Folder rename/delete ops flush every pending URI under the
affected subtree. Rename ops also flush pending edits queued against
renameOp.newUri (and descendants) before fs.rename runs.
- Legacy changes-map-only WorkspaceEdit payloads are unchanged.
Refactor:
- session.ts: extract mapDebugpyMissingModule helper; replace the duplicated
inline check in launch/attach catch blocks. Add jsdoc on DapStartRequestFailure.settled
documenting per-call ownership and how throwPreferredDapStartError consumes it.
- path-utils.ts: replace the no-op keepOpaqueResourceUri branch with an
OPAQUE_RESOURCE_SCHEMES Set so the structure carries the intent. Functionally
equivalent; new opaque schemes become a one-line Set change.
Tests:
- dap-launch-failures: cover the debugpy stderr -> 'pip install debugpy'
rewrite for launch and attach, plus a negative case (non-debugpy adapter
with the substring in stderr is left untouched).
- dap-launch-failures: model the delayed-launch-failure case the new
settled-race in throwPreferredDapStartError defends against. FakeDapClient
gains optional launchErrorDelayMs/attachErrorDelayMs.
- dap-launch-failures (DebugTool): assert adapter:'debugpy' early-throw
surfaces 'python not found in PATH' on both launch and attach when
selectLaunchAdapter/selectAttachAdapter return null, and the unspecified-adapter
path still falls back to the generic 'No debugger adapter' error.
- find.ts: export validateFindPathInputs and pin the new backslash-escape
semantics (\, no longer trips the comma-joined heuristic) plus the
existing brace-expansion and rejection paths.
- patch.ts: cover the post-write verification error message. The user-facing
ToolError must contain the caller-supplied relative path and not the
absolute resolvedPath (which still lives in the structured context for
log correlation).
- split-internal-url-sel: reword two mcp:// test comments that described a
'peeler refuses' guard that doesn't exist; rename the tests to reflect the
actual opaque-scheme rule.