Commit Graph

569 Commits

Author SHA1 Message Date
Peter Steinberger 964c8c84c1 refactor: consolidate coercion ownership (#122299)
* refactor: consolidate coercion ownership

Centralize four canonical coercion helpers, migrate exact core and plugin duplicates through narrow Plugin SDK facades, and enforce declaration and plugin-normalization ownership boundaries.

The sweep adds eight focused SDK exports while deleting more production and tooling code than it adds. User-visible behavior is unchanged except for safer equivalent object and UI parsing at existing boundaries.

* fix: guard integer option ownership

Register resolveIntegerOption with the canonical function owner and extend the declaration-guard fixture so future local duplicates fail validation.

* fix: keep integer helpers on numeric facade

Remove the unshipped duplicate string-coerce exports and route every affected plugin consumer through the existing number-runtime contract.

* fix: point numeric coercion to number runtime

Make boundary and declaration diagnostics recommend the canonical numeric facade, with failing-before coverage for both guidance paths.
2026-08-11 17:14:53 -07:00
Peter Steinberger 42a4d0a9b8 fix(openai): surface failed realtime response outcomes (#121177)
* fix(talk): type realtime response outcomes

* fix(ci): align realtime outcome contracts

* test(ui): identify completed realtime responses

* chore(plugin-sdk): refresh realtime voice baselines
2026-08-11 14:14:22 -07:00
Ayaan Zaidi 746a188c90 refactor(media): collapse attachment byte classification into media-core (#122168)
Attachment MIME gating and extraction dispatch used nine separate byte policies across five files; the allowlist could approve one MIME while dispatch observed another after a second sniff. @openclaw/media-core/attachment-classify now owns byte sniffing, text heuristics, extension mapping, and the seven attachment classes; the cache stores one classification that gates and dispatch share. Channel-declared MIME leads with the transport Content-Type as a byte-arbitrated hint, normalizeMimeType folds YAML/XML synonyms so existing configured allowlists keep matching, and input_file API callers keep declared-MIME precedence over cosmetic filenames. Net -135 production LOC.

Proof: 2,153 focused tests across media-core/media-understanding/media plus boundary-contract and deadcode-mirror suites; ClawSweeper local review clean (round 5, zero findings); live Telegram default-turn probe green.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-11 19:10:49 +00:00
Peter Steinberger cad77fb39c refactor: consolidate remaining coercion helpers (#122020) 2026-08-11 10:22:01 -07:00
Peter Steinberger fa03d9b913 refactor: consolidate coercion helpers (#121366)
* refactor: consolidate coercion helpers

* fix: remove duplicate coercion imports

* fix: preserve serialized coercion guard

* chore: ratchet coercion helper carve-outs

* fix(test): keep gauntlet subprocess startup lean

* fix: preserve imported session timestamp semantics

* fix: preserve catalog timestamp string semantics

* chore: align plugin SDK surface ratchet

* fix: preserve trajectory and SDK string contracts

* fix(test): preserve QA record assertion semantics

* fix: complete standalone record guard rename

* refactor(cron): use canonical string coercion

* fix(acpx): preserve Pi timestamp parsing

* test(channels): adapt custody test harnesses

* test(telegram): classify media harness as test support

* test(acpx): split timestamp contract coverage

* test(channels): support generated custody contracts

* chore: ban the full coercion helper name set

Extends the declaration guard to all eleven consolidated helper names and
renames the cron schedule-identity readNumber wrapper to readScheduleInteger
so the banned generic name cannot regrow.

* fix(scripts): repair release-validation guard drift and lint cause

Restores the renamed isJsonRecord guard in assertTrustedWorkflowHarness after
main added isRecord call sites in parallel, and attaches the caught YAML error
as the thrown error cause (preserve-caught-error was red on main).

* fix: preserve Claude timestamp string semantics

* fix: preserve persisted timestamp string semantics

* fix: preserve date-first timestamp contracts

* fix(openai): harden delegation failure formatting

* chore: close coercion helper guard gaps

* test(openai): model non-error delegation rejection

* chore: refresh plugin SDK API contract

* fix(tasks): use canonical string field reader

* fix(ai): use canonical provider error field coercion

* fix(browser): migrate native bootstrap coercion

* docs(plugin-sdk): clarify text record export compatibility

* fix(gateway): normalize approval execution identity

* test(outbound): isolate message action poll harness
2026-08-11 00:02:18 -07:00
Peter Steinberger 18e5b0e3e3 test(web): trim duplicate provider test surfaces (#121727) 2026-08-10 15:44:54 -07:00
Jason (Json) 0b663e7a62 fix(plugins): preserve legacy runtime-doctor imports (#121220)
* fix(plugins): preserve legacy runtime doctor imports

* fix(plugins): map legacy doctor package types
2026-08-09 19:01:49 -06:00
Peter Steinberger bd6c6aaef2 refactor(ws): consolidate raw WebSocket payload decoding (#121268)
* refactor(ws): consolidate raw data conversion

* fix(scripts): keep gateway client source-loadable

* refactor(ws): share plugin frame decoding
2026-08-09 16:32:18 -07:00
Peter Steinberger 081a565cba perf(doctor): restore telegram doctor repairs dropped on source-run hosts (#120954)
* perf(doctor): keep telegram doctor enumeration off the runtime graph

Telegram's built doctor artifact reached execa through dist chunking, so a
source-run host (pnpm dev, tsx CLI, vitest) could not require it and silently
dropped all 9 telegram legacy config rules plus its state migration. The
artifact also pulled telegram's runtime stores, making it a 674-chunk outlier
that dominated doctor enumeration.

Root cause: `src/token.ts` took the broad `plugin-sdk/provider-auth` barrel for
`resolveDefaultSecretProviderAlias`, dragging the auth-profile store, provider
runtime, and plugin install graph (execa, kysely, commander) into the closure.
The alias now has a narrow `plugin-sdk/secret-provider-alias` leaf, and
provider-auth re-exports it so its runtime surface is unchanged.

Thread-binding, sent-message, and sticker-cache row shapes, keys, and legacy
sidecar readers move to `*.legacy-state.ts` leaves. The doctor closure keeps
the rows and drops the ACP, session-binding, send, logger, and plugin-runtime
graphs the stores also load.

The postbuild control-plane verifier only required each artifact in a plain
Node child, the one host where these graphs resolve fine, so it proved nothing
about the invariant that broke. It now also walks each built doctor artifact's
static import closure and fails when it reaches the process-spawn graph, which
is the dist-level analogue of the source closure guard.

Guard rules added for provider-auth, acp-runtime, and conversation-runtime; the
telegram boundary test became a real closure assertion instead of a string grep.

* fix(doctor): drop dead export surface from the telegram legacy-state split

Knip and oxlint caught leftovers from the split: the leaves exported helpers
only they use, the store modules re-exported constants nobody imports from them
anymore, and thread-bindings kept a `testing` barrel whose last production
caller was the migration path that now reads the leaf directly. Tests import the
constants from the leaf that owns them, and the reset helper directly.

The closure gate's failure message still interpolated a `host` field left over
from a probe-host approach that was reverted before commit; the existing verifier
test caught it. The gate now has its own coverage: a transitive chunk edge to a
forbidden dependency is reported, while dynamic imports and non-doctor contract
surfaces are not.

* fix(doctor): adopt the upstream telegram thread-binding store split

`main` landed an equivalent thread-binding leaf as `thread-bindings-store.ts`
while this branch was open, so the branch-local `thread-bindings.legacy-state.ts`
is dropped rather than kept as a second path for the same rows.

`state-migrations.ts` now reaches token.js through the lazy import `main` added,
so `token.ts` is no longer in the doctor closure at all. The narrow
`secret-provider-alias` leaf still matters: telegram's contract-api closure
reaches `provider-auth` through `token.ts` on current `main`, which is the same
execa/kysely/commander graph, so the barrel is repaired at its source instead of
being deferred a second time.

* fix(scripts): type the built doctor closure gate for the TypeScript migration

The gate was authored against the `.mjs` script and landed in the `.mts` file
`main` migrated to, so its parameters were implicitly `any` and `check:test-types`
failed. Adds the explicit signatures plus the violation type.

Regenerates the plugin-sdk API baseline: `provider-auth` re-exports the default
secret-provider alias from the new leaf, so its module hash moves while its
runtime export surface stays identical.
2026-08-09 08:50:03 -07:00
Peter Steinberger 8616c0c374 refactor: finish shared test helper migrations (#120996)
* test: finish shared helper migrations

* test: fix helper migration CI

* style: fix test import ordering

* test(acpx): restore deferred void types

* test: fix helper migrations after rebase
2026-08-09 06:00:06 -07:00
Peter Steinberger 8b0735e89f refactor(memory)!: remove the QMD backend; builtin is the only memory engine (#120936)
* refactor(memory): remove qmd backend

Make builtin the sole memory-core engine, rename the retained session helper barrel, retire QMD config with doctor migrations, and remove QMD runtime/UI/policy surfaces.

* docs(memory): remove qmd backend guidance

Delete the QMD concept page, rewrite memory documentation for builtin retrieval, and remove QMD from navigation and taxonomy source.

* refactor(memory): remove qmd-only leftovers

* refactor(memory): finish qmd integration cleanup

* build(deps): align root string-width types

* build(deps): model root string-width tooling

* refactor(memory): align qmd removal ui and docs

* fix(memory): preserve qmd external paths in doctor

* test(memory): remove obsolete backend probe case

* test(plugin-sdk): refresh private type baseline
2026-08-09 03:05:47 -07:00
Peter Steinberger 6192673da4 perf(doctor): delete the heavy doctor barrel and finish slimming enumeration (#120882)
* refactor(plugin-sdk): delete the heavy runtime-doctor barrel

Nothing may pull the state-db/kysely graph through a doctor barrel anymore.
The barrel's remaining heavy exports move to two narrow private-local
subpaths, each with a single purpose:

- doctor-repair-runtime: install-path diagnosis, plugin config removal, and
  state-database schema detect/repair (matrix doctor, voice-call lazy import)
- plugin-state-store-runtime: the sync keyed-store factory. It stays out of
  plugin-state-runtime because hot channel entrypoints import that at module
  load and opening a store pulls the state-database graph.

Doctor closures also stop pulling ssrf-runtime (fetch-guard + gateway net)
for two legacy private-network helpers that live in the lighter ssrf-policy
subpath: mattermost, nextcloud-talk, tlon, matrix.

The closure guard now forbids the two new heavy subpaths instead of the
deleted barrel, so the invariant keeps being enforced where it still applies.

* perf(doctor): keep heavy graphs out of every doctor closure

Doctor enumeration cold-loads each declaring plugin's contract closure, so
one heavy import in a closure is paid by the whole sweep. Four barrels were
still dragging unrelated graphs in for trivial helpers; each is repaired at
the leaf rather than by caching downstream:

- Legacy private-network config migration moves to a config leaf. It only
  reshapes records, but lived beside the SSRF runtime (DNS, proxy, logging),
  costing mattermost ~2.7s. ssrf-policy re-exports it, surface unchanged.
- Streaming config readers move to a leaf. They read two config keys, but
  streaming.ts also formats tool aggregates, pulling tool-display/logging/
  acp-core; that cost slack ~2.3s.
- signal took the channel-secret barrel for isRecord; the canonical plugin
  record guard is string-coerce-runtime (root AGENTS.md).
- llm-task took the provider-model barrel for parseModelRef, now a narrow
  model-ref-parse subpath.

Full doctor enumeration of all 42 declaring plugins, built mode:
legacy config rules 6668ms -> 1265ms, state migrations 184ms -> 127ms.
No plugin remains an outlier; the slowest is now ~380ms against a ~200ms floor.

Public export surfaces of every touched SDK subpath are byte-identical
(verified by diffing built module exports before/after); the API baseline
hashes move only because re-exported declarations emit differently.

The closure guard gains rules for each repaired barrel so the invariant
holds for future closures.

* fix(release): exclude new private-local declarations from the published package

Same pack-path rule as c41da3759f: private-local subpaths ship without d.ts.

* fix(doctor): repair the closure guard violations that break main

The landed guard fails on main: three closures import heavy barrels for one
symbol each. Two more surfaced once the guard learned about the provider-model
barrel. Each gets a narrow subpath at the leaf:

- telegram sent-message-cache + state-migrations took the session-store barrel
  (session accessor + state-db) for resolveStorePath -> session-store-paths
- discord thread-bindings.state took the channel-outbound barrel (reply
  pipeline + channel registry) for one identity write -> outbound-echo-runtime
- discord model-picker took the provider-model barrel for normalizeProviderId,
  which model-ref-parse now exposes beside parseModelRef

The guard also stops walking artifacts of plugins whose manifest declares no
doctor surface. Such a declaration gates the artifact off every enumeration
path exactly as resolvePluginDoctorContracts does, so its closure cost is never
paid; anthropic ("doctorContract": {}) was being held to a cost it cannot
incur. Absent declarations still load eagerly and stay enforced.

Side effect worth naming: discord's built doctor contract now loads again.
On main both discord and telegram fail to require in packaged builds (an
ESM-only transitive dep) and silently lose their repairs; this restores
discord and takes enumerated legacy config rules from 87 to 99. Telegram's
built artifact still pulls execa through dist chunking - a build-level defect
with a different owner, filed as follow-up.
2026-08-08 22:01:44 -07:00
Peter Steinberger c2e7c819f5 perf(doctor): slim remaining heavy doctor contract closures (#120811)
* perf(doctor): slim remaining heavy doctor contract closures

Follow-up to #120698: several doctor closures still cold-loaded multi-second
kysely-bearing graphs through other broad barrels (session-store-runtime,
realtime-voice, channel-outbound, logging-core, memory-host-core/-events,
sqlite-runtime, persistent-dedupe, and plugin-local barrels).

- lazy-import heavy helpers inside async migration bodies (codex, msteams,
  zalouser, workboard, matrix inbound-dedupe, memory-core migrations)
- bypass plugin-local barrels to defining modules (reef protocol,
  memory-core short-term-promotion)
- move to lighter existing subpaths (slack -> channel-streaming, matrix
  logger -> security-runtime, memory-wiki -> agent-scope-runtime, which now
  also exports resolveSessionAgentId)
- add narrow openclaw/plugin-sdk/realtime-voice-activation for discord's
  sync wake-name doctor rules
- split src/infra/kysely-sync-cache-state.ts so sqlite-transaction clears
  Kysely caches without value-loading kysely; split the memory-host-sdk
  kysely bridge off the schema/transaction bridge
- guard: forbid the heavy barrels in doctor closures with per-kind scoping

Cold enumeration per plugin: discord 52.6s->0.3s, msteams 30.9s->0.5s,
codex 29.6s->2.6s, zalouser 28.8s->2.3s, matrix 27.2s->3.2s,
slack 17.5s->1.5s, reef 9.9s->0.7s, memory-core 6.4s->3.6s,
workboard 3.4s->0.25s; all kysely-free except llm-task (named follow-up).

* fix(plugins): route slack streaming compat through a focused streaming-config subpath

The channel-streaming compat barrel is deprecated for extension production
code (deprecated-api-usage guard + SDK package contract). Add the narrow
non-deprecated openclaw/plugin-sdk/channel-streaming-config subpath for the
pure streaming config readers, and drop the now consumer-less
short-term-promotion barrel re-exports knip flagged.

* test(plugins): register memory-host-sdk kysely bridge in package boundary inventory

* fix(plugins): classify realtime-voice-activation as private-local

ClawSweeper P2: the subpath exports only a default target, which is the
private-local shape; register it in plugin-sdk-private-local-only-subpaths,
the package-boundary d.ts alias maps, and correct the public surface budgets
(realtime-voice-activation no longer counts as public).

* fix(release): exclude realtime-voice-activation declarations from the published package

Private-local subpaths ship without d.ts; register the files negation the
release pack-path check requires.
2026-08-08 20:28:58 -07:00
Peter Steinberger 75dbe52e3e refactor: one code path behind doctor legacy-state migrations (#120716)
* refactor(doctor): prefer manifest route-state owners

* refactor(doctor): unify config repair declarations

* refactor(doctor): unify legacy state migrations

* fix(doctor): satisfy migration pipeline guards

* fix(plugin-sdk): keep doctor adapter inside boundary
2026-08-08 18:23:15 -07:00
Peter Steinberger 8cb53c7b55 perf(doctor): keep bundled doctor contract closures dependency-light (#120698)
* perf(doctor): keep bundled doctor contract closures dependency-light

Doctor contract enumeration cold-loads each plugin's doctor-contract-api
closure via jiti, so a static value import of openclaw/plugin-sdk/runtime-doctor
pulled the state-db/kysely graph (~4.3s per closure) into
listPluginDoctorLegacyConfigRules / listPluginDoctorStateMigrationEntries.

- migrate all light doctor-contract closures (66 files) to the
  dependency-light openclaw/plugin-sdk/runtime-doctor-migrations subpath
- voice-call: load detect/repairOpenClawStateDatabaseSchema* lazily inside
  the migration bodies; keep only a type-only static runtime-doctor import
- matrix: split pure credential record shapes/normalizers into
  credentials-state.ts so the doctor closure no longer imports the sync
  plugin-state store through credentials-read
- guard: doctor-contract-closure-guard.test.ts now forbids static value
  imports of runtime-doctor in closures alongside agent-runtime

* fix(matrix): keep credential revocation record type module-local

Knip production scan flags the export as consumer-less; the type is only
referenced by the exported union and revocation guard signature.
2026-08-08 17:51:31 -07:00
Peter Steinberger bb019e4a53 fix(providers): preserve realtime tool-result delivery ownership (#120776)
Co-authored-by: Wan Yong <wan.yong@xydigit.com>
2026-08-08 17:03:43 -07:00
Peter Steinberger e6353d85ef fix(daemon): drop stale service version metadata (#120702) 2026-08-08 14:14:22 -07:00
Simone f7d31bdc56 fix(xai): keep voice turns working after malformed terminal output (#120002)
* fix(xai): tolerate malformed terminal output

* test(xai): prove malformed terminal recovery over websocket

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-08 13:52:29 -07:00
Peter Steinberger da4a656cdb improve: doctor migration checks no longer load every bundled plugin runtime (#120678)
* perf(plugins): declare doctor contract surfaces

* perf(doctor): slim migration import closures

* perf(plugins): narrow doctor declaration record surface and wire owner-test lane

Registry records carry only the doctorContract declaration instead of the whole
parsed manifest, and check:changed now selects the src/plugins-owned declaration
honesty and closure-guard tests for extension module/manifest changes so
cross-lane drift cannot pass PR classification.

* fix(doctor): keep control-plane dist imports require-safe

Keep doctor and channel control-plane chunks off exec-class dependencies, and enforce native require(esm) loading during postbuild.

* chore(plugin-sdk): regenerate API baseline

* chore(plugin-sdk): sync export ordering

* fix(plugins): satisfy doctor contract CI boundaries

* perf(doctor): make qqbot doctor closure dependency-light

qqbot was the last plugin above 5s in doctor state-migration enumeration
(~8s under tsx/jiti). The cost was not the state-key builder (already a
leaf): its doctor closure value-imported the runtime-doctor SDK barrel,
whose plugin-state-store/state-db re-exports pull kysely (~330 modules),
plus security-runtime for one fileExists (~200 modules), all resolved
per-module by jiti during enumeration.

Split the migration-define helpers and light re-exports into a new
private-local plugin-sdk/runtime-doctor-migrations subpath; runtime-doctor
re-exports it so its public surface is byte-identical (API baseline hash
unchanged). qqbot's doctor-contract and state-migrations now import only
the light subpath, swapping fileExists for the equivalent async
legacyStateFileExists already in the closure.

qqbot enumeration: ~8.0s/531 modules -> ~0.25s/18 modules.

* chore(plugin-sdk): drop private-local subpath from API baseline

runtime-doctor-migrations is private-local-only; the baseline tracks public
modules, and the earlier line was generated before the classification.

* fix(plugins): register runtime-doctor-migrations boundary paths

The private-local subpath list feeds the extension package boundary map;
the shared paths config and xai's derived overrides must carry the same
entry or the boundary contract test fails.
2026-08-08 13:29:18 -07:00
Peter Steinberger 48639663b0 chore(release): prepare 2026.8.1 (#120375) 2026-08-07 18:44:12 -07:00
joshavant 2751bc8991 Revert "fix(agents): restrict harness tool authority"
This reverts commit aacbcaacc8.
2026-08-07 18:40:17 -05:00
Peter Steinberger bc0c3fcd63 refactor(providers): shared generated-media download guard and binary-response adoption (#120351)
* fix(agents): cancel rejected provider binary bodies

* refactor(providers): share generated media downloads

* refactor(providers): adopt payload patch stream wrapper

* fix(providers): preserve generated media helper contracts
2026-08-07 15:45:53 -07:00
Vincent Koc aacbcaacc8 fix(agents): restrict harness tool authority 2026-08-07 15:07:12 +08:00
Peter Steinberger b4a26783f7 refactor(test): consolidate duplicated requireRecord and provider HTTP mock helpers (#119982)
* refactor(test): consolidate duplicated test helpers

* test: remove stale record guard import

* fix(test): remove orphaned record guards

* refactor(test): keep record requirement messages exhaustively typed

* fix(test): keep packages/ai record guard package-local
2026-08-06 14:48:01 -07:00
Peter Steinberger 8db2cb7a94 refactor(providers): deduplicate bundled model catalogs (#119627) 2026-08-05 07:40:07 -07:00
Vincent Koc e35d22807e perf(xai): lazy-load optional capability runtimes (#119374)
Punchcard-Session: coral-workshop-workshop-3f
2026-08-05 11:26:40 +08:00
Vincent Koc d21c6be181 test(xai): cover delayed OAuth connection setup 2026-08-04 14:17:19 +08:00
Vincent Koc ebf335f1a6 fix(voice): start socket timeout after auth preparation 2026-08-04 14:17:19 +08:00
Vincent Koc d5d4a63733 test(xai): cover playback mark overflow 2026-08-04 09:07:06 +08:00
Vincent Koc 73544a73af fix(xai): bound realtime playback marks 2026-08-04 09:07:06 +08:00
Peter Steinberger c83dcc2bc0 fix(security): harden network tool output at canonical owner boundaries (#118984)
* fix(security): bound external tool content at its canonical owner boundary

* fix(plugin-sdk): document supported security boundary and restore facade parity
2026-08-03 15:34:33 -07:00
Peter Steinberger 990fdcf6fd fix(providers): recognize configured media credentials across provider boundaries (#118761)
* fix(providers): honor configured media credentials across owner boundaries

* test(providers): align configured auth fixtures and derived SDK surfaces

* fix(providers): enforce shared DashScope media credential policy
2026-08-03 13:26:31 -07:00
Goutam Adwant a5160c1294 fix(xai): classify exhausted credits as billing (#118615) 2026-08-03 04:57:28 -07:00
Peter Steinberger ecc49b5a87 refactor(tts): absorb speech core package (#118513)
* refactor(tts): absorb speech core package

* fix(tts): preserve runtime SDK exports

* refactor(tts): remove private package exports

* test(tts): align canonical runtime mocks

* test(tts): complete canonical settings mocks

* chore(plugin-sdk): regenerate API baseline for #118513
2026-08-03 02:25:48 -07:00
Peter Steinberger 8a5cfa4cac fix(xai): preserve all response text blocks and citations (#118477) 2026-08-02 22:34:34 -07:00
Peter Steinberger da7b1a565d test(xai): consolidate catalog discovery setup (#118454) 2026-08-02 21:31:29 -07:00
Peter Steinberger ffe2268eec test(xai): consolidate realtime voice fixtures (#118318) 2026-08-02 19:04:38 -07:00
Peter Steinberger cbd4b8dec8 fix(memory): keep wiki search inside protected recall visibility (#118265)
* fix(memory): unify wiki session authorization

* build: expose memory core boundary declarations
2026-08-02 18:22:17 -07:00
Peter Steinberger cac802f474 test(xai): deduplicate search fixtures (#118263) 2026-08-02 17:13:02 -07:00
Peter Steinberger 131b9e3904 refactor(voice): share realtime session lifecycle (#117865)
* refactor(voice): share realtime session lifecycle

* test(voice): normalize abort rejection
2026-08-01 23:35:57 -07:00
Peter Steinberger 996b1e1e86 refactor(plugins): remove oauth pass-through wrappers (#117867) 2026-08-01 23:28:59 -07:00
Peter Steinberger 35a1777f68 refactor(providers): consolidate catalog setup helpers (#117824)
* refactor(providers): consolidate catalog setup helpers

* fix(huggingface): narrow discovered model entries
2026-08-01 22:17:52 -07:00
Yiğit ERDOĞAN 65d64818bb fix(tts): reject non-audio speech synthesis responses (#117345)
* fix(tts): reject non-audio speech synthesis responses

A successful HTTP 200 carrying a JSON error, an HTML sign-in page, or a
zero-byte body was returned as an audio buffer and written out as a playable
attachment. Route the xAI, Gradium, and Azure Speech synthesizers through the
shared binary-response contract that the OpenAI-compatible speech provider
already uses, so the media kind is validated before the body is read and the
unread body is canceled when it is rejected.

Each provider keeps its own overflow wording, so operator-facing messages are
unchanged apart from the new malformed-response rejection.

* test(azure-speech): prove the audio guard closes a real upstream socket

Adds a loopback node:http server that answers 200 with a JSON content type
and then never ends the body, driven through the real azureSpeechTTS path
with the real global fetch and no stubbed transport. Without the guard the
synthesis hangs to its timeout with the socket still open; with it the
response is rejected immediately and the server observes the close.

* fix(openai): reject malformed binary speech responses

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-01 16:13:15 -07:00
Vincent Koc 80c187b866 fix(xai): preserve terminal realtime voice outcomes (#117600)
* fix(xai): preserve canonical realtime terminal outcomes

* fix(xai): bound terminal tool outcome state

* test(xai): bound realtime outcome harness payloads

---------

Co-authored-by: Peter Steinberger <steipete@macos.shared>
2026-08-02 04:13:58 +08:00
Peter Steinberger 568ea29d2e refactor(plugins): inherit shared package boundary settings (#117474) 2026-08-01 09:11:09 -07:00
Vincent Koc eb45b87699 test(xai): type realtime sent item payloads 2026-08-01 21:39:06 +08:00
Vincent Koc 45c1c58cf2 test(xai): prove rejected tool call continuation 2026-08-01 21:18:38 +08:00
Vincent Koc fd16d9ad8e fix(xai): complete rejected realtime tool calls 2026-08-01 21:17:47 +08:00
Vincent Koc ff19ac31d1 test(xai): add live tool argument probe 2026-08-01 21:07:01 +08:00
Vincent Koc cc09e265bc test(xai): cover rejected realtime tool arguments 2026-08-01 21:07:01 +08:00