* fix(ports): ignore malformed lsof listener pids
* fix(infra): strictly reject malformed lsof pid tokens
Number.parseInt truncated tokens like "111abc" to 111, silently accepting
a corrupted lsof pid record. Switch to parseStrictPositiveInteger so the
whole token must be a valid positive integer, and restore the deduped
listener test that a prior change had replaced instead of extended.
* fix(cli): strictly reject malformed lsof pid tokens
Harden the force-free lsof parser to reject digit-prefixed garbage like
p111abc instead of truncating it to pid 111, and cover the sibling parser
with a focused regression test.
* fix(infra): strictly parse stale lsof pid tokens
Harden the stale gateway cleanup parser to reject digit-prefixed garbage
like p111abc instead of truncating it to pid 111, and add a focused
regression test without dropping the existing argv verification coverage.
* fix(ports): fail closed on malformed lsof pids
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(media): allow aws-sdk auth mode for image and audio/video paths
Media understanding tools failed for amazon-bedrock deployments using
auth.mode "aws-sdk" (BYOK via role/SSO/profile creds). Each path called
requireApiKey, which throws on the empty-key sentinel before the AWS SDK
credential chain can resolve creds at call time.
The image path (image.ts) resolves auth via getApiKeyForModel; the
audio/video paths route through resolveProviderExecutionAuth. Both now
mirror the chat path's allowMissingApiKeyModes allowance: when the
resolved key is empty and the mode is aws-sdk, execute keyless and let
the SDK resolve credentials. The image path skips setRuntimeApiKey so no
empty-string secret is persisted; audio/video return the kind:"none"
execution auth so the runner bypasses key rotation.
Closes#72031
* fix(media): cover Bedrock PDF SDK auth
* fix(pdf): register plugin completion streams
* fix(media): recognize Bedrock SDK tool auth
* chore: move release note to PR
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(agent-core): handle stdout/stderr stream errors in harness exec
Register error handlers on stdout and stderr streams in
NodeExecutionEnv.exec() to prevent uncaught exceptions when
a pipe breaks (e.g. EPIPE after child process exit).
Without these listeners, Node.js throws an uncaught exception
that crashes the entire agent-core process.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(agent-core): address review — hoist-safe mock and curly lint
- Use vi.hoisted pattern for spawnMock to fix ReferenceError in CI
- Wrap one-line if guard in braces per repo curly rule
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: keep release notes in PR context
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(codex): scope app-server rate limits to the physical client
Replace the process-global rate-limit cache with a WeakMap keyed by the
physical app-server client, tracking per-limitId revisions. Rolling
account/rateLimits/updated notifications merge sparsely per the protocol
contract (credits/individualLimit/planType survive nulls), and
usage-limit errors only trust a snapshot for auth-profile blocking when
the same client observed a primary update during the failing turn's
startup. Fixes cross-client rate-limit bleed in usage-limit error
messages. A new client-runtime module installs the
account/chatgptAuthTokens/refresh handler and the rate-limit observer
once per physical client, replacing per-start inline handlers in
shared-client, run-attempt, and side-question.
* refactor(codex): split thread/resume subscription safety into thread-resume
Move the thread/resume request out of thread-lifecycle into a dedicated
thread-resume module that retires the exact physical client when resume
acceptance is indeterminate: only a structured RPC rejection proves
Codex holds no subscription, so any other failure abandons the client
instead of returning it to the shared pool. Resume responses naming a
different thread now fail closed (assertCodexThreadResumeSubscription),
and the fresh-thread fallback requires a released subscription unless
the resume was a proven RPC rejection.
* refactor(codex): replace client-factory positional DI with shared-client factory
Delete the lazy positional-argument CodexAppServerClientFactory and use
the options-object factory type exported from shared-client. Callers in
run-attempt, compact, bounded-turn, provider-capabilities, and the
web-search provider now default to getLeasedSharedCodexAppServerClient
directly; the lazy indirection was ineffective because those modules
already import shared-client statically.
* feat(codex): route app-server turn traffic through a keyed turn router
Install one turn router per physical app-server client and replace the
broad per-attempt notification/request fanout with explicit per-thread
routes. Attempt startup reserves the thread route (before thread/resume
on the resume path, so early notifications buffer instead of racing),
run-attempt activates it with receive-time, queued, and request
handlers, arms the route before turn/start, binds the accepted turn id
to flush buffered traffic in wire order, and releases the route on
cleanup. Requests for a pending turn wait for binding instead of being
auto-declined, native turn completion waits use route state instead of
scanning buffered notifications, and correlation readers now match the
canonical v2 wire shapes only (top-level threadId, nested turn.id). The
unscoped response-delta lease-count attribution and its client API are
deleted along with the retired correlation predicates.
* test(codex): reset the shared binding store between thread-lifecycle tests
SQLite bindings are keyed by session identity rather than the per-test
temp dir, so earlier tests leaked resumable threads into fresh-start
expectations. The old silent resume-failure fallback masked the leak;
subscription safety surfaces it.
* test(codex): reset the binding store between delivery-hint iterations
The loop reuses one session identity across iterations, so the previous
iteration's thread would resume against a harness that cannot serve it.
resolveSupportedThinkingLevelFromProfile sorts the supported levels descending,
then, when every supported level exceeds the request (e.g. a stored `off` on a
profile that has no off level), fell through to `ranked.find(non-off)` — the
first entry of a descending list, i.e. the most expensive level. A session
carrying thinking `off` that switched onto such a model was silently remapped to
maximum reasoning and persisted (src/auto-reply/reply/directive-handling.persist.ts).
Use `ranked.findLast(non-off)` so a below-range request resolves to the cheapest
non-off level instead. Regression test included (red before, green after).
This surfaces with the first bundled no-off thinking profiles (Fireworks
GPT-OSS 120B, see #92217), but the clamp lives in shared core logic, so it ships
on its own.
* fix(gateway): show last error when status probe fails
* fix(gateway): scope status log diagnostics
---------
Co-authored-by: wAngByg <wAngByg@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(tui): suppress adjacent history-replay/live-final duplicate in ChatLog (#96967)
When sessions.changed triggers loadHistory(), assistant rows are replayed
via finalizeAssistant(text) without a runId. A late live final with the
same text and a runId can land immediately afterward, producing a visible
duplicate assistant reply.
Track the most recent history-replay assistant component in ChatLog and
suppress a following live final when the text matches and no other
component was appended in between. This is simpler and more targeted
than the prior event-handler approach because it operates at the
rendering layer where the duplicate is actually visible.
Fixes#96967
* fix(tui): bound replay-final dedupe window in ChatLog
* fix(tui): bound replay-final dedupe marker with TTL expiration
* fix(tui): satisfy no-promise-executor-return lint rule in chat-log test
* fix(tui): replace ChatLog text heuristic with runId-based surrender tracking
Drop the lastHistoryAssistant text-matching dedupe in ChatLog
(posed P1 false-suppression risk). Instead, track active runIds
surrendered to loadHistory() on sessions.changed 'new' — late
events for surrendered runs are suppressed by exact runId match.
Add ChatLog.hasStreamingRun() so the event handler can detect
when loadHistory restored a surrendered run as in-flight streaming.
Retain chatFinalizedRuns / chatFinalizedRunsWithDisplay from the
original fix for post-reload delta/error dedup of finalized runs.
Incorporates the precise surrender-tracking approach from closed
PRs #96979/#96986 while keeping the scoped ChatLog API minimal.
* fix(tui): render displayable late final for surrendered non-streaming run
When a sessions.changed 'new' surrenders an in-flight run and
loadHistory does not restore it as streaming, a late displayable
final must still be rendered — otherwise the user never sees the
assistant reply. Only suppress non-displayable finals (empty
message, no errorMessage).
Add focused test for non-displayable final suppression.
* test(tui): add coverage for empty in-flight restore + late displayable final
Covers the rank-up scenario from #96979: surrendered run restored
by loadHistory as empty streaming, late displayable final passes
through surrender check and renders to chatLog.
* fix(tui): suppress late finals for surrendered finalized runs to prevent history-replay duplicate
When sessions.changed 'new' fires, now surrender chatFinalizedRuns entries
too (as 'finalized'), not just sessionRuns ('in-flight'). This prevents
the finalized-before-reload history-replay duplicate: a late final for an
already-displayed run gets suppressed because the history-replay static
row covers the visible content.
In-flight surrendered runs still render displayable finals because the
user may not have seen the streaming text before the reload.
SurrenderedToHistoryRunIds is now Map<string,'in-flight'|'finalized'>
so the surrender check can branch on source. Move surrender check before
the finalizedRuns/chatFinalizedRuns guard so surrendered finalized runs
are routed correctly.
Add focused tests for history-replayed-visible (suppress) and
history-replay-missing (render) late finals.
* fix(tui): extend surrender coverage to sessions.changed reset reload path
Reset also calls clearTrackedRunState() + loadHistory() with the same
sessionKey, so late chat events for runs from the old session window
can race with the history replay the same way as 'new'. Surrender
sessionRuns and chatFinalizedRuns on both reasons (#96979 P1 rank-up).
* fix(tui): keep surrendered finalized marker after late delta to block later final
For finalized surrendered runs, a late delta must not clear the
surrender marker — otherwise a subsequent late final would slip
through and produce a duplicate. Only in-flight surrendered runs
clear the marker on delta (their final needs to render).
* fix(tui): gate session history reloads on persistence
Co-authored-by: xialonglee <li.xialong@xydigit.com>
* docs(changelog): note TUI external-run dedupe
* test(tui): type history load harness results
* test(tui): defer history results without async mocks
* chore(changelog): defer TUI note to release
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor(codex): raise app-server floor to 0.142 and drop range-compat protocol paths
* refactor(codex): model subagent mirror state as one map
* style(codex): format event-projector
* test(codex): drop unused shared-client test import
* refactor(codex): drop v1-era notification field aliases
* fix(codex): teach models to load deferred native spawn_agent via tool_search
* docs(codex): realign harness config tables
* docs(changelog): note Codex app-server protocol update