- Propagated builtin allowArgs metadata into TUI autocomplete entries.
- Let no-arg slash-looking prompts fall through to prompt-composer completions while keeping argument-capable commands scoped.
- Added regressions for /settings @ and /settings #copy.
- Stopped prompt-composer # actions and @ file references from claiming submitted slash-command argument text without explicit argument completions.
- Added provider regression tests for /rename title arguments and explicit command argument completions.
Fixes#4600
Added a DefaultResourceLoader compatibility adapter for legacy pi package-root imports and translated resourceLoader into OMP session discovery options so noExtensions/noSkills are preserved for subagents.
Added a regression test covering the loader snapshot and createAgentSession translation path.
Fixes#4567
Included ESM extension-local bare dependency entries in the legacy extension graph so their relative children receive the same mtime cache-bust rewrite as extension source modules.
Skipped CommonJS dependency entries to preserve native Bun CJS default import behavior.
Added a regression test for a local node_modules ESM dependency whose unchanged entry re-exports an edited helper.
Fixes#4565
`calculateCost`, `modelsAreEqual`, and `getBundledProviders` moved from the
`@oh-my-pi/pi-ai` barrel to `@oh-my-pi/pi-catalog/models` in the catalog
split (1b9d9d0851). The legacy-extension pi-ai root shim already bridged
`getBundledModel`/`getBundledModels` back under their old `getModel`/
`getModels` names but never re-exported the other relocated symbols, so any
legacy extension importing `calculateCost` from `@oh-my-pi/pi-ai` failed
plugin validation at install time with `Export named 'calculateCost' not
found in module '.../legacy-pi-ai-shim.ts'`.
Bridge all three symbols through the shim so pre-split legacy extensions
load again. Add regression tests that load fixtures importing
`calculateCost`, `modelsAreEqual`, and `getBundledProviders` from
`@oh-my-pi/pi-ai` and assert identity against the catalog implementations.
Fixes#4584
macOS laptops have no dedicated Forward Delete key. Fn+Backspace is the
only way to send \e[3~, and many macOS terminals (Terminal.app, some
iTerm2 profiles) deliver \x7f for that combo instead — so the keystroke
landed in the search box, not the delete handler, making session deletion
unreachable for those users.
Add a Backspace-on-empty-search handler alongside the existing Delete
check. With a typed query, Backspace stays bound to the search Input so
users can still edit their filter text. The existing confirmation dialog
guards against accidents.
Footer hint updated: [Del delete] -> [Del/⌫ delete].
`#resolveAdvisorRuntimeDescriptors` hardcoded `ThinkingLevel.Medium` when
no thinking suffix was configured. For reasoning models with no
controllable effort surface (`devin-agent`: `reasoning: true`,
`thinking: undefined` — Cascade selects effort by routing to sibling
model ids, not a wire param), that default tripped
`requireSupportedEffort` on the first advisor prompt with an empty
`Supported efforts:` list, disabling the advisor session-wide.
Route the default through `resolveThinkingLevelForModel(model, level)`
which preserves explicit `off`, clamps a concrete effort into the
model's supported range, and returns `undefined` for reasoning models
without controllable efforts — falling back to `Inherit` so no effort is
sent while reasoning stays enabled. Matches the `auto`-path fix
(`clampAutoThinkingEffort`) and the Autonomous Memory clamp
(`clampThinkingLevelForModel`).
Fixes#4579
`plan.defaultOnStartup` records a `mode_change` before the composer restores its draft; without this the draft-cleanup arm check treats the file as durable and the metadata-only JSONL leak reappears for default-plan sessions.
Added a regression case that drives a model_change + mode_change + draft-clear cycle and asserts the session file is dropped on close().
Fixes#4571
Limit empty-session close cleanup to files whose draft sidecar lifecycle
materialized an otherwise startup-metadata-only session. Direct
ensureOnDisk() callers now remain discoverable even when they have no
user/assistant messages yet, and handoff custom_message entries survive
close before the next user turn.
Added regression coverage for resumed draft cleanup, ACP-style explicit
ensureOnDisk() records, and handoff custom messages.
Fixes#4571
`SessionManager.saveDraft(text)` calls `ensureOnDisk()` so the draft
sidecar has a parent JSONL. A follow-up `saveDraft("")` only unlinks
the sidecar — the session file was left behind, and `#shouldHaveSessionFile()`
could not prune it once the load path latched `#fileIsCurrent` and
`#forceFileCreation` to true. Each draft-then-clear-then-exit cycle
leaked a ~500–750 B zombie into `~/.omp/agent/sessions/<cwd>/`
containing only the title slot, session header, and a handful of
`model_change`/`mode_change`/`thinking_level_change` entries.
`close()` now calls `#dropIfEmptyAndNoDraft()` after draining the
writer: when the file exists, holds no user/assistant messages, and
no draft sidecar is present, it removes the session file and its
artifacts directory via `deleteSessionWithArtifacts`. Real conversations,
sessions with a saved draft still on disk (needed for `--resume`), and
never-materialized sessions are untouched.
Fixes#4571
Collected the current extension graph on every load and registered supplemental Bun hooks for modules added after the first import.
Preserved exact-path filters by tracking covered realpaths per entry instead of widening hooks to unrelated files.
Added a regression test for an entry-only extension that later adds helper and leaf modules, then reloads an edited leaf.
Fixes#4565
Threaded the current load's mtime tag through rewriteExtensionPackageImports, rewriteExtensionBareImports, and a new relative-graph pass so ./helper.ts, #alias/*, and extension-local bare deps all rekey per reload.
Added a toGraphImportSpecifier helper that emits bare POSIX paths with ?mtime on POSIX and keeps file:// URLs on Windows/bundled targets, matching the entry loader.
Added a regression test covering same-process relative-helper reload freshness through the public loader.
Fixes#4565
Loaded legacy Pi extension entries through raw POSIX filesystem specifiers so Bun keys the cache-busting mtime query.
Allowed the extension graph onLoad hook to match and normalize the mtime query before rewriting source.
Added a regression test covering same-process reload freshness and clean fileURLToPath-derived paths.
Fixes#4565
Removed the bash tool execution-path rewrite that stripped trailing head/tail pipeline stages before running commands.
Added regression coverage for short-reading final pipeline stages.
Fixes#4562
Rendered the status-line token rate as an explicit tok/s unit so Ghostty no longer auto-detects the numeric value as a URL.
Added a regression test for the token_rate segment rendering contract.
Fixes#4541
- Replaced manual Container stubs with TranscriptContainer instances in test fixtures.
- Updated test context initialization to utilize the actual container implementation for chat message tracking.
Reverted the defensive typeof guard; the assistant component contract guarantees the method, and test doubles now mock it. Keeping the production call strict avoids masking broken mocks or silently skipping persistence-key recovery.
- Mocked messagePersistenceKey in event-controller-error-banner.test.ts and safe-guarded it in event-controller.ts to prevent TypeError.
- Updated thinking loop retry test expectations to handle new dynamic recoveredErrors structure.
- Updated schema version assertions in auth-storage-email-dedupe.test.ts to v5, preserving v6 for future schema test.
- Simulated scrollback commitment in event-controller-message-start.test.ts by rendering container and committing rows before advancing timers.
- Added comprehensive unit tests for `TranscriptContainer` to verify uncommitted block tracking.
- Created integration tests ensuring `AssistantMessageComponent` correctly streams thinking and answer content into scrollback.
- Added tests verifying that expanded tool evaluation output records rows correctly without duplication after settling.
- Updated `AssistantMessageComponent` test suite to cover table streaming scenarios in the unsettled tail.
- Enforced strict history protection by gating ephemeral block removal on uncommitted state across controllers and UI components.
- Optimized settled-row calculations using explicit mermaid fence detection and improved scrollback integrity.
- Refactored transience management to target only actively streaming blocks, preventing redundant label rendering.
- Implemented persistent compaction for auto-retry errors and enabled consistent terminal title updates during session renaming.
- Moved terminal title update logic to a single listener onSessionNameChanged.
- Removed redundant setSessionTerminalTitle calls from ExtensionUiController, InputController, and InteractiveMode.
- Ensured consistent side-effect execution for terminal titles and editor accents across all session name change triggers.
- Refactored abort reason handling to rely on shouldRenderAbortReason instead of isSilentAbort.
- Updated documentation to clarify that both silent and user-interrupt aborts yield no label.
- Replaced commit-based stability checks with a unified `isTranscriptBlockFinalized` tracking mechanism.
- Removed deprecated provisional rendering configuration and flags across tool and renderer interfaces.
- Standardized native scrollback boundary logic to pin at the first unfinalized block using settled row verification.
- Updated and refactored test suites to validate block finalization and settled row boundaries instead of deprecated commit stability methods.
- Introduced an automated retry recovery system to track, manage, and persist recovered error states within agent sessions.
- Enabled compact transcript rendering for recovered auto-retry errors by removing heuristic commit machinery.
- Improved raw read tracking and provenance in the ReadTool to support refined file snapshot recording and hashline editing.
- Excluded recovered assistant messages from default model context and updated event controllers to handle retry recovery life cycles.
- Removed complex snapshot caching and volatile/stable state tracking logic.
- Replaced multi-zone audit logic with streamlined tail-sample checks.
- Simplified render boundaries by deriving a single final boundary from the live region.
- Eliminated redundant audit state management and auxiliary safe-end interfaces.
The cmux release regression tests install spies on CmuxSocketClient.prototype. Bun keeps those spies active across later browser-* files unless the file restores them explicitly, so browser-cmux-socket.test could stop exercising the real socket client depending on order.
Restore all Bun test mocks in afterEach after draining any test tabs, preserving mocked cleanup while preventing cross-file pollution.
Fixes#4499
Codex review of #4502 flagged that a bare `.catch(() => undefined)`
neutralizes the unhandledRejection but leaves the affected `runInTab`
call blocked inside `runCmuxCode` until timeout when the in-flight
code does not make another cmux socket request (e.g. `await
wait(60_000)`). `releaseTab` was signaling the run only by rejecting
an orphaned promise.
Wire the tab-close event all the way into the cmux run body:
- `PendingRun` gains a `closeAc: AbortController` that `releaseTab`
aborts BEFORE calling `pending.reject`. `wait(...)` (via
`waitForBrowserRun` -> `untilAborted`), in-flight cmux socket calls
(via CmuxTab's `#request` -> `untilAborted`), and facade proxies
(via `bindBrowserRunFacade`) all consume the composed signal, so
the run body unwinds within a microtask instead of blocking to its
own timeout.
- `runInTabWithSnapshot`'s cmux branch composes `closeAc.signal` into
the run's signal (`AbortSignal.any([opts.signal, closeAc.signal])`)
and now publishes `runCmuxCode(...)`'s outcome to the shared
`promise` via `.then(resolve, reject)` and returns `await promise`.
Both branches thus await the same promise, so `pending.reject`
always has an attached handler (removing the original crash) AND
the caller sees `Tab "..." was closed` immediately instead of
waiting on the run's timeout.
- Drop the defensive `promise.catch(() => undefined)` — the promise
is now actively consumed on both backends.
The new regression test adds a second case that exercises the
reviewer's exact scenario (`await wait(60_000);`) and asserts:
1. `pending.closeAc.signal.aborted` flips from `false` to `true`
across `releaseTab`, with the tab-close error as its reason.
2. The awaited `runInTab(...)` rejects with `Tab "..." was closed`.
3. No `unhandledRejection` fires.
Verified locally by temporarily removing `closeAc.abort(...)` in
`releaseTab` — the new assertions fail; restoring it makes them pass.
Fixes#4499
The cmux branch of `runInTabWithSnapshot` awaits `runCmuxCode(...)`
directly and never awaits/`.catch`es the `Promise.withResolvers()`
promise it stashes on `tab.pending`. When `releaseTab` walks pending
runs and calls `pending.reject(new ToolError("Tab ... was closed"))`
(a sibling subagent's `browser close --all`, session-scoped reap, etc.),
that orphaned promise had zero handlers and Bun surfaced the rejection
as `unhandledRejection`, which the CLI's top-level handler treats as
fatal — killing every other tab and subagent sharing the process, not
just the affected run.
Attach a no-op `.catch(() => undefined)` to the promise immediately
after creation. Inert for the worker branch (which still awaits the
same promise via `raceWithTimeout`, and attaching a second handler is
safe) and neutralizes the orphan on the cmux branch.
Adds a regression test that drives real `acquireBrowser` /
`acquireTab` / `runInTab` / `releaseTab` against a mocked
`CmuxSocketClient`, races `releaseTab` against an in-flight cmux run,
and asserts no `unhandledRejection` fires.
Fixes#4499