Two fixes on top of the paged transport:
- Near-limit v2 framing no longer materializes the full base64 transport:
chunk lines are generated lazily from a single serialization, the 64 MiB
reassembly ceiling is enforced via Buffer.byteLength before any
full-payload allocation, and RPC stdout writes drain with backpressure
one physical line at a time. Peak RSS for a 63 MiB response drops
~686 MB -> ~521 MB; a rejected 80 MiB response drops ~507 MB -> ~259 MB
(parity with the v1 path).
- get_messages_page errors now carry a machine-readable code
(session_busy | stale_cursor). Both bundled clients' high-level
getMessages() drains discard partial pages and fall back to the legacy
snapshot on either code — previously a cursor invalidated by a
background mutation (e.g. an appended bash message) threw instead of
falling back. Direct page calls remain strict.
A budget-aborted keep-alive subagent's job row (job id == agent id) settles
failed and is retained ~5 min; executeCancel short-circuited to
already_completed for that window, leaving the zombie registration
unkillable exactly when the user wants it dead. Fall through to
cancelAgentRegistration for settled rows; keep already_completed when no
lingering registration exists.
Also stop wiring AgentLifecycleManager.global() onto SDK sessions created
with a caller-supplied agentRegistry: the global lifecycle releases through
AgentRegistry.global(), so it would report a cancel while releasing an
unrelated global ref. Without a lifecycle, cancel falls back to
dispose + unregister on the session's own registry.
Addresses both Codex P2 review findings on #6319.
Collect TSImportEqualsDeclaration/TSExternalModuleReference targets so
legacy .ts/.cts extensions using `import x = require("pkg")` get their
bare dependencies pinned like plain require() calls. Fold of the #6256
follow-up (comicchang/oh-my-pi@1e54b68) requested on #6324.
Servers may allow the unauthenticated MCP handshake yet protect individual
tool calls via _meta["mcp/www_authenticate"]. The 'reauthorization is not
required' guard would silently abort the tool-challenge reauth path.
The getDiscoverableProviders() guard skipped awaiting runtimeDiscoveryPromise
when no config-discovery providers exist, so a cold deferred selector backed
only by runtime model managers (extension fetchDynamicModels) with implicit
local discovery disabled still resolved against the offline cache. Awaiting
unconditionally is free when no runtime managers are registered
(refreshRuntimeProviders early-returns); the full refresh fallback stays
gated on discoverable providers.
Route loadSessionMessagesReadOnly through transcript mode (collapsed to the
latest compaction) so history:// transcripts of on-disk sessions retain
failed/aborted assistant tails that the provider-context builder now drops.
- Implemented ModelRegistry.hasProvider to return true when a provider has live models, is discoverable, or is registered at runtime.
- Replaced AgentSession's internal provider check with #isKnownProvider that delegates to the new hasProvider method, updating related fallback logic.
Three read paths raced background model discovery on cold start:
1. `get_available_models` RPC (rpc-mode.ts) read the registry
synchronously and returned a partial catalog containing only
statically-bundled models.
2. `set_model` RPC (rpc-mode.ts) read the registry synchronously and
rejected discovery-backed selectors with "Model not found".
3. `--model <provider>/<pattern>` CLI flag deferred retry (sdk.ts:2078)
resolved synchronously after extension registration, before
discovery-backed providers had populated `#models`.
Paths 1 and 2 are fixed by exposing the existing in-flight background
refresh promise (`#backgroundRefresh`, already tracked and cleared by
`refreshInBackground`) via a new public
`ModelRegistry.awaitBackgroundRefresh()` method, and awaiting it at each
RPC read site. No-op when no refresh is in flight (warm sessions
unaffected).
Path 3 mirrors the cold-cache race fix already applied to the
default-role fallback on this branch (issues #6114, #6162, sdk.ts:2343):
when a deferred pattern is unresolved and any discoverable provider is
registered, run a cache-aware `refresh("online-if-uncached")` pass
before the retry. Reuses the existing discovery machinery rather than
introducing a new ordering dependency.
The `omp models` CLI never had this bug because it awaits
`modelRegistry.refresh()` directly before listing.
Behavioral characteristics:
- **Warm-session fast path preserved**: when no refresh is in flight
(`#backgroundRefresh === undefined`), `await undefined` resolves in a
microtask. No regression for sessions that don't need discovery or
have already settled.
- **Failure isolation preserved**: `refreshInBackground()` already
swallows discovery errors via `.catch(...)`, so `awaitBackgroundRefresh()`
resolves even when discovery fails — callers then read whatever models
made it into `#models` (built-in + cached). No new failure modes.
- **Scoped**: doesn't change `refreshInBackground()` semantics. Adds a
new read-only awaiter with minimal API surface. Reuses the
well-established `refresh("online-if-uncached")` pattern for the
deferred retry path.
Reproduction (get_available_models RPC, with any discovery-backed
provider configured in `~/.omp/agent/models.yaml`):
cd ~
{
sleep 1
printf '%s\n' '{"id":"m1","type":"get_available_models"}'
sleep 5
} | timeout 15 omp --mode rpc-ui --approval-mode yolo 2>/dev/null \
| grep '"id":"m1"' | jq '.data.models | {count: length, providers: ([.[].provider]|unique)}'
Before: discovery-backed provider absent from the response on cold start.
After: discovery-backed provider present.
Reproduction (--model CLI flag, same config):
omp --mode rpc-ui --model <discovery-provider>/<model-id> --approval-mode yolo
Before: exits 1 with "Model \"<discovery-provider>/<model-id>\" not found".
After: starts rpc-ui session with the requested model selected.
/usage and omp usage now report Synthetic (synthetic.new) quota state
via GET /v2/quotas (free, does not consume quota): the rolling 5-hour
request limit with per-tick regeneration rate, and the weekly credit
quota in USD. Applies to API-key credentials for the synthetic
provider; every payload section is parsed defensively since only
subscription is documented.
The aborted-task follow-up hint always references history://<agentId>,
including when no transcript can actually be served. Following that link
then fails.
Add hasResolvableTranscript beside sessionFilesFromDisk, mirroring the
availability half of HistoryProtocolHandler's resolution semantics: a
registered ref's live session, a retained session file verified on disk,
or a disk-scanned .jsonl under a known artifacts dir (which still serves
hard-aborted children whose refs were unregistered). Probing never
throws; a stale path or unreadable artifacts subtree reads as unavailable
instead of failing delivery of the settled result. Render the transcript
clause from that check, independently of the resume affordance, so a
still-resumable idle/parked agent keeps its hub resume hint and a
disk-backed transcript keeps its link. Idle-completion hints are
unchanged.
Signed-off-by: Christian Stewart <christian@aperture.us>
Rebuild Hindsight runtime state when API URL or token settings change. Keep active subagent aliases routed through the latest primary state and reset bank caches across endpoint changes.
When FIRECRAWL_API_KEY is not set, fall back to Firecrawl keyless mode
(omit Authorization header). Auto-chain still requires a credential via
isAvailable; explicit webSearch: firecrawl works keyless via
isExplicitlyAvailable returning true.
Closes https://github.com/can1357/oh-my-pi/issues/4332