- Deleted the standalone Tester subagent file.
- Updated the main system prompt to incorporate comprehensive testing requirements and quality standards.
- Removed the Tester agent registration from the agent definitions.
Previously, paused/no_model advisors were inserted into #advisorStatuses
during descriptor resolution while running advisors were only inserted
during the build loop, causing the Map's insertion order to diverge
from the configured roster. This made disabled advisors appear before
active ones in /advisor status output. Now all entries are recorded
during resolution in roster order; the build loop confirms running
status without changing insertion order.
- Aborted the active agent loop synchronously when a terminal yield tool result finishes, so IRC-wake turns stop before another provider call.
- Added a regression covering idle IRC wake handling after a terminal yield.
Fixes#4963
Magic keyword matching now treats sentence punctuation and quotes as prose boundaries while still rejecting casing changes, inflections, and path/file-extension occurrences.
Added regression coverage for detection and highlighting across ultrathink, orchestrate, and workflowz.
Fixes#4965
- Added auto-sealing logic to `FinalizableBlock` to finalize displaceable snapshots when they enter the scrollback area.
- Updated TUI frame emission to publish committed rows and clamp them to segment bounds, ensuring accurate component updates.
- Introduced component tracking and cleanup in event controller tests to prevent resource leaks during finalization.
- Validated state transitions and post-emit synchronization through comprehensive new test suites for transcript and TUI components.
- Removed the stale pinned dependency edge before invoking Bun for same-repository git source replacements so Bun does not construct a dependency loop.
- Added a regression test for the pinned-to-unpinned GitHub plugin replacement path.
Fixes#4960
Registered the bundled virtual resolver on the omp-legacy-pi-bundled namespace while keeping the file-namespace scheme fallback for build-time resolution.
Updated the regression test to cover registry-key resolver inputs.
Fixes#4954
- Stopped the malformed-yield loop guard once a valid yield has been captured or termination is pending.
- Covered same-turn valid yield calls followed by malformed sibling yield calls.
Fixes#4957
Routed bundled virtual specifiers through Bun's plugin namespace so compiled-binary extensions can import @oh-my-pi value exports.
Surfaced extension load failures during session startup.
Fixes#4954
- Removed literal HTML comment sentinels (`<!-- -->`) from thinking block displays.
- Added logic to hide blocks that consist entirely of reasoning noise and updated display validation to omit empty formatted output.
- Refactored the memoization cache to maintain separate slots for prose and raw modes.
- Copied localProtocolOptions through SDK-created custom tool contexts so startup MCP tools resolve '/data/workspaces/can1357__oh-my-pi__4946/.omp-session/2026-07-09T16-06-43-993Z_019f47a1-a619-7000-9062-5f5d863afa45/local' against the active session.
- Exposed localProtocolOptions on extension contexts and covered the runner propagation path.
Fixes#4946
The assistant message_end fan-out is fire-and-forget in the session layer
and can be parked on extension delivery while agent_end is flushed through
#endInFlight, so agent_end can overtake it. #finishPrompt then unsubscribes
the prompt turn and the mapAssistantMessageEnd fallback never runs: an ACP
client that only received agent_thought_chunk updates (thinking streamed,
text arrived only on the trailing message) stays stuck on the thinking
block with no visible answer. On agent_end, emit the last assistant
message's text before resolving the prompt when live-message progress shows
no text was ever delivered, and defer the live-state reset past that flush
so a late message_end cannot resurrect fresh progress and double-emit.
Fixes#4902
The first-result viewport-repaint gate assumed only streamed
__partialJson placeholder shapes (SSH) could re-anchor; the write
renderer's collapsed pending preview paints a tail window from decoded
content, so its first partial result re-anchored to the top of the file
and left the committed tail rows stale above the new frame.
Resolve forceFirstResultViewportRepaint per renderer as a boolean or an
(args, options) predicate evaluated at paint time: write opts in when a
collapsed preview outgrew the streaming tail window, SSH stays scoped to
the streamed-placeholder shape it always covered.
Adopted from PR #4478 (roboomp) with an allocation-free line-count scan
and terminal-buffer regression coverage.
Fixes#4477
Esc during an active streaming turn required a second press within 2s
(two-step arm from #3493). In the no-input-waiter submit path the turn
starts with isStreaming=true but no working loader, so Esc fell into
the two-step branch and the agent_start subscription then wiped the
arm — repeated presses kept re-arming and never aborted. The loader-up
path already aborted on a single press, so the confirmation guarded no
coherent state. First Esc now aborts the streaming turn directly.
Adopted from PR #4938 (test + input-controller + changelog hunks only;
unrelated workflow-notice.md churn dropped).
Fixes#4921
recall (includeFacts) surfaces facts.fact_id as a result id, but
store.get only searched working_memory + episodic_memory, so every
surfaced fact id was a dead end for 'read memory://<id>' and
memory_edit ('not found in any scoped bank').
- store.get now falls back to the facts table (visibility mirrors
factRecall: same-session or scope='global'), returning a read-only
row with memory_store 'fact' and the full triple as content.
- coding-agent labels the store honestly ('fact') in memory:// reads
and reports not_editable (instead of not_found) for memory_edit ops
on fact ids; the facts table stays immutable.
Fixes#4725
Built-in model discovery admitted providers via peekApiKey, which
deliberately never refreshes OAuth rows, so a provider whose only stored
credential was an expired OAuth token was silently dropped from online
discovery and its token was never rotated (model selector 'refresh'
stayed empty for logged-in users).
Resolve built-in discovery keys through an online-only preflight that
refreshes an expired stored OAuth credential, applying the disabled/
configured/targeted provider filters before the side-effecting
resolution so refreshProvider(x) cannot rotate unrelated credentials.
Offline discovery stays peek-only. Under online-if-uncached the
preflight consults the same cache freshness the model manager uses
(2h default TTL, 5min non-authoritative retry) so tokens refresh
exactly when the manager will fetch — a fresh cache never triggers a
token-endpoint call.
Adopted from PR #4896 with two amendments: dropped an unrelated
workflow-notice.md prompt edit, and aligned the preflight cache TTL
with the manager's real 2h default (was 24h, which skipped the refresh
on the common startup path for caches aged 2-24h; regression covered
by the new online-if-uncached tests). Also corrected the stale
'Default: 24h' doc on cacheTtlMs in the catalog.
Fixes#4893
Co-authored-by: roboomp <omp@can.ac>
- Removed bundled reviewer and plan thinking-level hard pins so their model roles can supply configured effort.
- Added regression coverage for bundled reviewer and plan parsing.
- Updated the coding-agent changelog.
Fixes#4761
Extensions calling ctx.ui.addAutocompleteProvider (e.g. @ff-labs/pi-fff)
crashed at load with 'TypeError: ... is not a function' because omp's
ExtensionAPI.ui omitted pi's autocomplete-provider API; the throw also
aborted the rest of a try/catch-guarded session_start init.
ExtensionUIContext now declares addAutocompleteProvider(factory).
Interactive mode stacks each factory on the built-in editor provider in
registration order, re-applies the stack on every slash-command refresh,
and skips throwing/malformed factories; RPC, ACP, and headless contexts
accept the factory as a no-op, matching upstream pi's RPC behavior.
Fixes#4919
Extension sendUserMessage() without deliverAs fell through to prompt(),
which throws AgentBusyError during an active stream; the message was
dropped and surfaced as 'Extension sendUserMessage failed'. Route the
omitted-deliverAs path through prompt() with streamingBehavior 'steer'
so streaming queues a steer with normal prompt-flow side effects
(keyword notices, advisor auto-resume reset) and idle still starts a
turn.
ACP skill-command prompts now pass streamingBehavior 'steer'; the RPC
skill fast-path honors the prompt command's streamingBehavior field
(default steer) like the plain-prompt path already did. Documented the
extension-facing delivery semantics.
Synthesized from PR #4942 (prompt-flow steer routing, docs, tests) and
PR #4922 (RPC streamingBehavior threading, steer regression test);
dropped PR #4942's unrelated workflow-notice.md ellipsis churn.
Fixes#4923
Co-authored-by: roboomp <omp@can.ac>
Co-authored-by: metaphorics <metaphorics@users.noreply.github.com>
Named profiles loaded keybindings only from their own agent dir
(~/.omp/profiles/<name>/agent), silently dropping user-level bindings
from ~/.omp/agent/keybindings.* — e.g. Backspace remaps under
tmux/QTerminal. KeybindingsManager.create now merges the default
profile's keybindings under the active profile's, with the profile file
overriding per binding. The inherited file is loaded read-only so a
named-profile run never writes migration output into the default
profile's dir. Documented the exception in docs/config-usage.md.
Adopted from PR #4869 (dropped its unrelated workflow-notice.md churn,
added the read-only inherited load and its regression test).
Fixes#4867
Co-authored-by: roboomp <omp@can.ac>
Moved the config.yml/config.yaml filename order into pi-utils as
MAIN_CONFIG_FILENAMES and taught the auth-broker config reader to probe
both extensions with the same precedence as the settings loader.
Fixes#4914
Copied the selected main config path into per-CWD settings clones so config.yaml-backed sessions keep writing to config.yaml.
Added regression coverage for cloneForCwd updates against preseeded config.yaml.
First-run settings load now discovers an existing config.yaml next to
config.yml, loads it as the main settings file, and keeps writing back
to the discovered path instead of creating a stub config.yml beside it.
Refs #4914
The v16.3.12 explicit `selector` field (ff3b0c795c) was consumed by the
read tool but never threaded into the TUI renderers: ReadRenderArgs in
both readToolRenderer (read.ts) and ReadToolGroupComponent only derived
selectors from path-embedded `:sel` suffixes, so split-arg calls like
{ path, selector: "2-3" } rendered bare paths without line ranges or
raw modifiers.
Joined the explicit selector (trimmed, leading colons stripped, non-string
guarded) back onto the display path in renderCall, renderResult error and
success branches, and the grouped read summary, keeping hyperlinks on the
base path only.
Adopted from PR #4904 (both commits squashed), minus its unrelated
workflow-notice.md prompt churn.
Fixes#4899
- Replaced the unbounded native fetch (no total cap; no per-file cap for
open-ended ranges) adopted from PR #4903 with finite budgets: per-file
fetch covers bounded ranges up to endLine and open-ended ranges up to
startLine-1 plus the kept window, clamped to the native file-size
ceiling; the global ceiling scales by the same amplification.
- Threaded the scaled ceiling through mergeGrepResults so mixed
native+virtual ranged searches are not re-truncated pre-filter.
- Dropped the unrelated workflow-notice.md ellipsis churn from the PR.
- Added open-ended directory selector coverage.
Fixes#4898
- Raised or removed native grep pre-filter caps when line selectors are present so later selected lines are available to the post-filter.
- Added coverage for directory selectors beyond the normal multi-file per-file cap.
- Applied explicit grep selectors as per-file line filters for directory and glob searches instead of pre-validating them as single files.
- Clarified the grep selector prompt/schema language and added regression coverage for directory searches.
Fixes#4898
Models emit optional string args as empty strings; since ff3b0c795
(#4622) read/grep rejected a present-but-empty selector as invalid
instead of behaving like an omitted one. Normalize empty and
whitespace-only selector params to undefined before validation.
Adopted from PR #4881 minus unrelated prompt churn.
Fixes#4879
When the JS eval worker falls back to the in-process inline path, concurrent
JsRuntime instances share one realm. setCwd used to throw on exclusive-owner
conflicts, and the microtask delivery path turned that into a fatal
unhandledRejection that postmortem exited on. Stamp local cwd without
stealing the active realm, report init failures over the worker protocol,
and cover process survival with in-process and child-process regressions.
- Wrote a one-shot stderr working indicator before text print-mode prompts wait on the model.
- Covered pending text/json print-mode behavior with focused tests.
Fixes#4901
Address PR #4890 review: grok-4.5 defaults reasoning.effort to high, but xAI documents low as the tier for latency-sensitive tool calling. buildRequestBody now sets reasoning.effort=low so web search avoids paying for high-reasoning tokens and is less likely to hit the 60s hard timeout. Update the request-body regression tests to defend the new shape.
The executeBash direnv preflight clamps its load budget to a positive
caller command timeout, but the PTY / ACP-terminal backend preflight in
bash.ts passed the raw bash.direnvLoadTimeoutMs (30s default) with no
clamp — so a short-timeout command routed through those backends could
hang up to 30s on a cold `.envrc` before its own timeout is even
installed. Centralize the clamp inside applyDirenvPreflight (new
callerTimeoutMs option) so every backend inherits one contract:
`timeout: 0`/undefined keeps the full budget, a positive deadline clamps.
Co-Authored-By: seal <noreply@sealedsecurity.com>
Extract applyDirenvPreflight() so the ACP client terminal and PTY backends
get the same direnv/devenv overlay as executeBash (previously only the
one-shot path did). The helper is a pure (command, env) transform — merge
direnv's set under the caller's overlay, prepend a regex-gated unset -v for
removed vars — so interactive backends keep their own env shape (live TERM)
while executeBash still layers its non-interactive defaults on top. The three
dispatch branches are mutually exclusive, so no command is preflighted twice.
Also drop the content-hash export cache in loadDirenvEnv: always run
direnv export json and let direnv's own watch/mtime invalidation decide
freshness, so a changed watched file re-exports even when .envrc is unchanged.
Co-Authored-By: seal <noreply@sealedsecurity.com>
Drain stdout and stderr concurrently so a cold .envrc/devenv load can't fill the stderr pipe and block until the timeout cap. Thread the caller's abort signal and per-call timeout into the direnv preflight (AbortSignal.any + min timeout) so an aborted or short-timeout bash call returns promptly. Return the full direnv export diff and honor variable *removals*: the per-command env overlay can only add/override, so prepend a shell-level 'unset -v' for direnv's unset list, gated by a POSIX-identifier regex and skipped when the caller re-supplied the var. Tests: skip the real-direnv cases when direnv is absent, and isolate HOME/XDG so 'direnv allow' never writes into the developer's global store; add unset coverage.
The bash tool's persistent shell didn't carry a repo's direnv/devenv
environment, so devenv-provided tools (moon, project-pinned biome/bun,
toolchains) were off PATH and .envrc-set vars (e.g. GIT_DIR for a jj
secondary workspace) were missing. Resolve the nearest .envrc from the
run cwd, load its env via direnv export json, and merge it under the
caller's per-call env. Gated by bash.direnv (default auto, auto-allows).
Reset the jj label/status caches in invalidateGitCaches so a watcher HEAD move refetches; key the async jj lookups by their captured root so a mid-flight repo switch can't land one repo's label/counts in another's cache; thread effectiveGitCwd through the jj lookups so an active child repo uses its own jj root. Gate jj status on a detached/absent git HEAD (mirroring the jj branch overlay) so a nested ordinary git checkout under a jj workspace keeps its own git status instead of the ancestor jj status. Adds cache-coherence + mid-flight-race regression tests.
Carry task.blocker through AgentSession clone so a blocker reason survives storage round-trips; skip non-open tasks when blocking a phase so completed/abandoned work is never reopened; reject targetless block/unblock; render blocked rows distinctly (warning fg + reason) in the tool renderer and keep them visible in the collapsed HUD. Adds regression tests (red-green verified on the clone path).
collectShareRegexSecretValues (export/share.ts) and
collectAdvisorRegexSecretValues (advisor/runtime.ts) only skipped
strings already shaped like a "data:image/..." URL, but
ImageContent.data at rest is raw base64 - that URL form only exists
in the rendered viewer. Every image payload was regex-scanned like
any other string, wasting CPU on large screenshots the advisor/share
output never even renders (images render as "[image]"/are left
byte-for-byte intact), and an accidental regex match inside the
base64 bytes could poison the whole-batch collision set used to
decide whether other fields' friendly-name placeholders are safe.
Skip type: "image" blocks entirely in both generic tree walks.
The advisor's local obfuscateAssistantMessage rewrote text and
toolCall blocks but left thinking blocks untouched, so a friendly
placeholder minted for an earlier secret could still leak a
regex-protected value discovered later in the same delta once
rendered via includeThinking. Apply the same obfuscation as text
blocks, mirroring the existing thinking handling in
stripUnsafeFriendlyPrefixesFromAssistantContent.