arcane-os 0.76.0 → 0.78.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.78.0
4
+
5
+ - Add `AI.readPreparedTTS()` and shared `readPreparedSpeech()` for complete
6
+ cache-only narration lookup. Return a ready playback handle or `null` without
7
+ loading a model, unmuting, synthesizing, writing storage or repairing partial
8
+ audio. Preserve complete semantic matching and genuine storage errors.
9
+ - Keep lookup cancellation independent from a successfully returned handle.
10
+ Replay uses the existing prepared-playback owner and can borrow the caller's
11
+ silent reusable audio output; no autoplay permission is implied.
12
+ - Add optional existing-only DBOPFS table/file reads while preserving ordinary
13
+ lazy-creation defaults. Cache-only lookup and later audio reads leave missing
14
+ tables absent.
15
+ - Give Kokoro voice caching a real Worker-lifetime response cache after runtime
16
+ import, while DBOPFS remains the durable model/resource owner. Preserve saved
17
+ native caches and clear transient responses on unload.
18
+ - Report a complete stored model resource as cached loading rather than a new
19
+ network download. This release changes no model selection, native engine or
20
+ physical-device routing and makes no live-model execution claim.
21
+
22
+ ## 0.77.0
23
+
24
+ - Add `AI.prepareAudioOutput()` for synchronous, silent preparation of a reusable
25
+ WebAudio output. Observe its actual context state through immediate subscription
26
+ replay and `AI_AUDIO_OUTPUT_STATE_EVENT`; resume and dispose explicitly.
27
+ - Add optional `audioOutput` to `playPreparedTTS()` so prepared narration borrows
28
+ that same output across plays. Completion and stop leave the borrowed context
29
+ open; omission preserves the existing per-playback context lifecycle.
30
+ - Keep replacement, pending suspension, decoding, cancellation and disposal tied
31
+ to their actual output and playback owners. Cleanup failures remain observable
32
+ and failed context closure can be retried. Independent native speech, saved
33
+ audio and detached preparation retain their existing ownership.
34
+ - Preparation plays no sound, creates no audio source, resumes no context,
35
+ changes no mute setting and loads no model. A running WebAudio context is not
36
+ a guarantee of future autoplay, native speech or HTML audio readiness. No
37
+ physical-GPU placement or native-engine change is included.
38
+
3
39
  ## 0.76.0
4
40
 
5
41
  - Add public `textFormat:'plain'` to the `SpeechPlayback` constructor and
package/README.md CHANGED
@@ -20,7 +20,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
20
20
  share the theme, packaging, event, cancellation,
21
21
  and browser run contracts while retaining their selected application layout.
22
22
 
