arcane-os 0.68.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +9 -4
  3. package/browser-runtime/ai/browser-decisions.mjs +46 -2
  4. package/browser-runtime/ai/browser-speech-artifacts.mjs +58 -62
  5. package/browser-runtime/ai/browser-speech-providers.mjs +8 -3
  6. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +76 -164
  7. package/browser-runtime/ai/dbopfs-model-parts.mjs +344 -0
  8. package/browser-runtime/ai/dbopfs-model-resources.mjs +190 -0
  9. package/browser-runtime/ai/decision-runtime.mjs +17 -1
  10. package/browser-runtime/ai/decision-worker.mjs +21 -1
  11. package/browser-runtime/ai/model-resource-bridge.mjs +231 -0
  12. package/browser-runtime/ai/speech-worker-client.mjs +68 -28
  13. package/browser-runtime/ai/speech-worker-runtime.mjs +212 -97
  14. package/browser-runtime/core/contracts.mjs +1 -1
  15. package/docs/reference/README.md +2 -0
  16. package/docs/reference/ai/browser-decisions.md +57 -3
  17. package/docs/reference/ai/browser-speech.md +94 -26
  18. package/docs/reference/ai/browser-wasm.md +28 -20
  19. package/docs/reference/core-native-packaging.md +54 -0
  20. package/docs/reference/core-runtime.md +9 -0
  21. package/docs/reference/core-shared-host.md +190 -0
  22. package/docs/reference/inventory/package-api.json +3 -3
  23. package/docs/reference/inventory/runtime-modules.json +3 -3
  24. package/docs/reference/runtime-components.md +4 -2
  25. package/docs/reference/runtime-modules.md +120 -20
  26. package/docs/reference/sdk-api.md +64 -30
  27. package/package.json +2 -2
  28. package/runtime/arcane/components/theme-switcher.html +1 -1
  29. package/runtime/arcane/modules/AI.js +7 -22
  30. package/runtime/arcane/modules/AIProviderRuntime.js +84 -6
  31. package/runtime/arcane/modules/DBOPFS.js +180 -26
  32. package/schemas/arcane-app.schema.json +10 -0
  33. package/src/app-descriptor.mjs +23 -1
  34. package/src/core/host.mjs +2 -0
  35. package/src/core/hosts/windows/ArcaneHost.cs +46 -0
  36. package/src/core/hosts/windows/ArcaneLauncher.cs +21 -0
  37. package/src/core/runtime.mjs +9 -4
  38. package/src/core/services/packaged-web.mjs +8 -1
  39. package/src/core/shared-host.mjs +500 -0
  40. package/src/native/portable-layout.mjs +66 -34
  41. package/src/native/portable-provider.mjs +1 -0
