A top-level `return value;` in a JS eval cell was previously swallowed:
returnFinalExpression only handled ExpressionStatement, so ReturnStatement
flipped the IIFE wrapper which discarded the value. Rewrite the trailing
return into __omp_set_final_expr__((expr)) so the existing
final-expression channel surfaces the value just like a trailing
expression.
- Expose optional timeoutMs (clamped 0.5..60s) and pipe through to the
native walker. Default stays at 5s.
- On timeout, drain accumulated matches and return them with
truncated:true plus a notice line instead of throwing.
- Add gitignore boolean to the schema so callers can opt out of the
default exclude when looking for .env/.jsonl/build artifacts.
- Reject comma-in-paths array entries with an actionable ToolError.
- Update find.md with the knobs and the array-shape example.
ResolveBinary picked pip3 when only pip3 exists, but the spawn passed the
bare string "pip" which throws on macOS hosts without a pip alias. Use
the resolved path and wrap the helper in try/catch so a missing or
broken installer never escapes as an uncaught throw breaking URL reads.
The ignoreResultLimits flag now gates only the byte-budget tail-truncate,
never the explicit line window. Reads of internal URLs (skill, local,
memory) with a line range previously returned the tail of the file
instead of the requested window for files larger than the byte budget.
- Expanded `isAnthropicFastModeUnsupportedError` to treat 429 `rate_limit_error` responses mentioning fast mode as unsupported alongside 400 `invalid_request_error` speed-rejection cases.
- Added tests for unsupported-fast-mode detection covering 400, 429, and unrelated error payloads.
- Added `AgentSession.isFastModeActive()` with provider-scoped resolution and switched status-line rendering to use it for the fast-mode icon.
Two new `ServiceTier` values let users target priority/fast mode at one
provider family without paying premium costs on the other when switching
models mid-session:
- `"openai-only"` → resolves to `"priority"` on `openai` and
`openai-codex`; `undefined` everywhere else.
- `"claude-only"` → resolves to `"priority"` on direct `anthropic`;
`undefined` on Bedrock/Vertex Claude and elsewhere.
Implementation centers on a new `resolveServiceTier(serviceTier, provider)`
helper exported from `@oh-my-pi/pi-ai`. The three OpenAI providers and the
Anthropic provider all route through it, replacing the previous
`shouldSendServiceTier` type-guard pattern (which couldn't survive scoped
values — the input variable's literal type stops matching the wire type
once scopes are introduced). `shouldSendServiceTier` is kept as a plain
boolean for external callers but no longer narrows the input.
`getPriorityPremiumRequests` is reworked: it now counts Anthropic +
`"priority"` (fast mode) as one premium request — the original PR
introduced the realization but didn't update billing — and continues to
ignore providers that silently drop the field on the wire.
User-facing:
- `serviceTier` setting enum gains `"openai-only"` and `"claude-only"`
with clear UI descriptions.
- `/fast on` still sets the unscoped `"priority"`, but `/fast status`
and `isFastModeEnabled()` now report `on` for any priority-granting
tier (including scoped values). `/fast off` clears to `undefined`
regardless of scope.
- The Anthropic auto-fallback listener and re-arm clearing both cover
`"priority"` and `"claude-only"` (the two values that grant priority
on Anthropic). `"openai-only"` doesn't trigger the anthropic
fallback even if the user is on an Anthropic model — by design.
Tests cover all four resolver branches (unscoped passthrough, openai-only
match/miss, claude-only match/miss), Anthropic provider's wire `speed`
field under each scope, and updated premium accounting.
Replaces the parallel `speed` knob with the existing `serviceTier`
concept. The anthropic-messages provider now realizes
`serviceTier: "priority"` by setting `speed: "fast"` on the wire and
appending the `fast-mode-2026-02-01` beta header; other providers
continue to pass `service_tier` through natively or ignore it.
User-facing impact:
- `/fast` no longer dispatches on model.api. It just toggles
`serviceTier: "priority"`. Anthropic-specific translation lives
entirely in the provider.
- Anthropic auto-fallback marker is now the generic `"priority"`
identifier in `AssistantMessage.disabledFeatures` instead of
`"anthropic.fast_mode"`.
- New `clearAnthropicFastModeFallback(providerSessionState)` export is
invoked from `AgentSession.setServiceTier` when transitioning into
`"priority"`, so re-running `/fast on` after the provider
auto-disabled fast mode actually re-arms the next request instead of
silently no-oping.
Provider-side cleanups:
- Tightened cast site (`ParamsWithSpeed` alias) for the typed
`speed: "fast"` injection.
- Widened the rejection matcher (`\bspeed\b` + `not support`) so
phrasing drift ("is not supported" vs "does not support", quoted vs
backticked) doesn't break the fallback.
Dropped from the PR:
- `Agent.speed` / `AgentOptions.speed` / `SimpleStreamOptions.speed`
fields.
- `SpeedChangeEntry` and `appendSpeedChange` from the session entry
schema; service-tier change entries already cover this.
- `AgentSession.setSpeed` / `.speed` and the previousSpeed
capture/restore in `switchSession` — collapsed back into
`setServiceTier` + previousServiceTier, which now covers the rollback
too.
- Preserved launch and attach request failures when configurationDone also fails.
- Handled initial stop-outcome watcher rejections for failed launch and attach attempts.
- Rejected directory-valued debug launch programs before adapter selection and documented the debugpy launch shape.
Fixes#1187
- Added splitInternalUrlSel to iteratively peel internal-URL selector chunks while preserving unsupported schemes like mcp://.
- Updated ReadTool to use the internal splitter before routing so selector parsing is handled via parseSel.
- Added unit tests covering malformed selectors, namespaced skill hosts, and unchanged behavior for non-URLs or unsupported schemes.
The schema-level fix (normalizing {} to true in open schema positions)
makes the prompts correct-by-construction: models that follow the schema
grammar now emit string values for extra.title without needing softer
prompt language. Revert the SHOULD/optional hedging to the original MUST
wording per maintainer review.
Fixes#1179
Grammar-constrained models (e.g. Qwen3.6-35B-MTP via llama.cpp) emit
`extra: { title: {} }` instead of `extra: { title: "<string>" }` because
the resolve schema declares `extra` as Record<string, unknown> with an
open value schema, leaving the model free to drop in an empty object.
The apply guard then threw 'Plan approval requires extra: { title: ... }'
on every retry, looping the model indefinitely (issue #1179).
Plan approval now uses a layered title resolution:
1. `extra.title` if it is a non-empty string (and sanitizes to non-empty)
2. First `# Heading` in the plan content
3. Filename stem of `planFilePath` (`'/data/workspaces/can1357__oh-my-pi__1179/.omp-session/2026-05-19T03-59-21-254Z_019e3e63-62a6-7000-be63-371f2cd6d67d/local/PLAN.md'` → `PLAN`)
4. Literal `plan` as a final safety net
Each candidate is run through `normalizePlanTitle`; rejected ones fall
through. Extracted as `resolvePlanTitle` in plan-mode/approved-plan.ts
so it's unit-testable.
Prompt language relaxed from MUST to SHOULD for `extra.title` in
plan-mode-active.md and plan-mode-tool-decision-reminder.md, noting the
fallback so models don't waste turns on a now-optional field.
Fixes#1179
- 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.
- Added capParseErrors in shared render utilities and updated ast_grep and ast_edit to return capped parseErrors plus parseErrorsTotal.
- Threaded the preserved totals into parse-error formatting and renderer output so labels and overflow counts report the full number of issues.
- Added an ast_grep test asserting parse errors are capped at PARSE_ERRORS_LIMIT while parseErrorsTotal retains the original count.
- Implemented strict base64 validation and normalization for image `displayValue` payloads in the JS runtime, supporting strict strings, `Uint8Array`, `Buffer`, `ArrayBuffer`, typed-array views, and JSON `Buffer` objects.
- Dropped unrecognized image payloads while emitting a warning and fallback text instead of forwarding malformed data.
- Added tests covering successful coercions and invalid image data rejection paths.
- Guard renderInlineMarkdown against non-string input: partial JSON during
streaming can leave option label fields as undefined, causing marked.lexer
to throw 'undefined is not an object (evaluating e.replace)'. The ask tool
renderer now silently falls back to an empty string or baseColor output.
- Sanitize normalizePlanTitle instead of hard-rejecting: models that produce
natural-language plan titles like 'My Improvement Plan' were getting a
ToolError on every resolve call, causing an infinite retry loop. Spaces are
now converted to hyphens, remaining invalid chars are dropped, and only
truly unresolvable titles (empty after sanitization, path separators) throw.
- Fix ask.md prompt example: the example showed the legacy single-question
format (question/options/recommended at the top level) while the schema
requires questions: [{id, question, options}]. Models that follow examples
closely (Qwen3) generated calls that always failed schema validation.
Fixes#1176
Rewrites JSON-roundtripped Zod 4 schema objects that leak Zod internals as
JSON Schema keywords (e.g., `type:"enum"`, `enum:{...}`) into valid
JSON Schema 2020-12. This prevents validation failures when such schemas
are used as tool input schemas (e.g., from MCP servers).
Updates `isZodSchema` to reject deserialized Zod impostors that retain
`_zod` but lose their prototype.
Wires `toJSON` methods onto TypeBox shim schemas to ensure `JSON.stringify`
produces clean JSON Schema, preventing future leaks.
Fixes#1101
Tightened the hashline prompt to forbid readability spaces after the payload separator.
Preserved parsed payload text verbatim and surfaced a warning when a payload block looks like separator padding was added across every line.
Fixes#1166
Wires `speed: "fast"` and the `fast-mode-2026-02-01` beta into the
Anthropic provider, plumbs a matching `speed` option through
`SimpleStreamOptions` and the Agent, and teaches `/fast` to dispatch
on the active model's API (Anthropic -> speed=fast, OpenAI -> existing
serviceTier=priority path). Server is the authority on which models
support fast mode.
When the server rejects an unsupported model, the provider mirrors the
strict-tools fallback: drops the field, retries the same turn
transparently, persists the disable via `providerSessionState`, and
surfaces the action through the new `AssistantMessage.disabledFeatures`
marker so the session can sync the toggle off and warn the user.
- Added stats sync, browser tab, and JS eval workers as explicit --compile
entrypoints in scripts/ci-release-build-binaries.ts so Bun emits them
into bunfs in published binaries, matching the dev build script and the
AGENTS.md worker spawn contract.
- Switched the release-binary smoke step in .github/workflows/ci.yml to
invoke --smoke-test (in addition to --version) so this regression
cannot ship again.
- Added packages/coding-agent/test/issue-1150-repro.test.ts pinning the
symmetric contract: both build scripts must list every worker entry.
Fixes#1150
- Set GIT_CONFIG_* and GIT_TERMINAL_PROMPT environment variables to prevent test interference from user gitconfig, LFS filters, signing, and credential helpers.
- Enhanced git error messages to include stdout when stderr is empty, providing better diagnostics for test failures.
- Replaced symlink-resolving pathIsWithin with lexical path containment check to prevent test isolation bypass via symlinked extensions.
- Tracked ACP tool-call inputs per session and replayed them via `toolArgsById`/`getToolArgs` plumbing.
- Merged ACP tool execution end content from start and result events so command output replay preserves original args.
- Scoped ACP async-job draining by session `ownerId` and `agentId` with in-flight tracking and permission-gated deferred turns.
- Refactored compaction telemetry and async tests with per-test telemetry setup and asynchronous teardown resets.
- Collapsed a multi-line reinstall-warning `console.log` statement into a single line.
- Preserved the existing warning message text and only simplified its formatting.
Capped the session tree selector gutter width so deeply branched histories preserve space for the selected entry text.
Added regression coverage for heavily branched trees at wide and narrow viewport widths.
Fixes#1144
- Held wire-level agent_end until #promptInFlightCount drops to 0, preventing AgentBusyError when subscribers fire the next prompt synchronously from agent_end.
- Added #pendingAgentEndEmit field and #flushPendingAgentEnd(), called from #endInFlight and #resetInFlight.
- Added regression test covering re-entrant prompt() from agent_end listener.