23
- This checkout defines the `0.76.0` SDK contract. Development applications declare
23
+ This checkout defines the `0.78.0` SDK contract. Development applications declare
24
24
  `arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
25
25
  designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
26
26
  to refresh and compare the installed SDK and lockfile with the published channel;
@@ -96,7 +96,7 @@ export function createDbopfsResourceStore({dbopfs, tableName, fetchImpl = global
96
96
  const file = await parts.read(entry.name, {signal});
97
97
  if (file) {
98
98
  const state = await parts.state(entry.name, {signal});
99
- onProgress?.({phase: 'download', completed: state.parts.length,
99
+ onProgress?.({phase: 'load', message: 'Reading stored model resource', completed: state.parts.length,
100
100
  total: state.parts.length, unit: 'shards', cached: true, url: request.url});
101
101
  return {...entry.metadata, file};
102
102
  }
@@ -997,6 +997,7 @@ function createOrdinaryArtifactModuleRouter(
997
997
  role,
998
998
  reader,
999
999
  originalWorker,
1000
+ responseCaches,
1000
1001
  ) {
1001
1002
  const nestedWorkers = new Set();
1002
1003
  const router = completeValue({
@@ -1015,8 +1016,8 @@ function createOrdinaryArtifactModuleRouter(
1015
1016
  return reader.fetchResource(input, init, modulePath);
1016
1017
  },
1017
1018
 
1018
- async openCache() {
1019
- throw new Error('Browser CacheStorage is unavailable in this speech Worker; resources use DBOPFS through fetch.');
1019
+ openCache(modulePath, name) {
1020
+ return responseCaches.open(name);
1020
1021
  },
1021
1022
 
1022
1023
  createWorker(modulePath, specifier, options = {}) {
@@ -1143,6 +1144,63 @@ function installOrdinaryArtifactModuleRouter(scope, configuration, role, send, s
1143
1144
  const resourceClient = createModelResourceClient({ send });
1144
1145
  let requestSignal = null;
1145
1146
  let reportProgress = null;
1147
+ let cacheClosed = false;
1148
+ const responseCacheEntries = new Map();
1149
+ const responseCaches = {
1150
+ async open(name) {
1151
+ assertCacheActive(requestSignal);
1152
+ const cacheName = String(name);
1153
+ let cache = responseCacheEntries.get(cacheName);
1154
+ if (!cache) {
1155
+ const responses = new Map();
1156
+ cache = {
1157
+ responses,
1158
+ api: {
1159
+ async match(input) {
1160
+ assertCacheActive(requestSignal);
1161
+ const stored = responses.get(cacheRequestKey(input));
1162
+ if (!stored) return undefined;
1163
+ const response = new Response(stored.body, stored);
1164
+ Object.defineProperties(response, {
1165
+ url: { value: stored.url },
1166
+ redirected: { value: stored.redirected },
1167
+ type: { value: stored.type },
1168
+ });
1169
+ return response;
1170
+ },
1171
+ async put(input, response) {
1172
+ const signal = requestSignal;
1173
+ assertCacheActive(signal);
1174
+ const key = cacheRequestKey(input);
1175
+ const copy = response.clone();
1176
+ const body = await copy.blob();
1177
+ assertCacheActive(signal);
1178
+ responses.set(key, {
1179
+ body,
1180
+ status: response.status,
1181
+ statusText: response.statusText,
1182
+ headers: [...response.headers],
1183
+ url: response.url,
1184
+ redirected: response.redirected,
1185
+ type: response.type,
1186
+ });
1187
+ },
1188
+ },
1189
+ };
1190
+ responseCacheEntries.set(cacheName, cache);
1191
+ }
1192
+ return cache.api;
1193
+ },
1194
+ };
1195
+ function assertCacheActive(signal) {
1196
+ throwIfAborted(signal, 'speech-resource-cache-cancelled');
1197
+ if (cacheClosed) throw new Error('The speech Worker response cache was unloaded.');
1198
+ }
1199
+ function cacheRequestKey(input) {
1200
+ const RequestConstructor = scope.Request ?? Request;
1201
+ const request = new RequestConstructor(input);
1202
+ return JSON.stringify([request.method, request.url]);
1203
+ }
1146
1204
  if (!is.function(originalFetch)) {
1147
1205
  throw workerError(
1148
1206
  "ARCANE_AI_PROVIDER_UNAVAILABLE",
@@ -1166,8 +1224,9 @@ function installOrdinaryArtifactModuleRouter(scope, configuration, role, send, s
1166
1224
  role,
1167
1225
  reader,
1168
1226
  originalWorker,
1227
+ responseCaches,
1169
1228
  );
1170
- const originalProperties = ['fetch', 'Worker', MODULE_ROUTER_NAME].map(
1229
+ const originalProperties = ['fetch', 'Worker', 'caches', MODULE_ROUTER_NAME].map(
1171
1230
  function originalWorkerProperty(name) {
1172
1231
  return { name, descriptor: Object.getOwnPropertyDescriptor(scope, name) };
1173
1232
  },
@@ -1240,7 +1299,22 @@ function installOrdinaryArtifactModuleRouter(scope, configuration, role, send, s
1240
1299
  receive: resourceClient.receive,
1241
1300
  setSignal(signal) { requestSignal = signal; },
1242
1301
  setProgress(report) { reportProgress = report; },
1302
+ enableResponseCache() {
1303
+ // Transformers captures cache capability during import. Kokoro then uses
1304
+ // this real, Worker-lifetime match/put cache for voices; fetch remains the
1305
+ // sole route to durable DBOPFS resources. No native saved cache is touched.
1306
+ assertCacheActive(requestSignal);
1307
+ Object.defineProperty(scope, 'caches', {
1308
+ configurable: true,
1309
+ enumerable: true,
1310
+ writable: true,
1311
+ value: responseCaches,
1312
+ });
1313
+ },
1243
1314
  async cleanup() {
1315
+ cacheClosed = true;
1316
+ for (const cache of responseCacheEntries.values()) cache.responses.clear();
1317
+ responseCacheEntries.clear();
1244
1318
  resourceClient.close(new Error('The speech Worker resource owner was unloaded.'));
1245
1319
  let cleanupFailure = null;
1246
1320
  try {
@@ -1773,6 +1847,7 @@ export function createSpeechWorkerRuntime({ role, scope = globalThis, send } = {
1773
1847
  );
1774
1848
  const namespace = await import(entry.moduleUrl);
1775
1849
  throwIfAborted(signal, `${role}-load-cancelled`);
1850
+ environment.enableResponseCache();
1776
1851
  restoreNamespace = configureRuntimeNamespace(
1777
1852
  namespace,
1778
1853
  configuration,
@@ -2071,6 +2146,7 @@ export function installBrowserSpeechArtifactModuleWorker(role, scope = globalThi
2071
2146
  target?.sourceUrl ?? request.targetUrl,
2072
2147
  );
2073
2148
  await import(target?.moduleUrl ?? request.targetUrl);
2149
+ environment.enableResponseCache();
2074
2150
  scope.removeEventListener("message", bootstrap);
2075
2151
  await new Promise((resolve) => queueMicrotask(resolve));
2076
2152
  for (const queuedEvent of queued.splice(0)) replayWorkerMessage(scope, queuedEvent);
@@ -727,6 +727,54 @@ pause delays the next queued audio on the existing `AudioContext` clock; it
727
727
  does not delay the preceding promise after that passage's last buffer ends.
728
728
  The promise is a playback result, not a listener acknowledgement.
729
729
 
730
+ ## Read stored narration without starting generation
731
+
732
+ `await ai.readPreparedTTS({parts,textFormat,storage,identity,signal,onState})`
733
+ returns a complete prepared handle or `null`. It uses the same complete inputs,
734
+ selected speech configuration, formatting treatment, segmentation, storage key,
735
+ and semantic `identity` as `prepareTTS()`. Supply the same options used to
736
+ prepare the audio. A changed selection or input is a cache miss, not permission
737
+ to generate replacement audio.
738
+
739
+ This operation never activates or unmutes a provider, synthesizes speech,
740
+ creates stored records, repairs partial audio, or joins pending generation.
741
+ With `storage`, it reads the existing manifest and every ordered audio file.
742
+ Missing or incomplete audio returns `null`; unreadable metadata and other
743
+ storage errors reject. Without storage, it finds only already-complete audio
744
+ retained by this same AI instance. A miss leaves all existing data unchanged.
745
+
746
+ ```javascript
747
+ async function readNarration(text, key, signal) {
748
+ return ai.readPreparedTTS({
749
+ parts: [{
750
+ input: text,
751
+ voice: speechSelection.model.defaultVoice,
752
+ speed: 1,
753
+ pauseAfterMs: 0
754
+ }],
755
+ storage: {db: dbopfs, table: 'saved_narration', key},
756
+ identity: {
757
+ model: speechSelection.model,
758
+ runtime: speechSelection.runtime
759
+ },
760
+ signal
761
+ });
762
+ }
763
+ ```
764
+
765
+ A returned handle has the ordinary `segments`, `state`, `ready`, `getAudio(index)`,
766
+ and `cancel()` contract and can be passed to `ai.playPreparedTTS()` from the
767
+ application's playback action, including with its existing `audioOutput`.
768
+ The lookup signal governs only the read and rejects cancellation as
769
+ `AbortError`. After a successful return, aborting that signal does not invalidate
770
+ the retained handle; explicit handle cancellation and playback cancellation
771
+ remain separate. A successful read does not promise future autoplay permission
772
+ or prevent a later application-owned removal of stored files.
773
+
774
+ Applications decide which saved narration candidates to inspect. For example,
775
+ newest-first lookup can skip a newer partial preparation and then inspect an
776
+ older complete candidate without activating any model.
777
+
730
778
  ## Prepare narration once and replay stored audio
731
779
 
732
780
  Use `ai.prepareTTS()` when preparation must continue independently of playback,
@@ -889,7 +937,7 @@ provider cleanup remains observable through that operation's settlement.
889
937
 
890
938
  ### Playback controls and independent lifetimes
891
939
 
892
- `ai.playPreparedTTS(prepared,{signal,onState})` returns
940
+ `ai.playPreparedTTS(prepared,{signal,onState,audioOutput})` returns
893
941
  `{state,error,finished,pause(),resume(),stop()}` immediately. It uses the
894
942
  existing AI audio-clock scheduler and waits for each earlier segment before scheduling
895
943
  later audio. Ready adjacent buffers retain contiguous scheduling. Requested
@@ -902,8 +950,9 @@ states are `waiting`, `waiting-for-gesture`, `scheduled`, `paused`, `complete`,
902
950
  `stopped`, and `error`. Use `waiting-for-gesture` to present an audio-unlock
903
951
  control. A `false` result from `resume()` is not a first-segment-ready signal;
904
952
  inspect `state` and `error` to distinguish a stopped or unavailable context
905
- from browser gesture waiting. The ordinary playback path creates its audio
906
- context immediately, before the first audio segment is ready.
953
+ from browser gesture waiting. With `audioOutput` omitted or `null`, playback
954
+ creates its own audio context immediately, before the first segment is ready,
955
+ and closes that context after completion, stop, cancellation, or failure.
907
956
 
908
957
  `finished` resolves `true` after natural playback completion and `false` after
909
958
  stop, playback cancellation, or failure. Real failures also reach the existing
@@ -931,6 +980,87 @@ preparations. A page that starts several preparations owns and observes every
931
980
  `ready` promise, even when it plays only one handle. Browser autoplay permission
932
981
  still applies; neither promise proves that a person heard the audio.
933
982
 
983
+ ### Prepare a silent reusable WebAudio output
984
+
985
+ `ai.prepareAudioOutput()` synchronously returns an AI-owned output handle.
986
+ It creates or reuses that AI's persistent WebAudio context and observes its
987
+ actual state. Preparation stays silent: it does not resume the context, unmute
988
+ speech, load a model, create an audio source, play a test sound, or install
989
+ gesture listeners. It does not wait for speech preparation or provider readiness.
990
+
991
+ The handle exposes `kind:'web-audio'`, live `state`, `contextState`, and `error`
992
+ getters, `subscribe(listener)`, `resume()`, and `dispose()`. Repeated preparation
993
+ returns the same handle until its output is disposed or its context closes.
994
+ `contextState` is the platform context's current state, or `null` when unavailable;
995
+ `error` retains the complete actual error, or `null`.
996
+
997
+ | Output state | Meaning |
998
+ | --- | --- |
999
+ | `ready` | This WebAudio context is currently `running`. |
1000
+ | `suspended`, `interrupted`, or another platform state | The context reports that state; suspension alone does not establish a gesture requirement. |
1001
+ | `gesture-required` | A resume operation actually reported `NotAllowedError` while the context was not running. |
1002
+ | `unavailable` | Context creation failed; `error` contains the actual failure. |
1003
+ | `error` | An output operation failed; inspect the complete `error`. |
1004
+ | `disposing`, `disposed` | Owner disposal is in progress or has completed. |
1005
+ | `closed` | The underlying context has closed outside completed owner disposal. |
1006
+
1007
+ `subscribe(listener)` immediately replays `{kind,state,contextState,error}` and
1008
+ then delivers state changes through the canonical AI event owner. It returns
1009
+ an idempotent unsubscribe function. The named export
1010
+ `AI_AUDIO_OUTPUT_STATE_EVENT` from `AI.js` is `'ai-audio-output-state'`; its
1011
+ canonical occurrences carry the same snapshot in `detail` and an output-specific
1012
+ `operationId`. Handle subscriptions select only their own output. Disposal or
1013
+ context closure releases those subscriptions after the terminal notification;
1014
+ a later handle subscription still receives the current snapshot immediately.
1015
+
1016
+ `resume()` is an explicit asynchronous action for the application's user-gesture
1017
+ flow. It resolves `true` only when the context is actually running, and `false`
1018
+ when unavailable, disposed, superseded, or held by a paused prepared playback.
1019
+ A platform rejection rejects with its actual error and retains that failure in
1020
+ the output state. It does not unmute speech or load a model. Output readiness
1021
+ does not establish HTML audio readiness, native-speech readiness, future autoplay
1022
+ permission, available speech audio, or audible delivery.
1023
+
1024
+ Pass the handle explicitly to borrow that exact output for prepared playback:
1025
+
1026
+ ```javascript
1027
+ // ai and prepared are the same AI owner and preparation used above.
1028
+ const audioOutput = ai.prepareAudioOutput();
1029
+
1030
+ function observeNarrationOutput(listener) {
1031
+ return audioOutput.subscribe(listener);
1032
+ }
1033
+
1034
+ function resumeNarrationOutput() {
1035
+ // Call from the application's user action and observe the returned promise.
1036
+ return audioOutput.resume();
1037
+ }
1038
+
1039
+ function playNarration() {
1040
+ return ai.playPreparedTTS(prepared, {audioOutput});
1041
+ }
1042
+
1043
+ function disposeNarrationOutput() {
1044
+ // Call when this owner no longer needs the output; observe its promise.
1045
+ return audioOutput.dispose();
1046
+ }
1047
+ ```
1048
+
1049
+ The selected handle must be live and belong to the same AI instance; another
1050
+ owner's, closed, or disposing output throws `TypeError`. Borrowed playback
1051
+ retains the same ordered scheduling, pause/resume, cancellation, and single-lane
1052
+ replacement behavior. Completion or stopping playback leaves the output open
1053
+ for reuse. Merely preparing an output does not change the omitted-option path.
1054
+
1055
+ The application owns the output lifetime and calls `dispose()` when finished
1056
+ with it. Disposal stops playback bound to that context and any following work
1057
+ waiting on its clock, observes cleanup, and awaits the actual context close.
1058
+ It preserves detached preparation, saved audio, unrelated contexts, and model
1059
+ selection. Concurrent calls share the disposal promise. Actual cleanup or close
1060
+ errors reject and remain observable; a failed context close permits a later
1061
+ disposal retry. Stop/mute controls retain their existing playback/provider scope
1062
+ and do not replace this explicit output disposal.
1063
+
934
1064
  ## Stream chunks as they arrive
935
1065
 
936
1066
  Use the configured `ai` created above. Run this snippet from an owned user
@@ -1549,8 +1679,13 @@ as graph runtimes:
1549
1679
  Inside the dedicated speech Worker, native CacheStorage is temporarily
1550
1680
  unavailable before runtime import, so the selected runtime takes its existing
1551
1681
  fetch path. Transformers' public browser/custom/filesystem cache switches are
1552
- disabled there. The SDK does not fabricate Cache matches or mutation results,
1553
- and it neither clears nor changes saved native caches. The original Worker
1682
+ disabled there. After the runtime import, Kokoro's voice `caches.open()` calls
1683
+ use a Worker-local response cache with actual `match`/`put` results. It retains
1684
+ complete response bodies and metadata in memory for that Worker only; a miss
1685
+ does not fetch, and ordinary fetch still owns durable DBOPFS persistence. This
1686
+ temporary response retention is separate from Kokoro's own in-memory voice
1687
+ arrays. Unload clears it, and an unfinished put cannot repopulate it afterward.
1688
+ The SDK neither clears nor changes saved native caches. The original Worker
1554
1689
  environment is restored on unload or failed import. Existing explicit
1555
1690
  `removeBrowserSpeechModelCache()` remains a separate caller-selected operation.
1556
1691
 
@@ -1564,6 +1699,10 @@ closed resource parts survive cancellation. Existing whole-file caches remain
1564
1699
  readable without migration. HTTP resume is used only when the server returns
1565
1700
  the requested range; otherwise the complete resource is downloaded anew.
1566
1701
  Worker termination aborts and joins its parent-owned resource operations.
1702
+ An existing complete resource reports `phase:'load'`, `cached:true`, and
1703
+ `message:'Reading stored model resource'` with its complete shard progress.
1704
+ That event describes a DBOPFS read, not a network download or model-session
1705
+ reload. The resource owner does not fetch that stored response again.
1567
1706
 
1568
1707
  Routing discovery is best effort and is not an admission gate. If the scanner
1569
1708
  cannot interpret a module, that module is left unchanged and follows its native
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "sdkVersion": "0.75.0",
3
+ "sdkVersion": "0.77.0",
4
4
  "environment": {
5
5
  "runtime": "Node.js for Node entrypoints; browser for browser-only entrypoints",
6
6
  "minimumVersion": "22.23.2 for Node entrypoints",
@@ -10,13 +10,14 @@
10
10
  },
11
11
  "artifactCount": 93,
12
12
  "javascriptArtifactCount": 91,
13
- "esmExportCount": 373,
13
+ "esmExportCount": 375,
14
14
  "artifacts": [
15
15
  {
16
16
  "file": "runtime/arcane/modules/AI.js",
17
17
  "name": "AI.js",
18
18
  "kind": "esm",
19
19
  "exports": [
20
+ "AI_AUDIO_OUTPUT_STATE_EVENT",
20
21
  "AI_BROWSER_SPEECH_CONFIGURATION_PROTOCOL",
21
22
  "AI_BROWSER_SPEECH_ERROR_CODES",
22
23
  "AI_BROWSER_SPEECH_EVENT_TYPES",
@@ -30,7 +31,7 @@
30
31
  "availability": "Browser + native bridge + TWiN Cloud",
31
32
  "protocol": "arcane-ai-browser-speech-configuration/1, AIProviderRuntime arcane-ai-provider/2 routes, globalThis.arcaneEvents, TWiN Cloud HTTPS, Arcane.ollama, Arcane.speech, Android WebView bridge",
32
33
  "normalization": "Complete mutable caller-owned browser speech authority, SDK-owned provider registration/replacement/disposal, explicit STT/TTS activation, normalized Whisper STT and Kokoro TTS execution with default capacities 1 and 4 respectively, explicit provider-neutral execution snapshots through status(role,{execution:true}), sticky readiness, canonical window.user readiness without compatibility-event object-identity admission or polling, exact ordered structural calls with required arguments.message, atomic nonblank all-ID tool-result sequencing, complete all-choice streaming/data validation, native Ollama structural adaptation, complete-response then all validated tool callbacks then completion ordering, observed async callbacks, Blob/File transcription, default repeated-formatting-mark removal from cloned outbound TTS input, explicit textFormat:'plain' preservation for complete synthesis/preparation while streaming cleanup remains automatic and caller/visible content stays exact, immediate exact-segment synthesis, per-call voice/speed capture, optional final-segment pause on the existing audio clock, optional per-call terminal playback completion, detached preparation with complete semantic DBOPFS audio reuse and same-owner pending request sharing; each preparation owns its pending activation interest, final-interest cancellation aborts that activation while other interests preserve it, complete cache hits start no model, and cancellation after readiness leaves the ready model loaded; independent prepared playback without a model load for cached audio, ordered audio-clock scheduling, playable audio, active-generation TTS operation-failure routing, mute, and cancellation are normalized; provider/model/runtime/voice policy remains caller-owned. Both speech roles default execution.device to auto: webnn-npu when navigator.ml.createContext is exposed, then webgpu when navigator.gpu is exposed, then wasm for CPU execution. Failed backend loads clean up the pool before trying fresh Workers with the same prepared model and dtype; explicit devices never fall back. STT capacity only accepts 1; TTS accepts 1 through 4. Execution snapshots expose requestedDevice, selectedDevice, maxConcurrentRequests, and activeRequestCount for both roles; selectedDevice is null while unloaded and names the successful upstream session backend request when loaded. It does not prove every operation used the physical NPU: WebNN unsupported operations may use WASM, and compatibility depends on the browser, driver, hardware, and graph.",
33
- "surface": "Native fetchTTS(payload,signal) and fetchSTT(audio,signal) forward caller cancellation separately from their complete Core speech payloads, normalize aborts to AbortError and suppress late results; actual engine interruption remains service-owned. prepareTTSPlayback(payload,signal) silently returns a real Blob or native-speech descriptor; native controls expose finished/released/state/error/pause/resume/stop, and actual release owns the playback lane. fetchTTS and durable prepareTTS report ARCANE_AI_TTS_AUDIO_EXPORT_UNAVAILABLE for native voices. Request-local model overrides remain exact and never mutate the configured model. Browser-speech protocol/event/error/reason constants; default `AI`; read-only `providerRuntime`, `browserSpeechConfiguration`, and `browserSpeechDescriptor`; explicit `providerRuntime.status(role,{execution:true})` snapshots; `streamTTS(text,end,options={})` with optional voice, speed, pauseAfterMs, and waitForPlayback; default speech-input cleanup with SDK-internal preparation metadata and explicit complete-call textFormat:'plain' consumed before engine dispatch; `prepareTTS({parts,storage,identity,textFormat,signal,onState})` returning ordered segments, state, ready, getAudio(index), and cancel(); `playPreparedTTS(prepared,{signal,onState})` returning state, error, finished, pause(), resume(), and stop() with one playback lane per AI and observational state/error callbacks; configure/dispose speech, route lifecycle, declaration-validated chat/stream requests with exact ordered calls; optional streamRequest toolText:{name,field} and onToolText(text,call,displayId) for decoded selected argument text with actual call identity, distinct from ordinary prose and terminal tool execution; synthesis/transcription and playback controls; initializes from current canonical user readiness, installs `window.ai`, and projects `ai-ready` plus active-generation `ai-tts-failure`."
34
+ "surface": "Native fetchTTS(payload,signal) and fetchSTT(audio,signal) forward caller cancellation separately from their complete Core speech payloads, normalize aborts to AbortError and suppress late results; actual engine interruption remains service-owned. prepareTTSPlayback(payload,signal) silently returns a real Blob or native-speech descriptor; native controls expose finished/released/state/error/pause/resume/stop, and actual release owns the playback lane. fetchTTS and durable prepareTTS report ARCANE_AI_TTS_AUDIO_EXPORT_UNAVAILABLE for native voices. Request-local model overrides remain exact and never mutate the configured model. Browser-speech protocol/event/error/reason constants; default `AI`; read-only `providerRuntime`, `browserSpeechConfiguration`, and `browserSpeechDescriptor`; explicit `providerRuntime.status(role,{execution:true})` snapshots; `streamTTS(text,end,options={})` with optional voice, speed, pauseAfterMs, and waitForPlayback; default speech-input cleanup with SDK-internal preparation metadata and explicit complete-call textFormat:'plain' consumed before engine dispatch; `prepareTTS({parts,storage,identity,textFormat,signal,onState})` returning ordered segments, state, ready, getAudio(index), and cancel(); async `readPreparedTTS({parts,textFormat,storage,identity,signal,onState})` returning a complete ready handle or null through the same semantic matcher, with no activation, unmute, synthesis, playback, storage writes, or partial repair; manifest, audio availability, and returned-handle reads use DBOPFS createTable:false so absent tables remain absent; supplied adapters own that read-existing behavior; missing matches/audio return null, malformed metadata and storage failures reject, and omitted storage sees only completed matching same-AI in-memory audio; hits replay ready state, misses emit nothing, and the lookup signal detaches on settlement while returned-handle cancellation stays independent; `prepareAudioOutput()` returning the persistent WebAudio handle {kind,state,contextState,error,subscribe,resume,dispose} without resume, unmute, model load, sources, sound, or gesture listeners; current-state replay and output-specific canonical `AI_AUDIO_OUTPUT_STATE_EVENT` (`ai-audio-output-state`), with ready only for an actually running context; explicit owner disposal observes context-bound cleanup and close; `playPreparedTTS(prepared,{signal,onState,audioOutput})` returning state, error, finished, pause(), resume(), and stop() with one playback lane per AI and observational state/error callbacks; explicit live same-AI audioOutput borrows its context until owner disposal, while omission or null preserves per-playback terminal close; configure/dispose speech, route lifecycle, declaration-validated chat/stream requests with exact ordered calls; optional streamRequest toolText:{name,field} and onToolText(text,call,displayId) for decoded selected argument text with actual call identity, distinct from ordinary prose and terminal tool execution; synthesis/transcription and playback controls; initializes from current canonical user readiness, installs `window.ai`, and projects `ai-ready` plus active-generation `ai-tts-failure`."
34
35
  },
35
36
  {
36
37
  "file": "runtime/arcane/modules/AIModelSelectionController.js",
@@ -582,7 +583,7 @@
582
583
  "availability": "Browser / native WebView",
583
584
  "protocol": "OPFS, DBOPFSWorker, Compression Streams, per-realm globalThis.arcaneEvents authority, and native BroadcastChannel for live same-app/storage-partition committed changes",
584
585
  "normalization": "App scope and recognized file parsing are normalized; nonblank unreadable JSONL rows remain complete raw strings, empty-table removal reports removed, absent, or not-empty after a create:false directory lookup and without scanning or recursively clearing a directory, same-named files are preserved, aliases and cached handles are invalidated only when the directory is gone, and unexpected DOM/storage errors are preserved.",
585
- "surface": "default `DBOPFS`; installs `window.dbopfs`, emits `dbopfs-ready`; subscribeChanges(listener,{signal,once}) observes DBOPFS_EVENT_TYPES.change (dbopfs-change), returns idempotent unsubscribe/.dispose, and publishes metadata {applicationId,storagePath,tableName,directoryName,fileName,action,originId,sequence,changeId,remote} after successful storage commits only. Actions are write/append/delete/table-delete; batches preserve partial success, remote receipts invalidate targeted aliases before local publication and never rebroadcast, and pagehide/pageshow closes/reopens the native channel and clears memory caches without replay or fabricated changes. Local subscription/storage continue when BroadcastChannel is unavailable. Table/file/backup APIs include removeEmptyTable(tableName). createCompressedPNG({tableNames,additionalTables,signal}) returns a PNG Blob without download or record writes; omitted names select all, [] none, and supplied tables replace matching saved tables in the export. downloadCompressedPNG(name,options) reuses the same preparation and initiates one download; cancellation prevents it. restoreFromPNG(file,{selectTables}) closes the decoded image after pixel extraction, awaits the optional caller selection/projection before any writes, and rejects DBOPFS_RESTORE_WRITE_FAILED AggregateError after all selected table batches settle when records fail, preserving original errors and failures {tableName,fileName,reason}; successful writes remain. No-options restore and setMany retain their contracts."
586
+ "surface": "default `DBOPFS`; installs `window.dbopfs`, emits `dbopfs-ready`; subscribeChanges(listener,{signal,once}) observes DBOPFS_EVENT_TYPES.change (dbopfs-change), returns idempotent unsubscribe/.dispose, and publishes metadata {applicationId,storagePath,tableName,directoryName,fileName,action,originId,sequence,changeId,remote} after successful storage commits only. Actions are write/append/delete/table-delete; batches preserve partial success, remote receipts invalidate targeted aliases before local publication and never rebroadcast, and pagehide/pageshow closes/reopens the native channel and clears memory caches without replay or fabricated changes. Local subscription/storage continue when BroadcastChannel is unavailable. Table/file/backup APIs include getTableHandle(tableName='',{create=true}={}), readFile(tableName='',fileName='',{createTable=true}={}), get(tableName='',fileName='',force=false,{createTable=true}={}), and removeEmptyTable(tableName). Explicit create:false/createTable:false reads existing tables without creation or joining pending create work; default lazy creation remains unchanged. Low-level missing tables/files reject NotFoundError; get retains parsed caching/force semantics and resolves null only for NotFoundError, propagating other failures. Worker fallback reads already use existing app/table/file handles. createCompressedPNG({tableNames,additionalTables,signal}) returns a PNG Blob without download or record writes; omitted names select all, [] none, and supplied tables replace matching saved tables in the export. downloadCompressedPNG(name,options) reuses the same preparation and initiates one download; cancellation prevents it. restoreFromPNG(file,{selectTables}) closes the decoded image after pixel extraction, awaits the optional caller selection/projection before any writes, and rejects DBOPFS_RESTORE_WRITE_FAILED AggregateError after all selected table batches settle when records fail, preserving original errors and failures {tableName,fileName,reason}; successful writes remain. No-options restore and setMany retain their contracts."
586
587
  },
587
588
  {
588
589
  "file": "runtime/arcane/modules/DBOPFSDocumentLibrary.js",
@@ -1091,13 +1092,14 @@
1091
1092
  "name": "PreparedSpeech.js",
1092
1093
  "kind": "esm",
1093
1094
  "exports": [
1094
- "prepareSpeech"
1095
+ "prepareSpeech",
1096
+ "readPreparedSpeech"
1095
1097
  ],
1096
- "summary": "Owns detached ordered speech preparation, complete semantic audio reuse, and per-caller cancellation behind AI.prepareTTS.",
1097
- "availability": "Browser or native WebView with Blob, AbortController, an injected synthesis callback, and optional ready DBOPFS",
1098
+ "summary": "Owns detached ordered speech preparation, complete semantic audio reuse, cache-only lookup, and per-caller cancellation behind AI.prepareTTS and AI.readPreparedTTS.",
1099
+ "availability": "Browser or native WebView with Blob, AbortController, optional ready DBOPFS, and an injected synthesis callback for generation",
1098
1100
  "protocol": "In-process preparation handle plus caller-owned DBOPFS raw audio and version-1 semantic manifest",
1099
- "normalization": "Preserves complete original parts and JSON-compatible semantic context, shares pending matching work on the same owner/storage group, admits segments in order, stores complete Blobs and MIME metadata, retains successful audio after failure or cancellation, and separates generation readiness from playback.",
1100
- "surface": "Named prepareSpeech({owner,parts,originalParts,selection,segmentation,storage,identity,signal,onState,synthesize}) returns {segments,state,ready,getAudio(index),cancel()}; applications use AI.prepareTTS and AI.playPreparedTTS so the shared AI owner retains segmentation, provider readiness, capacity, and playback."
1101
+ "normalization": "Preserves complete original parts and JSON-compatible semantic context, shares pending matching work on the same owner/storage group, admits segments in order, stores complete Blobs and MIME metadata, retains successful audio after failure or cancellation, and separates generation readiness from playback. Cache-only lookup uses the same semantic matcher, reads every stored audio reference without writes or repair, and returns null for missing matches/audio while preserving genuine read failures.",
1102
+ "surface": "Named prepareSpeech({owner,parts,originalParts,selection,segmentation,storage,identity,signal,onState,synthesize}) returns {segments,state,ready,getAudio(index),cancel()}. Named async readPreparedSpeech({owner,parts,originalParts=parts,selection=null,segmentation=null,storage=null,identity=null,signal=null,onState=null}) returns a complete ready handle or null without synthesis, activation, unmute, playback, or storage writes. Manifest, audio availability, and returned-handle reads pass DBOPFS createTable:false, leaving absent tables absent; supplied adapters own the same read-existing contract. Omitted storage reads only completed matching same-owner in-memory preparations; pending or failed work returns null. A hit replays ready state and a miss emits nothing. The lookup signal detaches on settlement; the returned handle keeps independent explicit cancellation and MIME-preserving getAudio, including later storage errors. Applications use AI.prepareTTS, AI.readPreparedTTS, and AI.playPreparedTTS so the shared AI owner retains segmentation, provider readiness, capacity, and playback."
1101
1103
  },
1102
1104
  {
1103
1105
  "file": "runtime/arcane/modules/PrintView.js",