@@ -170,7 +170,7 @@ default `AI`; read-only `providerRuntime`, `speechActivationPending`, `browserSp
170
170
  `streamRequest()`, `streamMessage()`, `fetchRequest()`, `fetch()`,
171
171
  read-only `ttsSegmentation`, `configureTTSSegmentation()`,
172
172
  `streamTTS(text='',end=false,options={})`,
173
- `prepareTTS({parts,storage,identity,signal,onState})`,
173
+ `prepareTTS({parts,textFormat,storage,identity,signal,onState})`,
174
174
  `playPreparedTTS(prepared,{signal,onState})`,
175
175
  `finishTTS()`, `prepareTTSPlayback()`, `fetchTTS()`, `fetchSTT()`, `stopAudio()`, `resumeAudio()`,
176
176
  `playAudio()`; consumes `user-entity-loaded` and `arcane-ollama-ready`,
@@ -393,14 +393,17 @@ synthesis payload to `Arcane.speech.synthesize(request, {signal})`; Core aborts
393
393
  are normalized to `AbortError` with `ARCANE_AI_REQUEST_ABORTED`. Actual engine
394
394
  interruption belongs to the registered service. It does not independently
395
395
  choose a provider, cloud fallback, model, runtime, or
396
- voice policy for the application. Every call removes repeated same formatting
397
- marks from a cloned outbound input before delegation; the caller's payload stays
398
- unchanged. The third `preparation` argument is reserved for SDK-internal
396
+ voice policy for the application. By default, calls remove repeated same
397
+ formatting marks from a cloned outbound input before delegation; the caller's
398
+ payload stays unchanged. Public payload field `textFormat:'plain'` preserves
399
+ the complete input, including whitespace and repeated marks. The SDK consumes
400
+ that field before provider/host dispatch. Omission and other values retain the
401
+ default cleanup. The third `preparation` argument is reserved for SDK-internal
399
402
  delegation, where `{speechInputPrepared:true}` prevents a second cleanup pass;
400
403
  applications omit it. `streamTTS(text='',end=false,options={})` and
401
404
  `finishTTS()` use the selected playback boundary. The third-argument options below
402
- are available in SDK `0.5.12`. The `textFormat` compatibility extra added in
403
- SDK `0.5.16` is ignored beginning in `0.5.17`:
405
+ are available in SDK `0.5.12`. In these streaming options, the `textFormat`
406
+ compatibility extra added in SDK `0.5.16` remains ignored:
404
407
 
405
408
  | Field | Default | Meaning |
406
409
  | --- | --- | --- |
@@ -408,7 +411,7 @@ SDK `0.5.16` is ignored beginning in `0.5.17`:
408
411
  | `speed` | Current `ai.voiceSpeed` | A supplied positive speed is captured for those segments and forwarded to `fetchTTS()`. It does not change `ai.voiceSpeed`. |
409
412
  | `pauseAfterMs` | `0` | Finite, nonnegative milliseconds placed after the final extracted segment on the existing audio clock. Invalid values throw `RangeError`; no pause is inserted between this call's other segments. |
410
413
  | `waitForPlayback` | `false` | Omission retains the preparation promise. With `true`, the promise resolves after every extracted segment reaches a terminal playback state: `true` when all naturally end, or `false` after terminal cancellation or failure. |
411
- | `textFormat` | Ignored compatibility extra | Repeated same formatting marks are removed automatically from every TTS call. This value no longer selects or disables cleanup. |
414
+ | `textFormat` | Ignored streaming compatibility extra | Streaming narration retains automatic cleanup. The public plain selection belongs to complete synthesis payloads or `prepareTTS` options. |
412
415
 
413
416
  Audio-file voice and speed use the existing `fetchTTS()` validation and error path.
414
417
  The automatic cleanup changes only the outbound speech-input copy; displayed,
@@ -418,7 +421,7 @@ text, single marks, ordinary punctuation, and other characters remain literal.
418
421
  A trailing candidate mark waits for the next character so a repeated run split
419
422
  across chunks is still omitted. Ordinary prose streams immediately.
420
423
  `end:true`, `finishTTS()`, muted terminal calls, and `stopAudio()` clear pending
421
- formatting state. `textFormat` is not an opt-out.
424
+ formatting state. Streaming `textFormat` is not an opt-out.
422
425
 
423
426
  Voice, speed, pause, and playback overrides belong to the segments
424
427
  extracted in that invocation, including any text buffered by an earlier call.
@@ -458,18 +461,29 @@ last audio buffer ends, without waiting out that pause. Completion describes
458
461
  the playback lifecycle, not proof that a listener heard the sound. See the
459
462
  [complete-passage example](ai/browser-speech.md#queue-complete-passages-and-wait-for-playback).
460
463
 
461
- `prepareTTS({parts,storage,identity,signal,onState})` returns an immediate
464
+ `prepareTTS({parts,textFormat,storage,identity,signal,onState})` returns an immediate
462
465
  `{segments,state,ready,getAudio(index),cancel()}` handle for detached complete
463
466
  speech preparation. Parts are strings or `{input,voice?,speed?,pauseAfterMs?}`
464
467
  records. It retains the full source for semantic matching, snapshots the
465
468
  selected speech configuration and segmentation, and applies automatic
466
- formatting cleanup once to the speech copy. Generation uses the existing
469
+ formatting cleanup once to the speech copy by default. `textFormat:'plain'`
470
+ preserves each complete input before the same ordered segmentation. Prepared
471
+ segment text participates in reuse, keeping cleaned and exact synthesis inputs
472
+ distinct when they differ. This SDK text-treatment choice does not change the
473
+ selected engine's own input capacity. Generation uses the existing
467
474
  bounded provider queue without adding playback. Optional
468
475
  `storage:{db,table,key}` saves complete audio files and their MIME metadata in
469
476
  the caller's ready DBOPFS instance; the separate JSON-compatible `identity`
470
477
  adds application-owned semantic context. Reuse compares complete inputs rather
471
478
  than an SDK version alone.
472
479
 
480
+ Only missing audio joins TTS activation. Each preparation supplies its own
481
+ cancellation lifetime to the existing provider runtime. The last pending
482
+ activation interest cancels the owned load; other preparation/playback or
483
+ joining direct-load interests preserve it. Cache-only preparation never loads
484
+ the model, and cancelling after activation does not unload an already-ready
485
+ model. Provider work owns actual cancellation and cleanup completion.
486
+
473
487
  `state` is `queued`, `preparing`, `ready`, `error`, or `cancelled`.
474
488
  `onState({state,completed,total,segments,error})` synchronously observes
475
489
  preparation progress. `ready` resolves the complete record after every segment
@@ -479,8 +493,8 @@ the retained MIME type. Preparation failure rejects; cancellation rejects as
479
493
  `AbortError` and preserves successfully stored segments. Matching pending
480
494
  requests share synthesis only on the same AI instance and storage group;
481
495
  cancelling one handle does not cancel another active matching caller.
482
- Preparation cancellation prevents later synthesis, but an already-started
483
- shared provider load/unmute has no per-preparation signal and may finish.
496
+ Preparation cancellation prevents later synthesis and releases its activation
497
+ interest. The provider's operation remains responsible for actual cleanup.
484
498
 
485
499
  `playPreparedTTS(prepared,{signal,onState})` can attach immediately. Its returned
486
500
  `{state,error,finished,pause(),resume(),stop()}` handle schedules segments in
@@ -1017,9 +1031,20 @@ The singleton exposes read-only `protocol`, `configured`, and `speechMuted`;
1017
1031
  `supportsTranscriptionCapture(providerId=null)`;
1018
1032
  `createTranscriptionCapture(options={})`;
1019
1033
  `synthesize(payload,options={},preparation={})`; and
1020
- `setSpeechMuted(muted)`. Provider payloads must be data-only; callbacks,
1034
+ `setSpeechMuted(muted,{signal}={})`. Provider payloads must be data-only; callbacks,
1021
1035
  accessors, symbols, and cycles are rejected at the provider boundary.
1022
1036
 
1037
+ `setSpeechMuted(false,{signal})` shares pending activation while retaining an
1038
+ independent interest for each caller. Aborting one signal rejects only that
1039
+ caller's wait. When no pending interests remain, abort reaches the owned load.
1040
+ No-signal callers and direct `load('tts',...)` calls joining that activation
1041
+ also retain it. A rejected load preserves its complete error for the remaining
1042
+ callers. An already-aborted signal rejects before changing mute intent.
1043
+ Explicit `setSpeechMuted(true)` still cancels shared activation and unloads TTS;
1044
+ once accepted, that mute cleanup runs to completion rather than being undone
1045
+ by cancellation of its caller's signal. Completed activation is not unloaded
1046
+ merely because a former observer later aborts.
1047
+
1023
1048
  Selection options admit `localOnly=false`; inspection admits
1024
1049
  `{localOnly=false,signal=null}`; startup admits
1025
1050
  `{startLanguageModel=true,startMuted=true,startTranscription=false,signal=null}` (including an omitted
@@ -1032,9 +1057,12 @@ records are the closed `{llm,stt,tts}`, `{stt,tts}`, or
1032
1057
  `{providers,routes,expectedProviders}` shapes described below.
1033
1058
  `configureFromTuple()` accepts exactly six provider/model preference entries.
1034
1059
 
1035
- Every direct TTS `request()` or `synthesize()` call removes repeated same
1036
- formatting marks from a cloned outbound payload's `input` or `text` field. The
1037
- caller's payload and request records remain unchanged. The optional
1060
+ By default, direct TTS `request()` and `synthesize()` remove repeated same
1061
+ formatting marks from a cloned outbound payload's `input` or `text` field.
1062
+ Public payload field `textFormat:'plain'` preserves that input exactly. The SDK
1063
+ consumes this field before queuing/dispatch; queued requests retain their
1064
+ selected text treatment. Omission and other values keep the default cleanup.
1065
+ The caller's payload and request records remain unchanged. The optional
1038
1066
  `preparation` argument is reserved for SDK-owned delegation;
1039
1067
  `{speechInputPrepared:true}` prevents a second pass after another SDK speech
1040
1068
  boundary has already cleaned the copy. Applications omit that argument. LLM
@@ -2193,6 +2221,59 @@ default `DBOPFS`; installs `window.dbopfs`, emits `dbopfs-ready`; table/file/bac
2193
2221
 
2194
2222
  Exact exports: `DBOPFS_EVENT_TYPES`, `DBOPFS_REASONS`, `default`.
2195
2223
 
2224
+ `subscribeChanges(listener, {signal, once})` observes
2225
+ `DBOPFS_EVENT_TYPES.change` (`dbopfs-change`) through the existing canonical
2226
+ `arcane-os/event-manager` source. It returns an idempotent unsubscribe function
2227
+ with `.dispose()`; aborting the supplied signal also removes the subscription.
2228
+ Each listener receives an event whose `detail` contains:
2229
+
2230
+ | Field | Meaning |
2231
+ | --- | --- |
2232
+ | `applicationId`, `storagePath` | Existing application identity and `apps/<application-id>` OPFS scope. |
2233
+ | `tableName`, `directoryName` | Logical table and physical directory; `memories` / `memory` retain their existing alias. |
2234
+ | `fileName` | Complete record filename, or `null` for a removed table. |
2235
+ | `action` | `write`, `append`, `delete`, or `table-delete`. A write does not claim whether a record was newly created. |
2236
+ | `originId`, `sequence`, `changeId` | Per-document origin and incrementing committed occurrence, shared by local and remote notifications for that occurrence. These are correlation fields, separate from realm-local canonical event-source IDs. |
2237
+ | `remote` | `false` at the writing DBOPFS source, `true` when received from another live document. |
2238
+
2239
+ A write is published after writable-stream `close()` succeeds, or after the
2240
+ existing OPFS worker acknowledges its completed write/flush/close. A removal
2241
+ is published after OPFS removes the record or table. The affected cached record
2242
+ or table, including alias keys and removed-table handles, is invalidated before
2243
+ listeners run. A read already in progress may still resolve its earlier
2244
+ snapshot; it cannot repopulate the cache after an intervening delivered change.
2245
+ Notification metadata does not contain, rewrite, or reread record bodies.
2246
+
2247
+ `set()` and raw `writeFile()` publish one occurrence per completed write;
2248
+ `setMany()`, `deleteMany()`, clear operations, and restore retain their existing
2249
+ per-record results and publish only their actual successful mutations. A
2250
+ partial batch is not a transaction: successful records are announced even when
2251
+ siblings fail. Missing records, absent/non-empty empty-table removal, failed
2252
+ writes/removals, and reads emit no committed-change event. `deleteTable()` keeps
2253
+ its existing return/error-reporting behavior; its returned `true` alone does
2254
+ not imply that a committed-change event was emitted.
2255
+
2256
+ Within one document, subscribe through this source or the canonical event
2257
+ manager, rather than consuming both as separate changes. Across documents,
2258
+ DBOPFS owns one native `BroadcastChannel` named from the application storage
2259
+ path. Native storage-partition matching plus that path selects the receiving
2260
+ documents. A remote receiver invalidates its affected cache before publishing
2261
+ locally and never rebroadcasts the occurrence. There is no echo back to the
2262
+ writing DBOPFS source. Ordinary storage success remains success if a transport
2263
+ notification fails; the notification error is reported separately.
2264
+
2265
+ Subscriptions are live-only: subscribe before the application's initial read,
2266
+ then let the application map the table/file/action to its own records and UI.
2267
+ DBOPFS performs no polling, initial full-table read, automatic picker refresh,
2268
+ or event-history replay. The channel lasts for the owning page, independent of
2269
+ individual listener lifetimes, so a document that only writes still announces
2270
+ its commits. `pagehide` closes the channel; `pageshow` after suspension reopens
2271
+ it and clears only that document's in-memory caches. Resuming a page does not
2272
+ fabricate a storage mutation. Applications own any resume-time view refresh.
2273
+ Missed events during suspension, process termination, or realm loss are not
2274
+ replayed; the stored records remain the authority. Direct OPFS writes made
2275
+ outside DBOPFS are not observed by this contract.
2276
+
2196
2277
  `createCompressedPNG({tableNames, additionalTables, signal})` returns an
2197
2278
  `image/png` Blob without downloading or writing records. Omitted `tableNames`
2198
2279
  selects all discovered saved tables; `[]` selects none. Named absent tables
@@ -2234,16 +2315,35 @@ unreadable row remains in its original string form so the owning application
2234
2315
  can display, diagnose, or recover it without silent data loss. DOM and storage
2235
2316
  errors remain observable. The logical `memories` table continues to map to the
2236
2317
  physical `memory` directory, and successful or already-absent empty-table
2237
- removal invalidates both alias and cached-handle state. Transport: OPFS,
2238
- DBOPFSWorker, Compression Streams.
2318
+ removal invalidates both alias and cached-handle state. Cross-document change
2319
+ delivery requires native `BroadcastChannel` in the same origin and storage
2320
+ partition, with the same application scope; separate WebView profiles do not
2321
+ share this transport. If it is unavailable, local change subscriptions and
2322
+ ordinary DBOPFS storage continue, without cross-document notification or a
2323
+ polling replacement. Transport: OPFS, DBOPFSWorker, Compression Streams, native
2324
+ BroadcastChannel, and the existing event-pubsub-backed canonical event owner.
2239
2325
  [Deep protocol details](protocols.md).
2240
2326
 
2241
2327
  ### Example
2242
2328
 
2243
2329
  ```javascript
