* fix(ai): prevent websocket cache clobber on concurrent acquire
The WebSocket session cache acquire path (expired or non-reusable cached
entry) used unconditional delete/set around the connectWebSocket() await.
A concurrent request could install a newer entry during that await, which
the resuming acquire then clobbered, orphaning the socket that carried the
real previous_response_id continuation and corrupting multi-turn Codex
conversations.
Mirror the release path's owner-checked helpers: delete via
deleteOwnedWebSocketSession, and install via a new setOwnedWebSocketSession
that only writes when the cache still matches what this acquire left behind
(the stale entry, or undefined after removing it / on first connect). A
different cached entry means a concurrent request already won the session.
The busy-cached branch is unaffected: it returns a transient socket and
never writes the cache.
* fix(ai): close CAS loser websocket promptly without leaking its socket
* fix(ai): correct verifyClient callback type in race regression test
* fix(ai): remove unused variable and fix lint in race regression test
* test(ai): prove concurrent websocket loser closes
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(cron): bound restart catch-up to the active schedule
Editing a recurring job's schedule made the gateway fire it immediately on
the next restart. Startup catch-up compares the new schedule's previous slot
against lastRunAtMs, which still belongs to the retired schedule, so a slot
that never existed under the old schedule counted as missed.
Record when scheduling inputs take effect and replay a missed slot only when
it is newer than that. Jobs whose schedule never changed carry no stamp and
keep replaying every computed slot, so catch-up is unchanged for them.
The missed-slot predicate was duplicated in the runnable check and the
backoff-deferral pass; both now share one helper so the bound cannot drift.
Fixes#91944
* fix(cron): protect schedule activation ownership
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(ai): tool calls fail when an unsupported schema keyword is nested
The strip walked only properties, items, anyOf, oneOf and allOf, copying every
other value through untouched. A keyword the model rejects therefore survived
inside additionalProperties, prefixItems, patternProperties, contains,
propertyNames, not, if/then/else, dependentSchemas and $defs, and the request
was refused by the provider even though the strip reported success.
Walk the same containers the caller already enumerates in
agent-tools-parameter-schema.ts.
* fix(ai): cover all nested schema containers
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(ai): avoid websocket cache clobber on stale reused release
* fix(ai): prefer-const holdFirstReuse in websocket cache race test
* fix(ai): avoid websocket cache release clobber
* fix(ai): add curly braces for websocket cache delete guard
* fix(ai): owner-check websocket cache deletes without LOC growth
* ci: bump plugin SDK public surface budget pins to match main tip
* fix(ai): satisfy curly lint for websocket cache owner delete
* ci: allowlist readonly state db and format SDK surface pins
* style: oxfmt plugin-sdk surface report
* ci: drop duplicate readonly state db kysely allowlist entry
* test(ai): prove cached websocket lease ownership
Move the contributor stale-release regression into the canonical cached transport suite and prove authenticated real WebSocket close, cancellation, and queued idle expiry cannot evict a replacement lease.
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(gateway): manage audio and video attachments
* feat(gateway): preserve generated media metadata
* fix(gateway): align seeded managed media
* fix(gateway): scope pending media trust by URL
* fix(gateway): split managed media by trust
* fix(gateway): require terminal media trust unanimity
* fix(gateway): align managed media metadata types
* fix(gateway): refresh managed media contracts
* chore(ci): model managed media test seams
* test(sessions): expect canonical media kinds
* fix(terminal-core): prevent $ pattern injection in replaceHomePath
The second argument of String.prototype.replace() interprets $ sequences
as special replacement patterns ($&, $`, $', $1-$9, $<name>). When the
user home directory path contains $ followed by special characters, the
replacement produces a corrupted path in the display string.
Using a replacer function (() => fallbackHome) instead of the raw string
argument prevents $ pattern interpretation, ensuring the literal home
directory path is used regardless of its content.
* test(terminal-core): cover literal dollar home paths
---------
Co-authored-by: lizeyu-xydt <li.zeyu@xydigit.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* docs(agents): standardize canonical record guards
* refactor(model-catalog): use canonical record guard
* refactor(scripts): use canonical record guard
* refactor(ui): use canonical record guard
* refactor(codex): use canonical record guard
* refactor(agents): use canonical record guard
* refactor(sessions): use canonical record guard
* refactor(doctor): use canonical record guard
* refactor(infra): use canonical record guard