arcane-os 0.69.0 → 0.70.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 +20 -0
- package/README.md +1 -1
- package/browser-runtime/ai/browser-decisions.mjs +46 -2
- package/browser-runtime/ai/browser-speech-artifacts.mjs +58 -62
- package/browser-runtime/ai/browser-speech-providers.mjs +3 -0
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +76 -164
- package/browser-runtime/ai/dbopfs-model-parts.mjs +344 -0
- package/browser-runtime/ai/dbopfs-model-resources.mjs +190 -0
- package/browser-runtime/ai/decision-runtime.mjs +17 -1
- package/browser-runtime/ai/decision-worker.mjs +21 -1
- package/browser-runtime/ai/model-resource-bridge.mjs +231 -0
- package/browser-runtime/ai/speech-worker-client.mjs +68 -28
- package/browser-runtime/ai/speech-worker-runtime.mjs +212 -97
- package/browser-runtime/core/contracts.mjs +1 -1
- package/docs/reference/ai/browser-decisions.md +57 -3
- package/docs/reference/ai/browser-speech.md +45 -17
- package/docs/reference/ai/browser-wasm.md +28 -20
- package/docs/reference/core-native-packaging.md +13 -0
- package/docs/reference/core-runtime.md +9 -0
- package/docs/reference/core-shared-host.md +190 -0
- package/docs/reference/inventory/runtime-modules.json +3 -3
- package/docs/reference/runtime-modules.md +97 -7
- package/docs/reference/sdk-api.md +41 -30
- package/package.json +2 -2
- package/runtime/arcane/modules/AI.js +2 -18
- package/runtime/arcane/modules/AIProviderRuntime.js +77 -3
- package/runtime/arcane/modules/DBOPFS.js +180 -26
- package/src/core/host.mjs +2 -0
- package/src/core/runtime.mjs +9 -4
- package/src/core/services/packaged-web.mjs +8 -1
- package/src/core/shared-host.mjs +500 -0
- package/src/native/portable-layout.mjs +66 -34
|
@@ -8844,13 +8844,14 @@ occurrences use source `ai-model-controller`; every load or unload owns one
|
|
|
8844
8844
|
non-null operation ID shared by its state and progress occurrences. Public
|
|
8845
8845
|
progress retains the exact selected `modelId`, `phase`, state, and nested
|
|
8846
8846
|
file-progress field names. Browser-WASM download progress preserves
|
|
8847
|
-
`completed`, `total`, and `unit:'files'
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8847
|
+
`completed`, `total`, and `unit:'files'`, with complete per-file `members`
|
|
8848
|
+
reporting stored shards. A member's `total` remains `null` until its complete
|
|
8849
|
+
response is stored, unless the HTTP range plan already establishes its shard
|
|
8850
|
+
total. `activeTransfers`, `transferLimit`, and `transferMode` describe the
|
|
8851
|
+
active download work. Closed shards, active-worker changes, and completion
|
|
8852
|
+
boundaries publish semantic progress; the existing heartbeat repeats the latest
|
|
8853
|
+
record. These fields describe files and shards, without byte-based display
|
|
8854
|
+
requirements or estimated transfer rates.
|
|
8854
8855
|
Accessor-bearing provider status
|
|
8855
8856
|
and malformed progress reject with `ARCANE_AI_PROVIDER_STATUS_INVALID` and
|
|
8856
8857
|
`ARCANE_AI_PROVIDER_PROGRESS_INVALID`. After disposal, lifecycle operations fail with
|
|
@@ -9443,8 +9444,8 @@ console.log(provider.status().state); // unloaded
|
|
|
9443
9444
|
### Overview
|
|
9444
9445
|
|
|
9445
9446
|
Adapts an existing DBOPFS instance into a complete multi-model, ordered
|
|
9446
|
-
multi-file cache without renaming its public methods.
|
|
9447
|
-
|
|
9447
|
+
multi-file cache without renaming its public methods. Every new full response
|
|
9448
|
+
or Range transfer is committed in ordered parts. An interrupted set
|
|
9448
9449
|
is not a cache hit, but its completed members remain available to the next
|
|
9449
9450
|
install so only missing work is fetched.
|
|
9450
9451
|
|
|
@@ -9462,25 +9463,26 @@ workers
|
|
|
9462
9463
|
when the probe returns a usable total from exposed `Content-Range` or optional
|
|
9463
9464
|
descriptor `bytes`. The mutable
|
|
9464
9465
|
result contains `kind`, `tableName`, `downloadConcurrency`, the original
|
|
9465
|
-
`adapter`, and `ready`, `install`, `ensure`, and `
|
|
9466
|
+
`adapter`, and `ready`, `install`, `ensure`, `remove`, and `fetchResource`. `ensure()` returns
|
|
9466
9467
|
`files` and `cache:'cached'|'installed'`. It preserves the complete model content.
|
|
9467
9468
|
`offline:true` never downloads and rejects a miss with
|
|
9468
9469
|
`ARCANE_AI_MODEL_OFFLINE_MISS`.
|
|
9469
9470
|
|
|
9470
|
-
When supplied, `onProgress` receives
|
|
9471
|
-
|
|
9472
|
-
`
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
|
|
9476
|
-
|
|
9477
|
-
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9471
|
+
When supplied, `onProgress` receives ordered file progress and `members`
|
|
9472
|
+
records with each file's `name`, committed `completed` parts, `total`, and
|
|
9473
|
+
`unit:'shards'`. Streaming totals remain `null` until the response finishes;
|
|
9474
|
+
previously complete files identify `state:'cached'`. `activeTransfers` is the
|
|
9475
|
+
current transfer-worker count, `transferLimit` is selected concurrency, and
|
|
9476
|
+
`transferMode` is `probing`, `files`, `ranges`, or `single`. Closed-part and
|
|
9477
|
+
lifecycle changes publish promptly, with an existing 250 ms heartbeat. No
|
|
9478
|
+
byte progress, transfer rate, or estimated completion time is produced.
|
|
9479
|
+
|
|
9480
|
+
`fetchResource(input,{signal,onProgress,...requestOptions})` returns
|
|
9481
|
+
`{file,status,statusText,headers,url,redirected}` for a complete stored Fetch
|
|
9482
|
+
response. SDK browser model loaders use this shared owner for configuration,
|
|
9483
|
+
tokenizer, graph, external-data, and other fetched resources. Successful
|
|
9484
|
+
complete GET resources may be reused; supported HTTP ranges resume closed
|
|
9485
|
+
parts after interruption. Request framing stays at the transport boundary.
|
|
9484
9486
|
|
|
9485
9487
|
### Availability and normalization
|
|
9486
9488
|
|
|
@@ -9491,9 +9493,9 @@ resumable Range parts. For any source member, a followed redirect that
|
|
|
9491
9493
|
turns the first `bytes=0-0` probe into `200` triggers one direct probe of the
|
|
9492
9494
|
final URL. Confirmed support cancels the original body and starts the Range
|
|
9493
9495
|
pool; refusal keeps the original response as the ordinary single-fetch path,
|
|
9494
|
-
|
|
9496
|
+
without requiring a readable `Content-Length`. A missing `Content-Range` can
|
|
9495
9497
|
use optional descriptor `bytes` for Range planning; without either total, the
|
|
9496
|
-
store
|
|
9498
|
+
store uses one full fetch streamed into ordered persistent parts. A confirmed source member is divided into
|
|
9497
9499
|
deterministic OPFS Range parts of roughly 4 MB each, up to 4,096 parts. A
|
|
9498
9500
|
monolithic source may use at most
|
|
9499
9501
|
`downloadConcurrency` Range workers; split sources keep one Range worker per
|
|
@@ -9502,7 +9504,7 @@ in order as one logical Blob. A later non-206, contradictory exposed Range
|
|
|
9502
9504
|
response, or incorrectly framed body fails rather than assembling partial
|
|
9503
9505
|
content. On failure or cancellation, peers settle while completed shards and
|
|
9504
9506
|
Range parts remain available for retry; an unfinished active part restarts after
|
|
9505
|
-
refresh. A persisted Range part whose length does not match its requested HTTP
|
|
9507
|
+
refresh. Existing whole-file caches remain readable without migration. A persisted Range part whose length does not match its requested HTTP
|
|
9506
9508
|
frame is discarded and fetched again. A complete whole member supersedes its
|
|
9507
9509
|
redundant current Range fragments. Cleanup failure is warned without
|
|
9508
9510
|
hiding the usable model. Wllama requires every selected model Blob or File to
|
|
@@ -9784,7 +9786,7 @@ retains the complete list of deletions already completed.
|
|
|
9784
9786
|
|
|
9785
9787
|
Adapts an existing DBOPFS instance into a complete caller-selected speech
|
|
9786
9788
|
runtime/model store. It serializes each selected model with an exclusive Web
|
|
9787
|
-
Lock,
|
|
9789
|
+
Lock, preserves complete peer files and closed parts after failure, stores ordinary mutable selection
|
|
9788
9790
|
metadata before content, and treats a changed file inventory or source mapping
|
|
9789
9791
|
as a cache miss that is downloaded again.
|
|
9790
9792
|
|
|
@@ -9794,7 +9796,7 @@ as a cache miss that is downloaded again.
|
|
|
9794
9796
|
createDbopfsSpeechArtifactStore({ dbopfs, tableName='arcane_ai_browser_speech', fetchImpl=null, objectUrlFactory=null }={})
|
|
9795
9797
|
```
|
|
9796
9798
|
|
|
9797
|
-
The mutable result is `{protocol,tableName,prepare,remove}`.
|
|
9799
|
+
The mutable result is `{protocol,tableName,prepare,remove,fetchResource}`.
|
|
9798
9800
|
`prepare(authority,{signal,onProgress,offline=false,security})` uses a complete
|
|
9799
9801
|
compatible cache or downloads every declared file, preserves complete content
|
|
9800
9802
|
and materializes object URLs, then returns
|
|
@@ -9804,9 +9806,18 @@ needs those URLs. `offline:true` never fetches and rejects a miss with
|
|
|
9804
9806
|
authority's files and selection metadata. Optional `onProgress` receives
|
|
9805
9807
|
preparation and download records with `phase`, `stage`, `message`, and the
|
|
9806
9808
|
current `file` when available. File operations report actual `completed` and
|
|
9807
|
-
`total` inventory counts with `unit:'files'
|
|
9809
|
+
`total` inventory counts with `unit:'files'`, and `resource` carries the current
|
|
9810
|
+
closed-part progress with `unit:'shards'`; module preparation uses
|
|
9808
9811
|
`total:null`. Cancellation stops progress along with the owning operation.
|
|
9809
9812
|
|
|
9813
|
+
New files stream into ordered DBOPFS parts with no required response length.
|
|
9814
|
+
Existing complete whole files stay readable without migration.
|
|
9815
|
+
`fetchResource(input,{signal,onProgress,...requestOptions})` returns
|
|
9816
|
+
`{file,status,statusText,headers,url,redirected}` and gives the dedicated
|
|
9817
|
+
speech Worker the same complete resource storage owner for runtime fetches.
|
|
9818
|
+
Native ESM imports and unmapped classic Worker internals retain their platform
|
|
9819
|
+
loader; they are not claimed as intercepted HTTP fetches.
|
|
9820
|
+
|
|
9810
9821
|
### Availability and normalization
|
|
9811
9822
|
|
|
9812
9823
|
**Browser with a ready DBOPFS instance, OPFS, Web Locks, Fetch or an injected
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcane-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.70.0",
|
|
4
4
|
"description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
|
|
124
124
|
"test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
|
|
125
125
|
"test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/app-selection.test.mjs test/contracts.test.mjs test/conversation-closing-report.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/native-windows-icon.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
|
|
126
|
-
"test:functional": "node ./bin/arcane-test.mjs test/ai-ollama-readiness.test.mjs test/core-ollama-lifecycle.test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-decisions.test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/codex-app-server.test.mjs test/mcp-stdio-server.test.mjs test/core-classic-source.test.mjs test/core-client.test.mjs test/core-client-installation.test.mjs test/core-ai-installation.test.mjs test/core-llama-lifecycle.test.mjs test/core-development.test.mjs test/core-host.test.mjs test/core-model-assets.test.mjs test/core-native-services.test.mjs test/core-packaged-web.test.mjs test/core-runtime.test.mjs test/core-stdio.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/local-image-editing.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-media.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/native-decisions.test.mjs test/native-onnx.test.mjs test/native-module-packaging.test.mjs test/native-module-resource-references.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/rendered-print.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/theme-manager.test.mjs test/twin-cloud-images.test.mjs test/twin-cloud.test.mjs test/websocket-client-import-map.test.mjs test/websocket.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
|
|
126
|
+
"test:functional": "node ./bin/arcane-test.mjs test/ai-ollama-readiness.test.mjs test/core-ollama-lifecycle.test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-decisions.test.mjs test/browser-speech-providers.test.mjs test/browser-speech-artifacts.test.mjs test/browser-speech-resource-bridge.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/codex-app-server.test.mjs test/mcp-stdio-server.test.mjs test/core-classic-source.test.mjs test/core-client.test.mjs test/core-client-installation.test.mjs test/core-ai-installation.test.mjs test/core-llama-lifecycle.test.mjs test/core-development.test.mjs test/core-host.test.mjs test/core-shared-host.test.mjs test/core-model-assets.test.mjs test/core-native-services.test.mjs test/core-packaged-web.test.mjs test/core-runtime.test.mjs test/core-stdio.test.mjs test/dbopfs-committed-events.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-model-resources.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/local-image-editing.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-media.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/model-resource-bridge.test.mjs test/native-decisions.test.mjs test/native-onnx.test.mjs test/native-module-packaging.test.mjs test/native-module-resource-references.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/rendered-print.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/theme-manager.test.mjs test/twin-cloud-images.test.mjs test/twin-cloud.test.mjs test/websocket-client-import-map.test.mjs test/websocket.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
|
|
127
127
|
"test:integration": "node ./bin/arcane-test.mjs test/installed-package-runtime.test.mjs test/root-app-layout.test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-macos-provider.test.mjs test/native-plan.test.mjs test/native-portable-provider.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/browser-wasm-cpu.test.mjs test/wllama-webgpu-runtime.test.mjs",
|
|
128
128
|
"test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/html-import-registration.test.mjs test/html-import-packaged-script.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/prepared-speech.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
|
|
129
129
|
"check": "node tools/check-source.mjs && npm test",
|
|
@@ -1182,7 +1182,6 @@ class AI {
|
|
|
1182
1182
|
#speechJobSequence=0;
|
|
1183
1183
|
#stopOllamaReady=null;
|
|
1184
1184
|
#ttsSegmentation={...DEFAULT_TTS_SEGMENTATION};
|
|
1185
|
-
#preparedSpeechReadiness = null;
|
|
1186
1185
|
#preparedSpeechPlaybacks = new Set();
|
|
1187
1186
|
#nativeSpeechPlaybacks = new Set();
|
|
1188
1187
|
#speechUnlockContext = null;
|
|
@@ -4394,7 +4393,7 @@ class AI {
|
|
|
4394
4393
|
runtime.#browserSpeechTransition,activation.controller.signal
|
|
4395
4394
|
);
|
|
4396
4395
|
activation.controller.signal.throwIfAborted();
|
|
4397
|
-
await runtime.#providerRuntime.setSpeechMuted(false);
|
|
4396
|
+
await runtime.#providerRuntime.setSpeechMuted(false, {signal: activation.controller.signal});
|
|
4398
4397
|
if(runtime.#speechActivation!==activation)return false;
|
|
4399
4398
|
const status=runtime.#providerRuntime.status('tts');
|
|
4400
4399
|
runtime.muted=status.state!=='ready'||status.loaded!==true;
|
|
@@ -6409,22 +6408,7 @@ class AI {
|
|
|
6409
6408
|
if(selected) {
|
|
6410
6409
|
const status = runtime.#providerRuntime.status('tts');
|
|
6411
6410
|
if(status.state !== 'ready' || status.loaded !== true) {
|
|
6412
|
-
|
|
6413
|
-
const readiness = runtime.#providerRuntime.setSpeechMuted(false);
|
|
6414
|
-
runtime.#preparedSpeechReadiness = readiness;
|
|
6415
|
-
readiness.finally(
|
|
6416
|
-
function releasePreparedSpeechReadiness() {
|
|
6417
|
-
if(runtime.#preparedSpeechReadiness === readiness) {
|
|
6418
|
-
runtime.#preparedSpeechReadiness = null;
|
|
6419
|
-
}
|
|
6420
|
-
}
|
|
6421
|
-
).catch(
|
|
6422
|
-
function reportPreparedSpeechReadinessFailure(error) {
|
|
6423
|
-
runtime.#traceSpeech('prepareTTS.readiness.error', {error});
|
|
6424
|
-
}
|
|
6425
|
-
);
|
|
6426
|
-
}
|
|
6427
|
-
await runtime.#preparedSpeechReadiness;
|
|
6411
|
+
await runtime.#providerRuntime.setSpeechMuted(false, {signal: requestSignal});
|
|
6428
6412
|
assertPreparedSpeechSelection();
|
|
6429
6413
|
}
|
|
6430
6414
|
}
|
|
@@ -1688,6 +1688,7 @@ export class AIProviderRuntime {
|
|
|
1688
1688
|
#speechDesiredMuted = true;
|
|
1689
1689
|
#speechTransition = Promise.resolve();
|
|
1690
1690
|
#speechTransitionOwners = 0;
|
|
1691
|
+
#speechActivation = null;
|
|
1691
1692
|
#unsubscribeIntents = null;
|
|
1692
1693
|
#closed = false;
|
|
1693
1694
|
#closing = false;
|
|
@@ -2784,6 +2785,11 @@ export class AIProviderRuntime {
|
|
|
2784
2785
|
}
|
|
2785
2786
|
if (slot.loadPromise) {
|
|
2786
2787
|
if (this.#sameSelection(slot.selection, targetSelection)) {
|
|
2788
|
+
const activation = this.#speechActivation;
|
|
2789
|
+
if (role === 'tts' && activation?.loadPromise === slot.loadPromise
|
|
2790
|
+
&& signal !== activation.controller.signal) {
|
|
2791
|
+
return this.#observeSpeechActivation(activation, signal);
|
|
2792
|
+
}
|
|
2787
2793
|
return slot.loadPromise;
|
|
2788
2794
|
}
|
|
2789
2795
|
return Promise.reject(
|
|
@@ -2957,6 +2963,10 @@ export class AIProviderRuntime {
|
|
|
2957
2963
|
}
|
|
2958
2964
|
);
|
|
2959
2965
|
slot.loadPromise = loadOperation;
|
|
2966
|
+
if (role === 'tts' && this.#speechActivation
|
|
2967
|
+
&& signal === this.#speechActivation.controller.signal) {
|
|
2968
|
+
this.#speechActivation.loadPromise = loadOperation;
|
|
2969
|
+
}
|
|
2960
2970
|
publishAIRuntimeRoleState(
|
|
2961
2971
|
role,
|
|
2962
2972
|
roleRecord(
|
|
@@ -4270,19 +4280,34 @@ export class AIProviderRuntime {
|
|
|
4270
4280
|
return true;
|
|
4271
4281
|
}
|
|
4272
4282
|
|
|
4273
|
-
async setSpeechMuted(muted) {
|
|
4283
|
+
async setSpeechMuted(muted, {signal = null} = {}) {
|
|
4274
4284
|
arcaneLogging.debug('[Arcane speech runtime] setSpeechMuted', muted);
|
|
4275
4285
|
this.#assertOpen();
|
|
4276
4286
|
this.#assertNotConfiguring();
|
|
4277
4287
|
if (!is.boolean(muted)) {
|
|
4278
4288
|
fail('AI speech muted state must be a boolean.');
|
|
4279
4289
|
}
|
|
4290
|
+
assertAbortSignal(signal);
|
|
4291
|
+
if (signal?.aborted) throw normalizedAbort(signal.reason);
|
|
4292
|
+
if (!muted && !this.#speechDesiredMuted && this.#speechActivation
|
|
4293
|
+
&& !this.#speechActivation.controller.signal.aborted) {
|
|
4294
|
+
return this.#observeSpeechActivation(this.#speechActivation, signal);
|
|
4295
|
+
}
|
|
4280
4296
|
this.#speechDesiredMuted = muted;
|
|
4281
4297
|
if (muted) {
|
|
4282
4298
|
this.#speechMuted = true;
|
|
4299
|
+
this.#speechActivation?.controller.abort();
|
|
4283
4300
|
this.#slots.tts.loadController?.abort();
|
|
4284
4301
|
this.cancel('tts');
|
|
4285
4302
|
}
|
|
4303
|
+
const activation = muted ? null : {
|
|
4304
|
+
controller: new AbortController(),
|
|
4305
|
+
interests: new Set(),
|
|
4306
|
+
promise: null,
|
|
4307
|
+
loadPromise: null,
|
|
4308
|
+
settled: false
|
|
4309
|
+
};
|
|
4310
|
+
this.#speechActivation = activation;
|
|
4286
4311
|
const runtime = this;
|
|
4287
4312
|
this.#speechTransitionOwners += 1;
|
|
4288
4313
|
const transition = this.#speechTransition.catch(
|
|
@@ -4290,6 +4315,9 @@ export class AIProviderRuntime {
|
|
|
4290
4315
|
).then(
|
|
4291
4316
|
async function reconcileLatestAISpeechPreference() {
|
|
4292
4317
|
while (true) {
|
|
4318
|
+
if (activation?.controller.signal.aborted) {
|
|
4319
|
+
throw normalizedAbort(activation.controller.signal.reason);
|
|
4320
|
+
}
|
|
4293
4321
|
const desiredMuted = runtime.#speechDesiredMuted;
|
|
4294
4322
|
if (desiredMuted) {
|
|
4295
4323
|
runtime.cancel('tts');
|
|
@@ -4306,7 +4334,11 @@ export class AIProviderRuntime {
|
|
|
4306
4334
|
if (runtime.#speechDesiredMuted) {
|
|
4307
4335
|
continue;
|
|
4308
4336
|
}
|
|
4309
|
-
|
|
4337
|
+
const currentActivation = runtime.#speechActivation;
|
|
4338
|
+
if (currentActivation?.controller.signal.aborted) {
|
|
4339
|
+
throw normalizedAbort(currentActivation.controller.signal.reason);
|
|
4340
|
+
}
|
|
4341
|
+
await runtime.load('tts', {signal: currentActivation?.controller.signal ?? null});
|
|
4310
4342
|
}
|
|
4311
4343
|
if (runtime.#speechDesiredMuted === desiredMuted) {
|
|
4312
4344
|
runtime.#speechMuted = desiredMuted;
|
|
@@ -4318,10 +4350,52 @@ export class AIProviderRuntime {
|
|
|
4318
4350
|
).finally(
|
|
4319
4351
|
function releaseAIProviderSpeechTransitionOwnership() {
|
|
4320
4352
|
runtime.#speechTransitionOwners -= 1;
|
|
4353
|
+
if (activation) {
|
|
4354
|
+
activation.settled = true;
|
|
4355
|
+
if (runtime.#speechActivation === activation) {
|
|
4356
|
+
runtime.#speechActivation = null;
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4321
4359
|
}
|
|
4322
4360
|
);
|
|
4323
4361
|
this.#speechTransition = transition;
|
|
4324
|
-
return transition;
|
|
4362
|
+
if (!activation) return transition;
|
|
4363
|
+
activation.promise = transition;
|
|
4364
|
+
return this.#observeSpeechActivation(activation, signal);
|
|
4365
|
+
}
|
|
4366
|
+
|
|
4367
|
+
#observeSpeechActivation(activation, signal) {
|
|
4368
|
+
return new Promise(function observeAIProviderSpeechActivation(resolve, reject) {
|
|
4369
|
+
const interest = {};
|
|
4370
|
+
let settled = false;
|
|
4371
|
+
activation.interests.add(interest);
|
|
4372
|
+
function releaseSpeechActivationInterest() {
|
|
4373
|
+
activation.interests.delete(interest);
|
|
4374
|
+
signal?.removeEventListener('abort', cancelSpeechActivationInterest);
|
|
4375
|
+
}
|
|
4376
|
+
function cancelSpeechActivationInterest() {
|
|
4377
|
+
if (settled) return;
|
|
4378
|
+
settled = true;
|
|
4379
|
+
releaseSpeechActivationInterest();
|
|
4380
|
+
if (!activation.settled && activation.interests.size === 0) {
|
|
4381
|
+
activation.controller.abort(signal?.reason);
|
|
4382
|
+
}
|
|
4383
|
+
reject(normalizedAbort(signal?.reason));
|
|
4384
|
+
}
|
|
4385
|
+
signal?.addEventListener('abort', cancelSpeechActivationInterest, {once: true});
|
|
4386
|
+
activation.promise.then(function speechActivationReady(status) {
|
|
4387
|
+
if (settled) return;
|
|
4388
|
+
settled = true;
|
|
4389
|
+
releaseSpeechActivationInterest();
|
|
4390
|
+
resolve(status);
|
|
4391
|
+
}, function speechActivationFailed(error) {
|
|
4392
|
+
if (settled) return;
|
|
4393
|
+
settled = true;
|
|
4394
|
+
releaseSpeechActivationInterest();
|
|
4395
|
+
reject(error);
|
|
4396
|
+
});
|
|
4397
|
+
if (signal?.aborted) cancelSpeechActivationInterest();
|
|
4398
|
+
});
|
|
4325
4399
|
}
|
|
4326
4400
|
|
|
4327
4401
|
#providerFor(slot) {
|