2244
- import * as module from '/arcane/modules/DBOPFS.js';
2330
+ import '/arcane/modules/DBOPFS.js';
2331
+
2332
+ await dbopfs.readyPromise;
2333
+ const lifetime = new AbortController();
2334
+ const stop = dbopfs.subscribeChanges(function showCommittedParade(event) {
2335
+ const change = event.detail;
2336
+ if (change.tableName === 'moon_bus') {
2337
+ console.log(change.action, change.fileName, change.remote);
2338
+ }
2339
+ }, {signal: lifetime.signal});
2245
2340
 
2246
- console.log(Object.keys(module));
2341
+ await dbopfs.set('moon_bus', 'squid-parade.json', {
2342
+ destination: 'The moon', passengers: ['Captain Squid', 'One confused lobster']
2343
+ });
2344
+ // Another live page using this app/storage scope receives the same occurrence.
2345
+ // The application can read that one record when its view needs the body.
2346
+ stop(); // lifetime.abort() is the equivalent subscription cleanup.
2247
2347
  ```
2248
2348
 
2249
2349
  ## DBOPFSDocumentLibrary.js
@@ -1906,6 +1906,20 @@ default class PreferenceStore extends EventTarget
1906
1906
  subpath and managed browser key both resolve directly to the canonical runtime
1907
1907
  module. Deep protocol: [Browser runtime delivery](protocols.md#browser-runtime-delivery).
1908
1908
 
1909
+ The default adapter recognizes an absent optional preferences namespace only
1910
+ from the exact Core lookup tuple `code:'METHOD_NOT_ALLOWED'`,
1911
+ `reason:'core-namespace-unavailable'`, `namespace:'preferences'`, and the
1912
+ matching `method:'preferences.<operation>'`. Single `get`, `set`, and `delete`
1913
+ operations then retry once through the existing app-scoped local adapter after
1914
+ checking disposal and cancellation, preserving complete values and operation
1915
+ context. The existing Android unsupported-capability route is unchanged.
1916
+ An advertised `setMany` batch rejected for that exact absence retains its
1917
+ original rejection and performs no serial retry; subsequent operations use the
1918
+ selected local adapter.
1919
+ Partial-service, permission, persistence, transport and cancellation failures
1920
+ remain errors. Explicit adapters retain their own behavior. See the
1921
+ [complete PreferenceStore contract](runtime-modules.md#preferencestorejs).
1922
+
1909
1923
  ### Example
1910
1924
 
1911
1925
  ```javascript
