- Added optional `hide` metadata to skill capabilities so `SKILL.md` frontmatter intent is preserved when skills are loaded.
- Filtered system prompt skill rendering to exclude `hide: true` skills from the `<skills>` listing while keeping them loadable.
When an MCP server uses OAuth Dynamic Client Registration (RFC 7591) and
no client_id is pre-configured, MCPOAuthFlow registers a fresh public
PKCE client on each authorize, captures the issued client_id into a
private field, then discards it once the flow object goes out of scope.
At refresh time, MCPManager#resolveAuthConfig calls refreshMCPOAuthToken
with auth.clientId from mcp.json — which is empty for these servers —
so providers that require client_id on the refresh grant (e.g. Linear at
mcp.linear.app/token) reject with HTTP 401 invalid_client. The user is
forced to /mcp reauth manually every time the access token expires.
This change threads the resolved/registered client credentials back out
of the OAuth flow and persists them into mcp.json so refresh has what
it needs indefinitely:
- MCPOAuthFlow exposes resolvedClientId / registeredClientSecret getters.
- MCPCommandController#handleOAuthFlow returns OAuthFlowResult with
credentialId + clientId + clientSecret, populated from the flow's
post-login state.
- The initial-connect non-wizard path and /mcp reauth path persist the
returned client credentials into both auth.{clientId,clientSecret}
(used at refresh) and oauth.{clientId,clientSecret} (used by future
/mcp reauth to skip re-registration).
- The wizard's onOAuth callback signature now returns the same shape;
#launchOAuthFlow folds the registered credentials into wizard state so
the final mcp.json entry built by #buildServerConfigWithAuth includes
them under auth.{clientId,clientSecret}.
Servers that configure a static oauth.clientId in mcp.json (Notion,
Slack, Datadog) are unaffected: #tryRegisterClient short-circuits, the
returned clientId equals the configured one, and the write-back is a
no-op.
Adds two MCPOAuthFlow unit tests covering both paths.
Addresses codex review on #1060: an extension session_start handler that
calls setThinkingLevel via the exposed extension action (line 1541) would
have run BEFORE #registerPreparedSession set the record into #sessions and
BEFORE the session/new response was delivered to the client, causing
config_option_update to be pushed for a session id the client did not yet
know about. This is the exact race that #scheduleBootstrapUpdates already
documents and guards for available_commands_update / session_info_update
(Zed's 'Received session notification for unknown session' drop).
Moved the session.subscribe(...) installation out of #registerPreparedSession
and into #scheduleBootstrapUpdates's 50ms timer callback so the lifetime
subscription shares the same response-delivery guard as the existing
bootstrap notifications. The pre-bootstrap thinking level is still
communicated to the client through the response payload's configOptions
(newSession / loadSession / resumeSession / unstable_forkSession all return
it), so no state is lost; it is only the notification that is deferred.
For client-driven setSessionConfigOption({thinking}) the handler now only
skips its own push when the lifetime subscription is already installed.
Pre-bootstrap the handler keeps pushing (the client knows the session id
because they passed it in), post-bootstrap the subscription pushes
exactly once. No double-push, no missing pre-bootstrap notification.
Tests:
- updated existing pushes-config-option-update test to await past the 50ms
bootstrap timer before driving the internal setThinkingLevel
- updated the single-config_option_update-per-setSessionConfigOption test
the same way
- added 'suppresses lifetime config_option_update during the bootstrap
window' regression that drives setThinkingLevel synchronously after
newSession and asserts zero notifications, then asserts notifications
resume after the bootstrap timer fires
- bun test test/acp-agent.test.ts: 11/11 pass
Co-Authored-By: omp <noreply@oh-my-pi.dev>
ACP clients (Zed, etc.) only received `config_option_update` notifications
when they themselves drove the change via `session/set_session_config_option`.
Internal thinking-level updates (slash commands, automatic model-driven
adjustments, extension UI) bypassed the notification path, so client config
panels went stale until the next user-initiated change.
AgentSession now emits a `thinking_level_changed` event from
`setThinkingLevel`, and AcpAgent installs a session-lifetime subscription on
each managed session that pushes a fresh `config_option_update` whenever the
event fires — independent of prompt-turn lifecycle. The
`session/set_session_config_option` handler no longer pushes its own
notification for the `thinking` config (lifetime subscription covers it);
the response still returns fresh `configOptions` so callers see the new
state synchronously. Subscriptions are released in `#disposeSessionRecord`.
Also consolidated four duplicate `config_option_update` send sites into a
new `#pushConfigOptionUpdate(record)` helper.
Tests: added two cases to `test/acp-agent.test.ts` — one verifying internal
`setThinkingLevel` calls produce a `config_option_update` and a no-op
re-set produces none, and one verifying client-driven
`setSessionConfigOption(thinking, …)` produces exactly one notification.
Co-Authored-By: omp <noreply@oh-my-pi.dev>
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.
Keep the active page stealth setup synchronous, but make the broader CDP target UA override sweep selective and best-effort. Non-page or ephemeral Chrome targets can otherwise block worker initialization long enough for browser.open to hit the tool timeout before the tab worker sends ready.
Fixes#1053
- Raised the Bun minimum version to >=1.3.14 across package metadata, install scripts, and changelog notes.
- Removed the Photon native image pipeline and added SIXEL-based `sixel` support in pi-natives.
- Migrated coding-agent image handling and resizing to `Bun.Image`, including updated tests and a JPEG quality bump to 80.
- Added HTTP/2 fetch bootstrap with HTTPS-only fallback and updated Bun build flags for autoload suppression/`--keep-names`.
- Changed multi-file search paging to skip whole files and page results in file windows.
- Added per-file match caps, round-robin file selection, and new file-limit truncation reporting.
- Replaced match/result limit metadata with fileLimitReached and perFileLimitReached.
- Lowered read.defaultLimit default to 300 with 1 lead and 3 trailing context lines.
- Replaced the search skip test with file-pagination coverage and added per-file cap tests.
- Added session-stats analytics tooling to classify searches, detect repeats, and render relevance plots.
- Added `tools.artifactHeadBytes` and `tools.outputMaxColumns` settings with defaults in `SETTINGS_SCHEMA`.
- Expanded `OutputSink` with `headBytes`/`maxColumns` and middle truncate logic with elision markers and tracking.
- Updated output-meta to resolve sink settings, emit truncation metrics, and use `truncateMiddle` for spills.
- Integrated head and column limits into JS/Python/Bash/SSH/read output flows, with `:raw` skipping read truncation.
- Documented new output middle-elision and column-cap behavior in `CHANGELOG.md`.
- Added truncation tests for `OutputSink`, `truncateMiddle`, and read-tool line handling.
Addresses the codex review comments on #1015 plus a sweep of adjacent
ACP conformance gaps surfaced while wiring them up.
Tool call + diff metadata
- acp-event-mapper: thread session cwd through and resolve every
`ToolCallLocation` (initial args, in-flight updates, result details)
to absolute paths against it; ACP requires absolute paths for
client-side file mapping.
- edit/modes/patch: emit the destination path for moves in the diff
result so post-edit "open file" actions land on the new file.
Permissions
- agent-session: pass cwd into `extractPermissionLocations` and resolve
raw `path`/`file`/etc. fields against it before sending
`session/request_permission`.
- agent-session: gate the permission wrapper on
`bridge.capabilities.requestPermission && bridge.requestPermission`,
matching the read/write/bash capability+method pattern.
acp-agent
- `authenticate`: validate `methodId` against the methods advertised by
`initialize` and reject anything else, so malformed clients fail fast.
- `setSessionConfigOption(MODE_CONFIG_ID)`: also emit
`current_mode_update` so clients tracking `modes.currentModeId` see
the same transition `session/set_mode` would produce.
- Pass `runtime.notifyConfigChanged` to builtins; emit
`available_commands_update` from a shared `reloadPlugins` helper
reused by `/reload-plugins`, `/marketplace`, and `/plugins`.
- prompt resource handling: route `resource` content with `image/*`
MIME into the `images` array instead of dropping it as an opaque
blob; non-image blobs still fall back to the URI placeholder.
- pass session cwd to the event mapper.
Builtins
- model: call `runtime.notifyConfigChanged()` after a successful
`setModel` so the ACP config selector reflects the new model
immediately.
- mcp: redact query strings and userinfo from MCP server URLs before
emitting them in `/mcp list` (prevents leaking `?exaApiKey=…` style
secrets); wire `manager.setAuthStorage(...)` before `prepareConfig`
in `/mcp test|resources|prompts` so OAuth servers can refresh tokens.
- ssh: reject non-integer `--port` values via a `^\d+$` guard instead
of silently coercing through `Number.parseInt`; list project hosts
first and dedupe user-scope duplicates to match capability-loader
precedence.
- export: reject clipboard aliases (`--copy`, `clipboard`, `copy`)
before passing them to `exportToHtml` as a filename.
- compact / force / move / browser: surface underlying failures via
`usage(errorMessage(...))` instead of letting them crash the command.
- session save|delete: route through the active SessionManager so the
persist writer is consulted and stale storage references are removed.
- marketplace / plugins / reload-plugins: call `runtime.reloadPlugins()`
on install/uninstall/upgrade and enable/disable so slash command
registries and command lists refresh consistently.
- shared.usage: make async and `await runtime.output(...)` so
`sessionUpdate` text is never dropped or reordered.
- types: document the new `reloadPlugins` and `notifyConfigChanged`
runtime hooks.
bash tool
- Use a shared `fireKill()` from the abort listener so `session/cancel`
terminates the remote command immediately instead of waiting for the
next `currentOutput()` round trip.
- Race `currentOutput()` against the abort signal so a stuck
`terminal/output` RPC cannot delay cancellation.
- Kill the terminal before reading final output on timeout so a slow
output read cannot let a timed-out command keep running past the
enforced timeout.
Tests
- acp-agent.test: extend the existing config-option assertions to
verify both `model` and `thinking_level` changes emit
`config_option_update` notifications scoped to the right session.
- acp-builtins.test: cover `/model` emitting both
`notifyTitleChanged` and `notifyConfigChanged`; lock in the parsed
`mcp add` / `ssh add` call shapes so future arg-parser regressions
fail the test instead of silently writing different configs; add a
`reloadPlugins` stub plus a typed `notifyConfigChanged` slot to the
shared test runtime factory.
- acp-stdout-hygiene.test: drain stderr in parallel and assert no
JSON-RPC frame leaks onto it; terminate the spawned process so the
stderr pump resolves deterministically.
CHANGELOG: itemize the above under `[Unreleased] > Fixed`.
CI
- bun run check: clean (TS + Rust)
- bun run test: 4128 pass / 689 skip / 0 fail (TS); 252 pass / 0 fail
(Rust nextest)
- bun run ci:test:smoke: --version / --help / `stats --help` all OK
- Deferred initialize to flush queued credential_disabled events via queueMicrotask and event splicing.
- Added tests for pre-initialize credential_disabled emissions and onError propagation of handler failures.
- Replaced auth credential disable flow with CAS checks in #tryDisableAuthCredentialIfMatches and matching SQL statement.
- Retried OAuth getApiKey after disable failures; added peer-rotation race test for fresh token and active credential retention.
- Updated CHANGELOG for deferred microtask flushing plus eval import renames and diff URL/quoted-path parsing fixes.
- Extended short-form diff URL parsing to treat `<scheme>://N/diff` as a diff path across schemes, so `issue://N/diff` now follows the issue no-diff rejection path.
- Kept repository listing behavior unchanged for `<scheme>://owner/diff` by limiting diff short-form disambiguation to numeric hosts.
- Added regression coverage asserting `issue://9/diff`, `issue://9/diff/all`, and `issue://9/diff/3` now reject with the issue no-diff error.
- Updated JS import rewriting to route top-level `import` declarations through `__omp_import__` with support for import attributes.
- Added AST traversal to replace `import(...)` call callee nodes with `__omp_import__` so dynamic imports resolve via session-aware helper.
- Updated runtime `__omp_import__` to accept an optional options object and pass it through to `import(target, options)`.
- Fixed `issue://owner/diff` and `pr://owner/diff` parsing so they resolve to issue and PR list outputs.
- Fixed `pr` short-form parsing by requiring `scheme==='pr'` and a numeric host before `diff` matching.
- Fixed PR unified-diff parsing to decode quoted header paths and count `----`/`++++` hunk lines as one deletion/addition.
- Fixed `read` error rendering to emit status blocks with cleaned, range-aware, tab-normalized lines.
- Stopped `github-cache` from chmod-ing existing parent directories, preserving pre-existing permission modes.
- Added issue:// and pr:// URL handlers for single lookups and list queries with query filters.
- Added a SQLite-backed GitHub cache with soft/hard TTLs, stale hits, and background stale refresh.
- Removed issue_view and pr_view tool operations, inputs, and docs, requiring reads via issue:// and pr:// URLs.
- Added github-cache and issue-pr-protocol tests with temporary cache DB setup and OMP_GITHUB_CACHE_DB teardown.
- Standardized prompt templates across agents, tools, system, memory, and compaction to NEVER/AVOID wording.
- Reinforced policy language to ban edits/builds, state changes, and unsolicited JSON/code or filler output.
- Renamed stripRfc2119Bold to normalizeRfc2119, mapped NEVER/AVOID aliases, and skipped inline-code replacements.
- Updated the unreleased changelog to document the prompt-terminology migration.
- Switched issue and PR search handlers to `gh api /search/issues` with `is:issue`/`is:pr` queries.
- Added REST search response models and mapped issue/code/commit/repo payloads to normalized results.
- Updated code, commit, and repo search parsing to read `{items}` envelopes and convert snake_case fields.
- Fixed merged-PR output state by deriving it from `pull_request.merged_at` in test fixtures.
PR #998's branch was rebased on stale main and its CHANGELOG conflict
resolution dropped the immutable [14.9.8] and parts of [14.9.7]
released sections. Restore them and keep only the new credential_disabled
bullet PR #998 actually contributes under [Unreleased]/Added.
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>
[Unreleased] → Added entries for both the new "Approve and compact
context" ExitPlanMode selector choice and the underlying typed
`CompactionCancelledError` + `CompactionOutcome` plumbing.
Op: extend
- 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 `:conflicts` and `read conflict://<N>`/`read conflict://<N>/<scope>` support and rejected wildcard conflict reads.
- Added bulk conflict resolution via `write({ path: "conflict://*", ... })` across files with per-file grouping.
- Expanded conflict detection to scan files up to 10MB, track insertion-ordered history, and report full `X of Y` summaries.
- Reworked `spliceConflict` to match marker blocks by content, fixing shifted or stale block splicing.
- Added coverage for wildcard parsing, scoped reads, prepended-line splices, relocation, and warning assertions in detect/integration tests.
- Added `ConflictScope` parsing for `conflict://<N>/<scope>` with `ours`, `theirs`, and `base` validation.
- Added `read` support for full and scoped conflict URIs, rendering regions via `#readConflictRegion` with preserved formatting metadata.
- Added conflict-count and error handling in read results, including `Conflict #N not found` responses.
- Added `write`-path rejection for scoped conflict URIs, returning `ToolError` before lookup to enforce read-only behavior.
- Added unit and integration tests for scope parsing, conflict rendering, and read/write conflict error scenarios.
- Added conflictCount metadata and warning badge output to read results for files with unresolved conflicts.
- Added conflict detection parsing with strict marker matching and session-scoped conflict IDs.
- Added write-path conflict resolution for `conflict://N` using token expansion and marker validation before splicing.
- Added unit and integration tests for conflict scanning, history lifecycle, URI validation, and workflows.
- Introduced canonical `*** Cell` headers with `t:` and `rst` attributes in eval prompts, schema, and docs.
- Updated parser and grammar to parse `*** Cell` blocks, stop on `*** End`/next header/EOF, and handle invalid `rst` with errors.
- Added quote-aware attribute tokenizers and split HTML eval parsing into `Cell` and legacy `Begin` handlers with `py` defaults.
- Expanded parsing behavior and tests for `rst` booleans, title aliases, abort boundaries, and stray-line skips between cells.
- Replaced Python execution with a local `python -u runner.py` subprocess and NDJSON stdin/stdout framing.
- Removed shared-gateway architecture, including coordinator lifecycle APIs, `useSharedGateway` wiring, and `jupyter` CLI/actions.
- Simplified setup checks to a plain Python 3 availability probe and removed automatic dependency-install fallbacks.
- Updated kernel cancellation and display processing to use status frames, SIGINT/SIGTERM escalation, and normalized output coercion.
- Added `python-runner` integration and display tests while deleting legacy websocket and kernel lifecycle test suites.
- Fixed query tokenization to split on non-alphanumeric runs, aligning with FTS5 unicode61 indexer so punctuation-delimited terms like `git-commit` match correctly.
- Added LIKE-based substring fallback so infix queries FTS5 prefix matching cannot reach (e.g. `mit` matching `commit`) still return results.
- Merged FTS and substring results with deduplication, prioritizing FTS matches before substring-only matches up to the requested limit.
- Added new frustration and revised behavior metrics across stats types, parser mappings, and UI charts.
- Reworked session sync to fan out parsing across a capped worker pool with SyncOptions progress callbacks.
- Added worker-based parse messaging and throttled TTY progress rendering in the stats sync command.
- Updated behavior scoring to strip structured content, ignore noisy prompts, and fix profanity boundary regressions.
- Expanded user message schema and migrations, then repaired assistant model/provider links during sync backfill.
- Updated worker-import guidance in AGENTS.md and recorded sync-progress/metrics updates in package changelogs.
- Added worker-backed JS runtime via Transport and WorkerCore, with queued runs, status/error output, and clean teardown.
- Refactored executeInVmContext to reuse worker sessions, route pending runs by id, and fallback inline on spawn failure.
- Added rewrite helpers to convert top-level imports and demote top-level const/let/class declarations for persistence.
- Handled aborts by canceling in-flight tool calls, hard-killing worker sessions, and dropping JS timeout enforcement.