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.
Op: correct
Restores: ref:44e5e0bb8 — queued /skill: chip lifecycle parity with plain-text steer
EventController.#handleMessageStart now mirrors the user-role refresh in
the custom branch, gated on readPendingDisplayTag(details). Without this,
AgentSession's tag-keyed dequeue mutated #steeringMessages /
#followUpMessages correctly but pendingMessagesContainer kept painting
the stale chip until an unrelated trigger (next user submit, dequeue key,
compaction flush) fired a refresh.
Non-queued custom variants (ttsr-injection, irc:*, async-result,
hookMessage) skip the refresh — they never registered a pending chip, so
rebuilding pendingMessagesContainer for them would be pure waste.
Pairs with the existing E4 (array splice) regression — the new E10 covers
the UI-refresh side of the same dequeue event with both positive and
negative gate assertions.
Co-Authored-By: chatgpt-codex-connector[bot] (P2 review on PR #1043)
- 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`.
Status-line's context_pct segment was computing tokens via
calculatePromptTokens(lastAssistantMessage.usage), which sums input +
cacheRead + cacheWrite from the Anthropic API usage object. The /context
slash command is computed by computeContextBreakdown, an offline estimate
over the live session state (systemPrompt + tools + skills + messages).
Both numbers are correct under their own definition, but they can
diverge by 2x+ on the same session when a turn rotates cache tiers
(e.g. 5m → 1h ephemeral re-cache) and cache_creation_input_tokens spikes.
Users read the two surfaces as one consistent dashboard and treat the
mismatch as a bug.
Repro: same session at the same moment reports 212K (21.2%) in /context
and 44.2%/1M in the status line — ~230K gap driven by per-turn
cache_creation on a system-prompt boundary.
This change makes status-line use the same computeContextBreakdown
source as /context so both surfaces stay consistent. The breakdown
result is cached with a 2s TTL inside the component so the per-frame
status-line render does not re-walk every message via
estimateMessagesTokens on long sessions. The Anthropic API per-turn
prompt size remains observable via existing token_in / cache_read /
cache_write / token_total segments.
- Updated the TUI shutdown slash command handler to return a `SlashCommandResult` instead of `void`.
- Returned `commandConsumed()` after clearing the editor and invoking runtime shutdown.
- Imported `SkillPromptDetails` as a type in the input controller message imports.
- Zed dispatches RPC responses and notifications on separate async tasks, so `setTimeout(0)` lost the race against the session registration handler.
- Dropped `available_commands_update` left the slash-command palette empty (#1015; zed-industries/zed#55965).
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
- Adds omp acp subcommand that launches the agent as an ACP stdio server
- Registers the subcommand in the CLI dispatcher
- Threads terminal-auth args and ACP flags through the launch and main orchestrators
- Exports AgentSession on the public SDK surface
- Updates skills loader to support skill→slash-command conversion and prompt injection
- Updates input-controller to dispatch ACP built-in slash commands
- Adds create/resume/list/page session lifecycle handling to AcpAgent
- Implements mode switching (default vs. plan), MCP server configuration, and model/thinking config negotiation with the connected client
- Routes incoming ACP connections to AgentSession via the new ClientBridge
- Pinned final plan path before handleCompactCommand so queued messages use approved plan, not draft.
- Added regression coverage for setPlanReferencePath timing before compaction queue flush.
- Updated event-controller read-tool streaming handling to wait for a parseable target before routing tool calls, avoiding early component binding for unresolved arguments.
- Allowed internal-URL read calls to bypass the regular read grouping path and fall through to direct tool execution.
- Adjusted read tool rendering to honor the computed `expanded` flag for completed output instead of forcing expanded output.
- Added readArgsTargetInternalUrl in the read tool group component to detect targets handled by InternalUrlRouter from path or file_path arguments.
- Updated event-controller and UI helper read rendering paths to skip grouping read tool calls when those arguments target internal URLs.
- Passed session cwd and settings into internal URL resolution in read.ts and added tests for internal versus non-internal target detection.
The extension shutdown context action installed by
ExtensionUiController.initializeHookRunner was an empty stub, so
ctx.shutdown() in interactive mode silently did nothing while extensions
fell back to process.exit(0), bypassing session flush and terminal
restore. Flip InteractiveModeContext.shutdownRequested so the main
loop's existing checkShutdownRequested() drives the graceful path.
Fixes#1020.
Slash skill invocations bypassed the Enter / Ctrl+Enter contract that
every other slash command honors. Now Enter steers, Ctrl+Enter queues
a follow-up, sharing one #invokeSkillCommand helper between the editor
submit handler and handleFollowUp. Compaction short-circuit ordering
preserved.
Closes#1033
Adds a new `rpc-ui` mode that extends the existing headless RPC mode with
interactive tool support (ask tool, extension UI dialogs, etc.).
In plain `rpc` mode the session has `hasUI=false` and no UI context is
wired, so interactive tools are disabled. `rpc-ui` mode sets `hasUI=true`
and wires a single shared `RpcExtensionUIContext` instance into both the
tool context store and the extension runner. Both consumers share the same
`pendingExtensionRequests` map and output closure, so `extension_ui_response`
messages received on stdin are routed to the correct waiting promise
regardless of which code path (tool or extension) created the request.
Changes:
- `args.ts`: add `rpc-ui` to the `Mode` union and the parse guard
- `launch.ts`: expose `rpc-ui` in the OCLIF flag definition and help text
- `main.ts`: propagate `rpc-ui` through all RPC-mode guard conditions and
pass `setToolUIContext` to `runRpcMode` when the mode is `rpc-ui`
- `rpc-mode.ts`: accept optional `setToolUIContext` callback; create one
shared `RpcExtensionUIContext` instance and pass it to both the tool
context store and the extension runner
Makes `/skill:<name> [args]` work identically under both submission
keybindings, mirroring how free text is already routed during streaming:
- `/skill:foo` + Enter, streaming -> steer queue (interrupt)
- `/skill:foo` + Ctrl+Enter, streaming -> followUp queue
- `/skill:foo` + Enter, idle -> idle prompt
- `/skill:foo` + Ctrl+Enter, idle -> idle prompt (was: literal text)
A single private helper `#invokeSkillCommand(text, streamingBehavior)`
on `InputController` handles the dispatch; the Enter submit handler
calls it with "steer", and `handleFollowUp` calls it with "followUp"
after the compaction short-circuit so a skill typed during compaction
rides the same `queueCompactionMessage` queue as free text.
Behavior deltas vs upstream/main:
- Enter on `/skill:foo` during streaming now steers (was: queued as
followUp). Users who relied on the followUp default can press
Ctrl+Enter -- the same key they already use for free-text follow-ups.
- Ctrl+Enter on `/skill:foo` is new capability; previously the
literal string `/skill:foo ...` was sent as plain followUp text and
the skill was never invoked.
Op: extend
A fifth ExitPlanMode approval choice — sits between the existing
"Approve and execute" (purge session) and "Approve and keep context"
(full transcript). Runs `handleCompactCommand` against the plan-mode
transcript with a planning-specific custom instruction rendered from
`plan-mode-compact-instructions.md`, then dispatches the plan-approved
synthetic prompt so it lands as the first entry in the freshly-
summarized transcript — giving execution a fresh cache anchor with
the rationale carried over.
Cancel/fail contract:
- ok → bookkeeping runs, plan-approved synthetic prompt dispatched.
- cancelled → bookkeeping runs (tools restored, plan reference path
recorded), warning surfaced, dispatch skipped.
`markPlanReferenceSent` is intentionally deferred past
the cancel guard so `AgentSession.#buildPlanReferenceMessage`
re-injects the plan on the operator's next prompt() call.
If we marked it sent on cancel, the executor's first turn
would have no plan context.
- failed → bookkeeping runs, error already surfaced by executeCompaction,
dispatch proceeds best-effort. Approval intent stands.
Cancel vs. fail is discriminated via `instanceof CompactionCancelledError`
at the session/compaction error boundary (introduced in the previous
commit), so any abort source — operator Esc, extension hook, programmatic
abort — classifies uniformly without input-modality or message-string
coupling.
Op: extend
Introduce `CompactionCancelledError` and `CompactionOutcome` ("ok" |
"cancelled" | "failed") so callers can discriminate user-driven aborts
from generic failures via `instanceof`, instead of inspecting error
messages or `AbortError`-name strings.
`AgentSession.compact()`'s two abort-rejection sites now throw the
typed sentinel; the model-call wrapper normalizes AbortError-shaped
rejections to the sentinel only when the compaction's abort signal
is actually set, preserving every other exception unchanged so real
compaction bugs are not silently relabeled as cancellations.
`CommandController.executeCompaction` and `handleCompactCommand`
return `Promise<CompactionOutcome>`; the catch classifies via
`instanceof CompactionCancelledError`. Existing callers (`/compact`,
loop runner, auto-compact) ignore the return value — non-breaking.
Op: extend
- 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.
- 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.
- Updated addMessageToChat in UI helpers and InteractiveModeContext to return rendered components instead of void.
- EventController now tracks IRC message components and removes them after a 10-second TTL, avoiding duplicate expiry scheduling per message signature.
- EventController dispose now clears all pending IRC expiry timers and tests were added for immediate render, TTL removal, duplicate suppression, and timer cleanup.
- Added edit streaming fallback data to the edit render context and used it when no finalized patch text was available.
- Implemented hashline fallback rendering that strips envelope syntax, sanitizes text, and truncates output to a fixed number of lines with a streaming indicator.
- Updated tool execution to populate the fallback preview from the active edit strategy and added a renderer test for hashline envelope input while the diff is not yet computable.
The HTML export feed, sidebar tree, and TUI session tree did not handle
the developer role, so plan content injected after /plan approval (and
any other developer messages) was hidden from /export and shown as a
bare [developer] label in /tree.
Renders developer messages in the main feed with a dimmed
.developer-message style, labels them in the sidebar tree, counts them
in header stats, and shows their content in the TUI tree selector so
search matches the body.
Closes#753.
Co-Authored-By: omp <noreply@oh-my-pi.dev>
- Trim logo from 14w to 12w (2-wide legs).
- Diagonal BL→TR gradient instead of per-line LTR.
- Truecolor: 3-stop magenta→violet→cyan path that skips the deep-blue valley.
- 256-color: same 6-stop ramp as fallback.
- Compute the colored logo once at module load instead of per render.
- Added asynchronous Kitty conversion for assistant tool images using `convertToPng`, keyed per tool-call entry with cached and in-flight tracking.
- Updated assistant image rendering to prefer converted PNGs for Kitty terminals while preserving existing behavior for other protocols.
- Added a unit test that verifies WebP tool images are converted and rendered as Kitty image output instead of the raw image/webp fallback.
The server-side OAuthCallbackFlow callback window is 300_000 ms, but the
client starts its #send timer before the RPC command is dispatched. By
the time the callback server actually starts, some time has already been
consumed on the client side. If the user takes the full callback window
plus token exchange, the client 300_000 ms timeout fires first, rejects
login(), cleans up the onOpenUrl listener, and the server response
arrives into a discarded pending entry.
Use 600_000 ms (10 min) on the client — double the server window. The
server will always return an error or success response within its own
window, so this timeout is purely a safety net against server crash or
connection loss and never fires during a normal login.
Replace the static RPC_LOGIN_PROVIDERS allowlist with runtime detection
based on callback ordering.
Providers that support headless login (OAuthCallbackFlow) always call
onAuth first (emit the browser URL), then onManualCodeInput only as a
fallback for manual redirect-URL entry. Providers that require interactive
input (API-key paste, device-flow prompts, GitHub Enterprise URL) call
onPrompt before any onAuth fires.
onPrompt now branches on authEmitted:
- false (onPrompt before onAuth): reject immediately with a clear 'not
supported in RPC mode' error. The rejection propagates through
authStorage.login and is caught by the try/catch, returning an error
response. No deadlock.
- true (onPrompt after onAuth, inside OAuthCallbackFlow's fallback race):
return a never-settling promise so the race defers to the callback
server. A rejection here would be swallowed as null by .catch() and
spin the while(true) loop.
New providers self-classify by their actual call order with no list to
maintain.
OAuthCallbackFlow.#waitForCallback races the browser callback against
onManualCodeInput and catches any rejection as null. When onPrompt
threw, the catch turned it into null, the while(true) loop saw a
falsy result, and immediately re-invoked onManualCodeInput — a tight
spin that starved the callback server and made browser-callback logins
hang until timeout even when the user completed auth successfully.
Replace the throw with a never-resolving Promise<string>. The race
then blocks waiting for the callback server to deliver the code,
with no busy-looping. When the server-side login eventually
times out or the browser callback arrives, the pending promise is
abandoned and GC'd.
Addresses: https://github.com/can1357/oh-my-pi/pull/987#discussion_r3213450206
#send uses a hard-coded 30s timeout. OAuth flows require the user
to open a browser, authenticate with the provider, and wait for the
redirect — easily 1-3 minutes. Callers would see a spurious timeout
rejection while the server-side callback server was still live and
the user was still mid-flow.
Add optional timeoutMs parameter to #send (default 30_000, all
existing callers unaffected) and pass 300_000 from login().
Addresses: https://github.com/can1357/oh-my-pi/pull/987#discussion_r3213435093
RpcClient#handleLine was dropping extension_ui_request frames
(no isAgentEvent match → early return). Callers of login() had
no way to learn the auth URL, so the callback-server flow never
got a browser visit and the command hung until timeout.
- Add isRpcExtensionUiRequest type guard
- Add #extensionUiListeners set to RpcClient
- Dispatch extension_ui_request frames through that set in #handleLine
- login() accepts optional { onOpenUrl } callback; registers/
deregisters a listener scoped to the command's lifetime
Addresses: https://github.com/can1357/oh-my-pi/pull/987#discussion_r3213428270
- RpcCommand: add get_login_providers and login { providerId }
- RpcResponse: add typed responses for both commands
- RpcExtensionUIRequest: add open_url { url, instructions } event
(fire-and-forget; host opens the URL in system browser)
- rpc-mode: handle get_login_providers (returns provider list with
per-provider authenticated status) and login (drives authStorage.login
using RpcExtensionUIContext for onPrompt dialogs and notify for
onProgress; emits open_url for the auth page URL)
- rpc-client: add getLoginProviders() and login(providerId) methods
Anthropic counts sessions by metadata.user_id. Without this fix, OMP
generated fresh random entropy on every API request, inflating the
session count and preventing backend attribution to the authenticated
account.
Changes:
packages/ai:
- resolveAnthropicMetadataUserId() now accepts JSON-format user_id
matching real Claude Code's getAPIMetadata shape
({ session_id, account_uuid, ... }). Previously only the legacy
cloaking format was accepted on OAuth, causing stable caller-supplied
values to be silently discarded.
- AnthropicOAuthFlow.exchangeToken() and refreshAnthropicToken() now
populate OAuthCredentials.{accountId, email} from the token response
account block, removing the need for a separate /api/oauth/profile
round-trip.
- AuthStorage.getOAuthAccountId(provider, sessionId) returns the OAuth
accountId for the session-sticky credential, used to build
account_uuid in metadata.user_id. Guards against misattribution for
API-key, runtime-override, env-key, and fallback-resolver paths that
do not record a session credential.
packages/agent:
- Agent.metadataForProvider(provider) resolves request metadata for
the given provider via the installed resolver, or returns the static
metadata value. The plain metadata getter now returns only the static
value; provider-aware resolution is explicit.
- Agent.setMetadataResolver(fn) installs a (provider: string) resolver
evaluated per LLM request in agent-loop, after getApiKey records the
session-sticky credential, so account_uuid reflects the credential
actually used.
- AgentLoopConfig.metadataResolver is called with config.model.provider
after getApiKey, overriding the static metadata field.
packages/coding-agent:
- AgentSession.#syncAgentSessionId installs a metadata resolver that
builds { user_id: JSON.stringify({ session_id, account_uuid? }) },
matching the Anthropic session attribution format. account_uuid is
only included for provider="anthropic" to avoid leaking the OAuth
identity to third-party Anthropic-format-compatible providers.
- sessionId getter prefers providerSessionId when supplied via
AgentSessionConfig so all API paths (getApiKey, direct calls,
metadata resolver) share the same provider-facing session ID.
- prepareSimpleStreamOptions stamps session metadata on direct calls
(runEphemeralTurn, compaction, branch summary, title generation) so
they share the same session bucket as Agent.prompt requests.
- generateBranchSummary and generateSessionTitle accept a
(provider: string) metadata resolver evaluated after their own
getApiKey call for correct credential attribution.
- Updated ExtensionUIContext, InteractiveModeContext, and InteractiveMode to require editor factories to return CustomEditor instances.
- Removed the runtime compatibility guard and warning for non-CustomEditor implementations in setEditorComponent.
- Removed the test that verified rejection of non-CustomEditor factories in interactive-mode editor-component tests.