@@ -7325,6 +7339,15 @@ without restarting services. Its snapshot preserves complete service errors
7325
7339
  and active request state; see [runtime replay](core-runtime.md#state-and-frames)
7326
7340
  for authoritative readiness and reentrant lifecycle ordering.
7327
7341
 
7342
+ An unregistered method returns `METHOD_NOT_ALLOWED` with its exact `method`,
7343
+ first dotted `namespace`, and `reason:'core-namespace-unavailable'` when that
7344
+ namespace has neither a named service nor any registered method. Otherwise
7345
+ the reason is `core-method-unavailable`; built-in `system`, `app` and `version`
7346
+ namespaces count as present. This is a lookup result before handler or
7347
+ permission decisions. Handler/startup errors and older unmarked errors do not
7348
+ establish namespace absence. The dispatcher does not choose a replacement
7349
+ service; optional browser adapters own their documented response to absence.
7350
+
7328
7351
  ### Availability and normalization
7329
7352
 
7330
7353
  **Node on Windows, Linux and macOS native hosts; Android requires its native host
@@ -8821,13 +8844,14 @@ occurrences use source `ai-model-controller`; every load or unload owns one
8821
8844
  non-null operation ID shared by its state and progress occurrences. Public
8822
8845
  progress retains the exact selected `modelId`, `phase`, state, and nested
8823
8846
  file-progress field names. Browser-WASM download progress preserves
8824
- `completed`, `total`, and `unit:'files'` and adds `loadedBytes`, `totalBytes`,
8825
- `remainingBytes`, `bytesPerSecond`, `etaSeconds`, `activeTransfers`,
8826
- `transferLimit`, and `transferMode`. Unknown totals, rates, remaining values,
8827
- and ETAs are `null`; applications format the raw byte and second measures for
8828
- display. Chunk-driven transfer changes are coalesced on a 250 ms cadence, while
8829
- plan, active-worker, and completion boundaries publish immediately and the existing five-second
8830
- heartbeat repeats the latest record.
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.
8831
8855
  Accessor-bearing provider status
8832
8856
  and malformed progress reject with `ARCANE_AI_PROVIDER_STATUS_INVALID` and
8833
8857
  `ARCANE_AI_PROVIDER_PROGRESS_INVALID`. After disposal, lifecycle operations fail with
@@ -9420,8 +9444,8 @@ console.log(provider.status().state); // unloaded
9420
9444
  ### Overview
9421
9445
 
9422
9446
  Adapts an existing DBOPFS instance into a complete multi-model, ordered
9423
- multi-file cache without renaming its public methods. Each completed whole
9424
- member or Range part is committed independently. An interrupted set
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
9425
9449
  is not a cache hit, but its completed members remain available to the next
9426
9450
  install so only missing work is fetched.
9427
9451
 
@@ -9439,25 +9463,26 @@ workers
9439
9463
  when the probe returns a usable total from exposed `Content-Range` or optional
9440
9464
  descriptor `bytes`. The mutable
9441
9465
  result contains `kind`, `tableName`, `downloadConcurrency`, the original
9442
- `adapter`, and `ready`, `install`, `ensure`, and `remove`. `ensure()` returns
9466
+ `adapter`, and `ready`, `install`, `ensure`, `remove`, and `fetchResource`. `ensure()` returns
9443
9467
  `files` and `cache:'cached'|'installed'`. It preserves the complete model content.
9444
9468
  `offline:true` never downloads and rejects a miss with
9445
9469
  `ARCANE_AI_MODEL_OFFLINE_MISS`.
9446
9470
 
9447
- When supplied, `onProgress` receives existing ordered file progress plus
9448
- `loadedBytes`, `totalBytes`, `remainingBytes`, `bytesPerSecond`, `etaSeconds`,
9449
- `activeTransfers`, `transferLimit`, and `transferMode`. `activeTransfers` is the
9450
- current transfer-worker count. Transfer mode is
9451
- `probing`, `files`, `ranges`, or `single`. Unknown aggregate totals, remaining
9452
- values, rates, and ETAs are `null`. Chunk-driven changes are coalesced on a
9453
- 250 ms cadence, with immediate records at download start, plan or total
9454
- discovery, active-worker changes, and completion. `loadedBytes` includes current staged writes and
9455
- restored completed members or Range parts; bytes from a failed partial write
9456
- are removed from the total when that entry is discarded. A complete set of
9457
- declared member `bytes` values
9458
- makes split-model total and remaining progress available from the start;
9459
- otherwise aggregate-dependent fields remain `null` until they are honestly
9460
- known.
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.
9461
9486
 
9462
9487
  ### Availability and normalization
9463
9488
 
@@ -9468,9 +9493,9 @@ resumable Range parts. For any source member, a followed redirect that
9468
9493
  turns the first `bytes=0-0` probe into `200` triggers one direct probe of the
9469
9494
  final URL. Confirmed support cancels the original body and starts the Range
9470
9495
  pool; refusal keeps the original response as the ordinary single-fetch path,
9471
- with readable `Content-Length` used for progress. A missing `Content-Range` can
9496
+ without requiring a readable `Content-Length`. A missing `Content-Range` can
9472
9497
  use optional descriptor `bytes` for Range planning; without either total, the
9473
- store falls back to one full fetch. A confirmed source member is divided into
9498
+ store uses one full fetch streamed into ordered persistent parts. A confirmed source member is divided into
9474
9499
  deterministic OPFS Range parts of roughly 4 MB each, up to 4,096 parts. A
9475
9500
  monolithic source may use at most
9476
9501
  `downloadConcurrency` Range workers; split sources keep one Range worker per
@@ -9479,7 +9504,7 @@ in order as one logical Blob. A later non-206, contradictory exposed Range
9479
9504
  response, or incorrectly framed body fails rather than assembling partial
9480
9505
  content. On failure or cancellation, peers settle while completed shards and
9481
9506
  Range parts remain available for retry; an unfinished active part restarts after
9482
- 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
9483
9508
  frame is discarded and fetched again. A complete whole member supersedes its
9484
9509
  redundant current Range fragments. Cleanup failure is warned without
9485
9510
  hiding the usable model. Wllama requires every selected model Blob or File to
@@ -9761,7 +9786,7 @@ retains the complete list of deletions already completed.
9761
9786
 
9762
9787
  Adapts an existing DBOPFS instance into a complete caller-selected speech
9763
9788
  runtime/model store. It serializes each selected model with an exclusive Web
9764
- Lock, removes partial state after failure, stores ordinary mutable selection
9789
+ Lock, preserves complete peer files and closed parts after failure, stores ordinary mutable selection
9765
9790
  metadata before content, and treats a changed file inventory or source mapping
9766
9791
  as a cache miss that is downloaded again.
9767
9792
 
@@ -9771,7 +9796,7 @@ as a cache miss that is downloaded again.
9771
9796
  createDbopfsSpeechArtifactStore({ dbopfs, tableName='arcane_ai_browser_speech', fetchImpl=null, objectUrlFactory=null }={})
9772
9797
  ```
9773
9798
 
9774
- The mutable result is `{protocol,tableName,prepare,remove}`.
9799
+ The mutable result is `{protocol,tableName,prepare,remove,fetchResource}`.
9775
9800
  `prepare(authority,{signal,onProgress,offline=false,security})` uses a complete
9776
9801
  compatible cache or downloads every declared file, preserves complete content
9777
9802
  and materializes object URLs, then returns
@@ -9781,9 +9806,18 @@ needs those URLs. `offline:true` never fetches and rejects a miss with
9781
9806
  authority's files and selection metadata. Optional `onProgress` receives
9782
9807
  preparation and download records with `phase`, `stage`, `message`, and the
9783
9808
  current `file` when available. File operations report actual `completed` and
9784
- `total` inventory counts with `unit:'files'`; module preparation uses
9809
+ `total` inventory counts with `unit:'files'`, and `resource` carries the current
9810
+ closed-part progress with `unit:'shards'`; module preparation uses
9785
9811
  `total:null`. Cancellation stops progress along with the owning operation.
9786
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
+
9787
9821
  ### Availability and normalization
9788
9822
 
9789
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.68.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",
@@ -38,7 +38,7 @@
38
38
  overflow-wrap:anywhere;
39
39
  }
40
40
 
41
- button:hover:not(:disabled) {
41
+ button:hover:not(:disabled):not([aria-pressed="true"]) {
42
42
  background:color-mix(in srgb,currentColor 14%,transparent);
43
43
  }
44
44
 
@@ -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;
@@ -6336,7 +6335,7 @@ class AI {
6336
6335
  return result;
6337
6336
  }
6338
6337
 
6339
- prepareTTS({parts, storage = null, identity = null, signal = null, onState = null} = {}) {
6338
+ prepareTTS({parts, textFormat, storage = null, identity = null, signal = null, onState = null} = {}) {
6340
6339
  if(this.#providerSpeechModel()?.speech?.playback==='native'){
6341
6340
  const error=new Error('Native browser speech has no audio file to prepare or store; use prepareTTSPlayback.');
6342
6341
  error.code='ARCANE_AI_TTS_AUDIO_EXPORT_UNAVAILABLE';
@@ -6384,7 +6383,7 @@ class AI {
6384
6383
  if(!is.finite(pauseAfterMs) || pauseAfterMs < 0) {
6385
6384
  throw new RangeError('Prepared speech pauses must be nonnegative milliseconds.');
6386
6385
  }
6387
- const cleaned = stripSpeechFormatting(source.input);
6386
+ const cleaned = textFormat === 'plain' ? source.input : stripSpeechFormatting(source.input);
6388
6387
  const {segments: outputs} = this.#segmentSpeechText(cleaned, true, segmentation);
6389
6388
  for(const [index, input] of outputs.entries()) {
6390
6389
  if(!input.trim()) continue;
@@ -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
- if(!runtime.#preparedSpeechReadiness) {
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
  }
@@ -7175,6 +7159,7 @@ class AI {
7175
7159
  'model',
7176
7160
  'voice',
7177
7161
  'input',
7162
+ 'textFormat',
7178
7163
  'responseFormat',
7179
7164
  'speed'
7180
7165
  ]);
@@ -7183,7 +7168,7 @@ class AI {
7183
7168
  ||!acceptedKeys.has(key)
7184
7169
  ||!Object.hasOwn(descriptors[key],'value')){
7185
7170
  const error=new TypeError(
7186
- 'AI.fetchTTS accepts only model, voice, input, responseFormat, and speed data properties.'
7171
+ 'AI.fetchTTS accepts only model, voice, input, textFormat, responseFormat, and speed data properties.'
7187
7172
  );
7188
7173
  error.code='ARCANE_AI_TTS_REQUEST_INVALID';
7189
7174
  throw error;
@@ -7208,7 +7193,7 @@ class AI {
7208
7193
  error.code='ARCANE_AI_TTS_INPUT_INVALID';
7209
7194
  throw error;
7210
7195
  }
7211
- const input=speechInputPrepared===true
7196
+ const input=speechInputPrepared===true||descriptors.textFormat?.value==='plain'
7212
7197
  ?suppliedInput
7213
7198
  :stripSpeechFormatting(suppliedInput);
7214
7199
  const selection=this.#providerRuntime.selection('tts');