arcane-os 0.62.0 → 0.64.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 (44) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +1 -1
  3. package/browser-runtime/core/client.mjs +7 -2
  4. package/docs/reference/README.md +7 -0
  5. package/docs/reference/cli.md +5 -1
  6. package/docs/reference/core-client.md +11 -2
  7. package/docs/reference/core-native-packaging.md +57 -8
  8. package/docs/reference/core-repositories.md +149 -0
  9. package/docs/reference/inventory/package-api.json +163 -9
  10. package/docs/reference/local-ai.md +51 -7
  11. package/docs/reference/local-image-flux.md +154 -0
  12. package/docs/reference/local-image-generation.md +12 -0
  13. package/docs/reference/local-image-sdxl.md +139 -0
  14. package/docs/reference/local-whisper.md +219 -0
  15. package/docs/reference/native-speech.md +163 -0
  16. package/docs/reference/runtime-modules.md +16 -2
  17. package/docs/reference/sdk-api.md +335 -6
  18. package/package.json +5 -1
  19. package/runtime/arcane/components/speech.html +15 -3
  20. package/runtime/arcane/components/voice-transcription.html +19 -3
  21. package/runtime/arcane/modules/AI.js +250 -27
  22. package/runtime/arcane/modules/AIProviderRuntime.js +20 -3
  23. package/runtime/arcane/modules/AIRuntimeState.js +15 -2
  24. package/runtime/arcane/modules/ComponentContracts.js +3 -0
  25. package/schemas/arcane-app.schema.json +53 -2
  26. package/schemas/arcane-package.schema.json +53 -2
  27. package/src/core/repositories.mjs +210 -0
  28. package/src/core/services/speech.mjs +361 -0
  29. package/src/local-ai/config.mjs +9 -3
  30. package/src/local-ai/dev.mjs +32 -2
  31. package/src/local-ai/image-library.mjs +9 -0
  32. package/src/local-ai/image-models/flux.mjs +31 -0
  33. package/src/local-ai/image-models/sdxl.mjs +18 -0
  34. package/src/local-ai/install.mjs +26 -7
  35. package/src/local-ai/whisper/build.mjs +48 -0
  36. package/src/local-ai/whisper/config.mjs +61 -0
  37. package/src/local-ai/whisper/index.mjs +515 -0
  38. package/src/local-ai/whisper/install.mjs +274 -0
  39. package/src/local-ai/whisper/native/CMakeLists.txt +98 -0
  40. package/src/local-ai/whisper/native/NOTICE +18 -0
  41. package/src/local-ai/whisper/native/README.md +104 -0
  42. package/src/local-ai/whisper/native/main.cpp +489 -0
  43. package/src/native/portable-layout.mjs +37 -7
  44. package/src/native/windows-provider.mjs +39 -15
@@ -57,15 +57,22 @@ runtime layouts.
57
57
  | `arcane-os/core/runtime` | App-neutral native Core dispatcher and service lifecycle. |
58
58
  | `arcane-os/core/stdio` | Framed Node stdio transport with graceful runtime drain. |
59
59
  | `arcane-os/core/host` | Node-only reusable Core host lifecycle with explicitly supplied services. |
60
+ | `arcane-os/core/repositories` | Native persistent ArcaneData paths and explicitly selected connected working repositories. |
60
61
  | `arcane-os/core/packaged-web` | Core-owned packaged web listener with a persisted loopback origin and ready/failure events. |
61
62
  | `arcane-os/core/preferences` | Existing preference RPC methods with an application-selected file, ordered writes and owned drain. |
62
63
  | `arcane-os/core/diarization` | Lazy native speaker-diarization service, explicit stream methods and lifecycle/result events. |
63
64
  | `arcane-os/diarization` | Retained Nemotron model helper and independent caller-fed audio streams. |
64
65
  | `arcane-os/diarization/build` | First-party helper compilation against explicitly installed NeMo-Speech.cpp libraries. |
65
66
  | `arcane-os/core/local-ai` | Native llama.cpp, Ollama and ONNX Core service, model readiness, complete responses and owned lifecycle. See [local AI through Core](local-ai.md). |
66
- | `arcane-os/local-ai` | Official runtime installation and native artifact bundling for selected llama.cpp/Ollama/ONNX requirements and NeMo libraries. |
67
+ | `arcane-os/local-ai` | Official runtime installation and native artifact bundling for selected llama.cpp/Ollama/ONNX/image requirements and NeMo libraries. |
67
68
  | `arcane-os/local-ai/onnx` | Retained native ONNX worker sessions with explicit graph loading and complete tensor inference. |
68
69
  | `arcane-os/ai/core-onnx` | Optional browser access to system ONNX through an existing Core connection. |
70
+ | `arcane-os/local-ai/image` | Retained native stable-diffusion.cpp context, complete PNG generation and owned worker lifetime. |
71
+ | `arcane-os/ai/core-image` | Browser image lifecycle and complete PNG Blobs through an available Core service. |
72
+ | `arcane-os/core/image` | Native image Core service with independent runtime preparation and retained model ownership. |
73
+ | `arcane-os/core/speech` | Independent injected STT/TTS engine loading, request cancellation and joined Core service lifetime. |
74
+ | `arcane-os/ai/core-model-assets` | Complete stored browser model members projected through Core into native working files. |
75
+ | `arcane-os/core/model-assets` | Native working-file preparation, engine retain handles and joined cleanup. |
69
76
  | `arcane-os/integrated-provider` | Fixed integrated shared-development provider. |
70
77
  | `arcane-os/packager` | Low-level browser app packager. |
71
78
  | `arcane-os/release-bundle` | Deterministic external release bundles. |
@@ -386,6 +393,8 @@ The remaining data-only subpaths are eight JSON Schemas and package metadata.
386
393
  | `arcaneNativeBuilderProvider default export` | singleton | `arcane-os/native/portable-provider` | Portable native provider | Node; produces a portable payload rather than an executable host |
387
394
  | `startCoreHost()` | function | `arcane-os/core/host` | Native Core host composition | Node with readable input and writable output streams |
388
395
  | `readCoreLaunchContext()` | function | `arcane-os/core/host` | Native Core host composition | Node; reads explicitly selected launch JSON |
396
+ | `resolveArcaneDataPaths()` | function | `arcane-os/core/repositories` | Native repository workspaces | Node on Windows, Linux and macOS; Android supplies its data root |
397
+ | `createRepositoryWorkspace()` | function | `arcane-os/core/repositories` | Native repository workspaces | Node with Git; Android requires host process adaptation |
389
398
  | `createWindowsNativeProvider()` | function | `arcane-os/native/windows-provider` | Windows native provider | Node assembly; Windows x64/WebView2 execution |
390
399
  | `arcaneNativeBuilderProvider` | singleton | `arcane-os/native/windows-provider` | Windows native provider | Node assembly; Windows x64/WebView2 execution |
391
400
  | `arcaneNativeBuilderProvider default export` | singleton | `arcane-os/native/windows-provider` | Windows native provider | Node assembly; Windows x64/WebView2 execution |
@@ -403,6 +412,15 @@ The remaining data-only subpaths are eight JSON Schemas and package metadata.
403
412
  | `createLocalAIService()` | function | `arcane-os/core/local-ai` | Core local AI | Node with selected llama.cpp/Ollama/ONNX runtimes |
404
413
  | `createONNXRuntime()` | function | `arcane-os/local-ai/onnx` | Core local AI | Node with prepared Microsoft ONNX runtime |
405
414
  | `createCoreONNXRuntime()` | function | `arcane-os/ai/core-onnx` | Core local AI | Browser with an available Core ONNX service |
415
+ | `createImageRuntime()` | function | `arcane-os/local-ai/image` | Core local images | Node with a prepared native image runtime and caller-selected model |
416
+ | `createCoreImageRuntime()` | function | `arcane-os/ai/core-image` | Core local images | Browser with an available Core image service |
417
+ | `createLocalImageService()` | function | `arcane-os/core/image` | Core local images | Node Core with selected stable-diffusion.cpp requirements |
418
+ | `createLocalImageService default export` | function | `arcane-os/core/image` | Core local images | Same native image service factory |
419
+ | `createSpeechService()` | function | `arcane-os/core/speech` | Core native speech | Node Core with explicitly supplied speech engines |
420
+ | `createSpeechService default export` | function | `arcane-os/core/speech` | Core native speech | Same shared speech service factory |
421
+ | `prepareCoreModelAssets()` | function | `arcane-os/ai/core-model-assets` | Core model assets | Browser with complete Blob/File members and available Core service |
422
+ | `createModelAssetService()` | function | `arcane-os/core/model-assets` | Core model assets | Node Core with an application-selected working directory |
423
+ | `createModelAssetService default export` | function | `arcane-os/core/model-assets` | Core model assets | Same native working-file service factory |
406
424
  | `createLocalAIService default export` | function | `arcane-os/core/local-ai` | Core local AI | Same native service factory |
407
425
  | `ensureLocalAIRuntimes()` | function | `arcane-os/local-ai` | Core local AI | Node; selected upstream platform assets |
408
426
  | `bundleLocalAIRuntimes()` | function | `arcane-os/local-ai` | Core local AI | Node; complete native runtime assembly |
@@ -7409,12 +7427,23 @@ application builds. The build machine needs its normal Node/npm toolchain and
7409
7427
  The returned `arcane-native-builder/1` provider exposes `describe`, `doctor`,
7410
7428
  `prepare`, `build`, `verify` and `run`. Build returns `{app,target,manifest}`
7411
7429
  with the actual directory at `target.rootDir`. Verification reads the artifact;
7412
- it does not launch the window. Run launches `Arcane.exe` and observes complete
7413
- diagnostics and exit. Cancellation closes input and waits for host-owned drain,
7430
+ it does not launch the window. Run launches the executable recorded in the
7431
+ saved `manifest.host.executable` and observes complete diagnostics and exit.
7432
+ Cancellation closes input and waits for host-owned drain,
7414
7433
  including accepted service work, without a forced termination deadline. Missing
7415
7434
  prerequisites, download/extraction failures and host failures remain observable
7416
7435
  through the operation's error/event boundary.
7417
7436
 
7437
+ Assembly names the launcher from `appDescriptor.displayName` plus `.exe`,
7438
+ with a matching `.exe.config` file. Low-level compositions without a nonempty
7439
+ string display name use the app ID. Windows-reserved filename characters and
7440
+ control characters become underscores; reserved DOS device basenames receive
7441
+ an underscore prefix. Supported Unicode, spaces and case remain, as do the
7442
+ original descriptor and title. Icon embedding, file inventory, verification
7443
+ and run use this same name. Existing artifacts retain the executable recorded
7444
+ in their manifest; the reusable downloaded host retains `Arcane.exe` and
7445
+ `Arcane.exe.config`, and `runtime/ArcaneCore.exe` remains unchanged.
7446
+
7418
7447
  During assembly, `appDescriptor.native.icon` selects the app's existing image.
7419
7448
  PNG and ICO inputs produce a derived `runtime/arcane-app.ico` and update only
7420
7449
  the copied application's executable icon. Source assets, the selected host
@@ -7664,6 +7693,63 @@ separate argument; other composing hosts may use the same seam. This JavaScript
7664
7693
  contract does not establish a compiled host or matching release archive. See
7665
7694
  [launch-time locations](core-native-packaging.md#launch-time-locations).
7666
7695
 
7696
+ ## resolveArcaneDataPaths()
7697
+
7698
+ ### Overview
7699
+
7700
+ Resolves the native per-user `ArcaneData` directory and its `Repos` child without
7701
+ creating directories or changing application state.
7702
+
7703
+ ### Signature and result
7704
+
7705
+ ```text
7706
+ resolveArcaneDataPaths({dataRoot}={})
7707
+ ```
7708
+
7709
+ Returns absolute `{dataRoot,repositoriesRoot}`. Defaults use Windows LocalAppData,
7710
+ Linux XDG data home or `~/.local/share`, and macOS user Application Support.
7711
+ Android and other adapted hosts supply their persistent `dataRoot`. An explicit
7712
+ root is used directly, with relative paths resolved from the process working
7713
+ directory. See [native repository locations](core-repositories.md#persistent-locations).
7714
+
7715
+ ```javascript
7716
+ import {resolveArcaneDataPaths} from 'arcane-os/core/repositories';
7717
+
7718
+ const {repositoriesRoot} = resolveArcaneDataPaths();
7719
+ ```
7720
+
7721
+ ## createRepositoryWorkspace()
7722
+
7723
+ ### Overview
7724
+
7725
+ Owns one native connected working checkout under `ArcaneData/Repos/<name>` or an
7726
+ explicit existing path. The application owns the remote, branch and Core API.
7727
+
7728
+ ### Signature and result
7729
+
7730
+ ```text
7731
+ createRepositoryWorkspace({name,directory,dataRoot,remote,branch,onEvent,run=runProcess}={})
7732
+ ```
7733
+
7734
+ Returns `{directory,open,status,pull,push,close,drain,dispose}`. Construction does
7735
+ no I/O. Operations accept `{signal}`; a missing or empty destination clones once,
7736
+ while existing checkout files, branches, remotes and local changes remain intact.
7737
+ Same-directory calls are ordered within the process. Shutdown drains accepted
7738
+ work and preserves the checkout. No CLI cwd, launch state, preference, snapshot
7739
+ cache or existing user-data location changes. See the [complete inputs, results,
7740
+ errors and lifecycle](core-repositories.md#one-connected-working-checkout).
7741
+
7742
+ ```javascript
7743
+ import {createRepositoryWorkspace} from 'arcane-os/core/repositories';
7744
+
7745
+ const repository = createRepositoryWorkspace({
7746
+ name: 'moon-cheese-dispatches',
7747
+ remote: applicationConnection.remote
7748
+ });
7749
+ await repository.open({signal});
7750
+ await repository.close();
7751
+ ```
7752
+
7667
7753
  ## createCoreLocalAIProvider()
7668
7754
 
7669
7755
  ### Overview
@@ -7708,7 +7794,7 @@ separate states; prompts, models and application policy remain caller-owned.
7708
7794
  ### Signature and result
7709
7795
 
7710
7796
  ```text
7711
- createLocalAIService(configuration,{appRoot,runtimes=[],signal,onEvent}={})
7797
+ createLocalAIService(configuration,{appRoot,runtimes=[],signal,onEvent,prepare}={})
7712
7798
  ```
7713
7799
 
7714
7800
  Import it from `arcane-os/core/local-ai`. `configuration` is the authored local-AI
@@ -7718,6 +7804,13 @@ resolved installed runtime records. It returns a Core service definition with
7718
7804
  existing Core lifecycle. Shutdown cancels owned work and joins owned processes;
7719
7805
  an already running external service remains under its external owner.
7720
7806
 
7807
+ Optional `prepare({signal,onEvent})` supplies resolved runtime records during
7808
+ this service's startup. Only operations depending on that service wait for its
7809
+ preparation; the development listener and independently composed image/model
7810
+ asset services keep their own lifetimes. Pass only llama.cpp/Ollama/ONNX
7811
+ requirements to this factory; `createLocalImageService()` owns selected image
7812
+ requirements. The managed development and native entrypoints separate them.
7813
+
7721
7814
  The service provides `localai.status`, explicit `localai.services.recover`,
7722
7815
  `llama.status/models/load/unload/chat`, `onnx.status/load/run/unload`, and the documented native Ollama methods.
7723
7816
  `localai.state` reports runtime/model changes. `llama.chunk` and `ollama.chunk`
@@ -7761,6 +7854,231 @@ accessor's requests and subscriptions; unloading shared models is explicit.
7761
7854
  Construction installs no runtime and opens no transport. Existing browser
7762
7855
  ONNX/Wllama remain independent. See [Core and browser usage](local-ai.md#onnx-through-core-and-the-browser).
7763
7856
 
7857
+ ## createImageRuntime()
7858
+
7859
+ `createImageRuntime({libraryPath,bindingModulePath,variants=[],models=[],backend='auto',signal?,onEvent?})`
7860
+ from `arcane-os/local-ai/image` retains the native image owner for the selected
7861
+ library. Prepare the installed record through `ensureLocalAIRuntimes()` and
7862
+ supply complete model descriptors with actual native resource paths. The
7863
+ factory installs no software and starts its worker when an operation needs it.
7864
+
7865
+ It returns `load({model,context?,signal?})`, `generate({model,prompt,parameters?,
7866
+ signal?,onProgress?})`, `edit({model,image,prompt,strength,parameters?,signal?,onProgress?})`,
7867
+ `unload({signal?})`, `current()`,
7868
+ `subscribe(listener,{replay=true})` and asynchronous `close()`. A model ID uses
7869
+ its supplied descriptor; a complete descriptor may be passed directly.
7870
+ Generation returns all PNG results as `images: [{data,mediaType,width,height}]`
7871
+ with native result metadata. Prompts and parameters remain complete.
7872
+
7873
+ `edit()` accepts the original complete PNG as `image: {data: Uint8Array,
7874
+ mediaType}` and copies the supplied view at acceptance. Its explicit finite
7875
+ `strength` is from `0` through `1`, takes precedence over model/parameter
7876
+ defaults and is never clamped. It decodes in the existing worker and uses the
7877
+ same selected context, queue, progress, cancellation and complete result
7878
+ contract. SD1.4 advertises `txt2img` and `img2img`. See the
7879
+ [editing contract](local-image-editing.md#core-and-native-request) for supported
7880
+ PNG input, native preprocessing, alpha and strength-zero semantics; the
7881
+ original file remains unchanged.
7882
+
7883
+ Accessors for the same library share its worker and selected context. Competing
7884
+ native operations are ordered at that owner; unrelated services remain
7885
+ concurrent. State observation replays synchronously by default. Cancellation
7886
+ joins the owning native operation; a model constructor may finish before its
7887
+ new context can be released. Native accessor `close()` cancels its requests,
7888
+ joins them and releases the shared selected context before its working files
7889
+ can be retired. The final reference closes the worker. This differs from the
7890
+ browser accessor's detach-only ownership below. See the
7891
+ [image lifecycle, backend and error contract](local-image-generation.md).
7892
+
7893
+ ## createCoreImageRuntime()
7894
+
7895
+ `createCoreImageRuntime({client=getInstalledCoreClient(),signal?,onEvent?})`
7896
+ from `arcane-os/ai/core-image` returns immediately with `load`, `generate`, `edit`,
7897
+ `unload`, `inspect`, `current`, `subscribe` and `close`. It subscribes before
7898
+ requesting an initial status snapshot so newer lifecycle events win. It opens
7899
+ no connection and installs or selects no model.
7900
+
7901
+ `load({model,context?,assetProjectionId?,resourcePaths?,signal?})` and
7902
+ `generate({model,prompt,parameters?,assetProjectionId?,resourcePaths?,signal?,
7903
+ onProgress?})` use the selected Core service. Generation acknowledges with
7904
+ transient `Thinking` synchronously and returns complete
7905
+ `images: [{blob,mediaType,width,height}]` plus native result metadata.
7906
+ `unload({signal?})` explicitly releases the shared selected model;
7907
+ `inspect({signal?})` reads authoritative state without waiting for preparation.
7908
+ All request methods use `timeoutMs:0`. `current()` includes transient requests,
7909
+ status, busy and closed state; no prompt or image history is saved.
7910
+
7911
+ `edit({model,image,prompt,strength,parameters?,assetProjectionId?,resourcePaths?,
7912
+ signal?,onProgress?})` accepts the original PNG Blob or File and returns the
7913
+ same complete Blob result shape. It publishes `Thinking` before reading that
7914
+ input, preserves its complete encoded content through Core, and performs no
7915
+ browser drawing, re-encoding, resizing or saving. The request signal covers
7916
+ input preparation and native cancellation; an in-flight `Blob.arrayBuffer()`
7917
+ finishes before cancellation can be observed. No aborted result or late
7918
+ progress reaches the caller. See [full editing usage and format limits](local-image-editing.md).
7919
+
7920
+ `subscribe(listener,{replay=true,signal?})` returns an unsubscribe function.
7921
+ `close()` cancels and joins only this accessor's requests and subscriptions,
7922
+ leaving the shared Core client and selected model with their owners. Request
7923
+ errors retain complete details; an unavailable Core reports
7924
+ `ARCANE_IMAGE_CORE_UNAVAILABLE`. Aborted operations deliver no late result or
7925
+ progress. See the [complete browser example and events](local-image-generation.md#browser-accessor).
7926
+
7927
+ ## createLocalImageService()
7928
+
7929
+ `createLocalImageService(configuration,{appRoot,runtimes=[],signal?,onEvent?,prepare?,modelAssets?})`
7930
+ from `arcane-os/core/image` returns a Core service with `current()`, replaying
7931
+ `subscribe(listener)`, native preparation, request methods and joined disposal.
7932
+ `configuration` is the selected `localAI` record. Optional
7933
+ `prepare({appRoot,configuration,requirement,signal,onEvent})` returns one
7934
+ installed image-runtime record. Startup retains this preparation in the
7935
+ background; `image.status` and unrelated services remain responsive.
7936
+
7937
+ The service owns `image.status/load/generate/edit/unload` and
7938
+ `image.state/progress`. Pass the same `createModelAssetService()` instance that
7939
+ Core exposes when using `assetProjectionId` and `resourcePaths`. It retains
7940
+ prepared model files through actual native completion, replacement, unload and
7941
+ shutdown. PNG base64 is transport-only framing; the complete result is decoded
7942
+ by the browser accessor. See [parameters, results and ownership](local-image-generation.md#core-service-and-transport).
7943
+
7944
+ `image.edit` takes `{model,image,prompt,strength,parameters?,streamId?,
7945
+ assetProjectionId?,resourcePaths?}`. Only the Core JSON boundary encodes the
7946
+ input as `{data: completeBase64PNG,encoding:'base64',mediaType}`. The service
7947
+ acknowledges accepted work through `image.progress`, preserves the original
7948
+ prompt and complete image, and retains existing model-assets ownership through
7949
+ native completion. Its complete encoded PNG result has the same shape as
7950
+ `image.generate`; [editing semantics](local-image-editing.md) remain with the
7951
+ selected model and native engine.
7952
+
7953
+ ## createLocalImageService default export
7954
+
7955
+ The default export from `arcane-os/core/image` is the same
7956
+ `createLocalImageService` factory and lifecycle described above.
7957
+
7958
+ ## createSpeechService()
7959
+
7960
+ ### Overview and usage
7961
+
7962
+ `createSpeechService({stt,tts,signal}={})` from `arcane-os/core/speech` composes
7963
+ independently selected STT and TTS engines as one Core service. Supply either
7964
+ role or both. Construction downloads, installs and loads nothing; the host
7965
+ owns engine creation, model/device selection and supported formats.
7966
+
7967
+ ```javascript
7968
+ import {createCoreRuntime} from 'arcane-os/core/runtime';
7969
+ import {createSpeechService} from 'arcane-os/core/speech';
7970
+
7971
+ // transcriptionEngine is the host's already constructed, selected engine.
7972
+ const speech = createSpeechService({stt: transcriptionEngine});
7973
+ const core = createCoreRuntime({services: [speech]});
7974
+ core.start();
7975
+ ```
7976
+
7977
+ Each supplied engine exposes `current()`, replaying `subscribe(listener)`,
7978
+ `load({signal})`, `close()` and its role's
7979
+ `transcribe(request,{signal,onProgress})` or
7980
+ `synthesize(request,{signal,onProgress})`. The service forwards complete
7981
+ requests and results unchanged. Format decoding, model processing and encoding
7982
+ remain with that engine. This factory supplies no speech engine or automatic
7983
+ browser/native provider selection.
7984
+
7985
+ ### Members, results and events
7986
+
7987
+ The returned object has `name: 'speech'`, `current`, `subscribe`, `close`,
7988
+ `start`, `methods`, `drain` and `dispose`:
7989
+
7990
+ - `start(context)` subscribes and starts both configured engine loads
7991
+ independently, returning immediately. Core and status remain responsive
7992
+ during loading; one role's failure leaves the other usable.
7993
+ - `current()` and `methods['speech.status']()` return the same snapshot:
7994
+ `ready`, `transcriptionAvailable`, `synthesisAvailable`, `status`,
7995
+ `sttEngine`, `ttsEngine`, `roles.stt`, `roles.tts` and `closed`.
7996
+ `ready` requires both roles; each independent availability flag requires
7997
+ its model loaded in `ready` or `running` state while the service accepts work.
7998
+ `status` is `created`, `ok`, `closing` or `closed`; per-role state carries
7999
+ model loading, progress and complete errors. An omitted role is unavailable.
8000
+ - `subscribe(listener,{replay=true,signal}={})` replays current state by
8001
+ default and returns an unsubscribe function. Its signal owns only that
8002
+ subscription; a closed service can replay its final snapshot.
8003
+ - `methods['speech.transcribe'](request,context)` and
8004
+ `methods['speech.synthesize'](request,context)` are registered as the matching
8005
+ Core RPC methods. The context supplies `signal`, `requestId` and `emit`.
8006
+ The service publishes synchronous accepted `Thinking` before waiting for
8007
+ its role's load, then forwards the engine's complete progress and result.
8008
+ - `close()` and its `drain()` alias stop acceptance, abort the service lifetime,
8009
+ close both engines concurrently and await startup and request work. Repeated
8010
+ calls share one promise. `dispose()` also releases event subscriptions.
8011
+
8012
+ `speech.state` carries the complete snapshot. `speech.progress` carries
8013
+ `{requestId,role,status:'Thinking',progress}` with initial
8014
+ `progress:{phase:'accepted'}`. The service's subscription replays state; Core's
8015
+ generic runtime replay does not replay this custom event. Browser consumers
8016
+ subscribe before requesting `Arcane.speech.status()` and preserve any newer
8017
+ state event received while that request is pending.
8018
+
8019
+ ### Cancellation, errors and availability
8020
+
8021
+ The optional factory signal owns the service lifetime. A request signal is
8022
+ combined with that lifetime and reaches its selected engine. Cancelling one
8023
+ request waiting for a shared load settles that request without cancelling
8024
+ the other requests or role. No later cancelled progress or result is delivered.
8025
+ Actual queued/running native interruption belongs to the engine; it must settle
8026
+ before Core reaches service shutdown, because Core first cancels and awaits
8027
+ active requests.
8028
+
8029
+ Invalid engine interfaces or listeners throw `TypeError`. Missing roles reject
8030
+ their operation with `SPEECH_ENGINE_UNAVAILABLE`; work after shutdown begins
8031
+ rejects with `CORE_CLOSING`. Engine errors remain observable, and engine cleanup
8032
+ failures reject shutdown with `AggregateError`. Asynchronous listener failures
8033
+ reach developer diagnostics. Engine platform support remains its own contract;
8034
+ this Node composition layer adds no engine dependency. Existing AI readiness
8035
+ consumers recognize `whisper.cpp` and `kokoro-onnx` IDs independently of the
8036
+ service's engine-neutral interface. See the [complete engine and lifetime
8037
+ contract](native-speech.md).
8038
+
8039
+ ## createSpeechService default export
8040
+
8041
+ The default export from `arcane-os/core/speech` is the same
8042
+ `createSpeechService` factory, with identical inputs, results and lifecycle.
8043
+
8044
+ ## prepareCoreModelAssets()
8045
+
8046
+ `prepareCoreModelAssets({client=getInstalledCoreClient(),workingDirectory,members,signal?,onProgress?})`
8047
+ from `arcane-os/ai/core-model-assets` projects ordered complete
8048
+ `members: [{path,file}]` through an available Core connection. Each `file` is a
8049
+ Blob or File. It returns `{id,directory,members:[{path,nativePath}],release}`
8050
+ after all members finish writing and close. Independent members transfer
8051
+ concurrently; each member's writes remain ordered.
8052
+
8053
+ The caller releases preparation ownership with asynchronous idempotent
8054
+ `release()`. Native engines acquire their own retain handle before loading,
8055
+ and files remain until their actual native use ends. Cancellation and failure
8056
+ join transfers and uncancelled cleanup, preserving complete original errors.
8057
+ The helper downloads no model, changes no DBOPFS original and selects no
8058
+ engine. See [complete preparation and cleanup usage](model-assets.md).
8059
+
8060
+ ## createModelAssetService()
8061
+
8062
+ `createModelAssetService({appRoot=process.cwd()}={})` from
8063
+ `arcane-os/core/model-assets` returns the Core service for
8064
+ `modelAssets.open/write/complete/release/status` and `modelAssets.state`.
8065
+ Relative working directories resolve from `appRoot`; explicit absolute
8066
+ directories remain caller-selected. Each projection has its own child
8067
+ directory and complete member files.
8068
+
8069
+ `current()` returns `{closing,projections}`. `retain(id)` accepts a completed
8070
+ projection and returns `{id,directory,members,release}` for a native owner.
8071
+ `dispose()` releases preparation ownership and joins file writes, closes and
8072
+ all native retain lifetimes before cleanup. The native owner releases its
8073
+ handle only after its actual context/worker use ends. Original stored model
8074
+ files remain with their application owner. See
8075
+ [working-file service and wire operations](model-assets.md#native-ownership).
8076
+
8077
+ ## createModelAssetService default export
8078
+
8079
+ The default export from `arcane-os/core/model-assets` is the same
8080
+ `createModelAssetService` factory and lifecycle described above.
8081
+
7764
8082
  ## createLocalAIService default export
7765
8083
 
7766
8084
  The default binding from `arcane-os/core/local-ai` is exactly the named
@@ -7776,7 +8094,7 @@ import createLocalAIService from 'arcane-os/core/local-ai';
7776
8094
  ### Overview
7777
8095
 
7778
8096
  Reuses matching managed runtimes or installs complete selected official
7779
- llama.cpp/Ollama/ONNX distributions or NeMo libraries in an explicitly supplied directory.
8097
+ llama.cpp/Ollama/ONNX/image distributions or NeMo libraries in an explicitly supplied directory.
7780
8098
 
7781
8099
  ### Signature and result
7782
8100
 
@@ -7787,7 +8105,11 @@ async ensureLocalAIRuntimes({runtimes=[],directory,platform=process.platform,arc
7787
8105
  Import it from `arcane-os/local-ai`. Runtime requirements accept IDs or
7788
8106
  `{id,version?,url?}` records. The result is an array of absolute
7789
8107
  `{id,version,platform,architecture,root}` records, with `executable` for servers,
7790
- `modulePath` for ONNX, or NeMo library directories. Matching concurrent
8108
+ `modulePath` for ONNX, image `libraryPath`/`bindingModulePath`/`variants`/`models`,
8109
+ or NeMo library directories. Image requirements also accept `backend` and
8110
+ `models`; their pinned default runtime, CPU/Metal selection and separate model
8111
+ preparation are described in [local image generation](local-image-generation.md).
8112
+ Matching concurrent
7791
8113
  installs share their work. Downloads retain runtime libraries and surface
7792
8114
  network/archive failures; cancellation and operation events remain observable.
7793
8115
  Installation changes no global PATH or system service and loads no model.
@@ -7825,6 +8147,13 @@ contains the complete emitted inventory. Existing runtime destinations are
7825
8147
  preserved and reported as a conflict. Copying settles all started work and
7826
8148
  reports failures/cancellation without executing app services or models.
7827
8149
 
8150
+ Image records relocate the complete library, binding and variant paths. Model
8151
+ URL descriptors retain their original acquisition metadata for preparation
8152
+ after launch. Runtime-contained native model paths become artifact-relative;
8153
+ external working paths report their portability incompatibility rather than
8154
+ embedding this machine's path. Use the application-owned native-resource or
8155
+ post-launch model-assets workflow for those files.
8156
+
7828
8157
  ```javascript
7829
8158
  import {bundleLocalAIRuntimes} from 'arcane-os/local-ai';
7830
8159
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.62.0",
3
+ "version": "0.64.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",
@@ -61,17 +61,21 @@
61
61
  "./core/runtime": "./src/core/runtime.mjs",
62
62
  "./core/stdio": "./src/core/stdio.mjs",
63
63
  "./core/host": "./src/core/host.mjs",
64
+ "./core/repositories": "./src/core/repositories.mjs",
64
65
  "./core/packaged-web": "./src/core/services/packaged-web.mjs",
65
66
  "./core/preferences": "./src/core/services/preferences.mjs",
66
67
  "./core/diarization": "./src/diarization/service.mjs",
67
68
  "./core/local-ai": "./src/core/services/local-ai.mjs",
68
69
  "./core/image": "./src/core/services/image.mjs",
70
+ "./core/speech": "./src/core/services/speech.mjs",
69
71
  "./core/model-assets": "./src/core/services/model-assets.mjs",
70
72
  "./diarization": "./src/diarization/index.mjs",
71
73
  "./diarization/build": "./src/diarization/build.mjs",
72
74
  "./local-ai": "./src/local-ai/install.mjs",
73
75
  "./local-ai/onnx": "./src/local-ai/onnx.mjs",
74
76
  "./local-ai/image": "./src/local-ai/image-runtime.mjs",
77
+ "./local-ai/whisper": "./src/local-ai/whisper/index.mjs",
78
+ "./local-ai/whisper/build": "./src/local-ai/whisper/build.mjs",
75
79
  "./integrated-provider": "./src/integrated-provider-loader.mjs",
76
80
  "./packager": "./src/packager/core.mjs",
77
81
  "./release-bundle": "./src/release-bundle.mjs",
@@ -295,6 +295,7 @@
295
295
  const aiModuleImport = import('../modules/AI.js');
296
296
  const providerRuntimeImport = import('../modules/AIProviderRuntime.js');
297
297
  const {
298
+ continuesAIRuntimeOperation,
298
299
  requestAIRuntimeIntent,
299
300
  subscribeAIRuntimeState
300
301
  } = await import('../modules/AIRuntimeState.js');
@@ -366,6 +367,7 @@
366
367
  let ttsIntentGeneration = 0;
367
368
  let eventOperationSequence = 0;
368
369
  let transcriptionOperationId = null;
370
+ let transcriptionRuntimeOperationId = null;
369
371
  let ttsOperationId = null;
370
372
  let destroyed = false;
371
373
  let recognitionLanguage = is.string(host.recognitionLanguage)
@@ -826,8 +828,10 @@
826
828
 
827
829
  function applyRoleTransitions(previousSTTRole, previousTTSRole) {
828
830
  sttActivationController?.synchronize(sttRole);
829
- const sttBecameUnready = previousSTTRole.state === 'ready'
830
- && sttRole.state !== 'ready';
831
+ const continuingTranscription = transcriptionActive && !nativeCaptureSession
832
+ && continuesAIRuntimeOperation(previousSTTRole, sttRole, transcriptionRuntimeOperationId);
833
+ const sttBecameUnready = ['ready', 'recovering'].includes(previousSTTRole.state)
834
+ && sttRole.state !== 'ready' && !continuingTranscription;
831
835
  const ttsBecameUnready = previousTTSRole.state === 'ready'
832
836
  && ttsRole.state !== 'ready';
833
837
 
@@ -2011,10 +2015,16 @@
2011
2015
  throw error;
2012
2016
  }
2013
2017
 
2014
- const transcription = await aiRuntime.fetchSTT(
2018
+ const previousRuntimeOperationId = providerRuntime.status('stt').operationId;
2019
+ const pendingTranscription = aiRuntime.fetchSTT(
2015
2020
  audioFile,
2016
2021
  controller.signal
2017
2022
  );
2023
+ const activeRole = providerRuntime.status('stt');
2024
+ transcriptionRuntimeOperationId = activeRole.busy
2025
+ && activeRole.operationId !== previousRuntimeOperationId
2026
+ ? activeRole.operationId : null;
2027
+ const transcription = await pendingTranscription;
2018
2028
  if (
2019
2029
  generation !== transcriptionGeneration
2020
2030
  || sttRole.state !== 'ready'
@@ -2044,6 +2054,7 @@
2044
2054
  } finally {
2045
2055
  if (transcriptionAbortController === controller) {
2046
2056
  transcriptionAbortController = null;
2057
+ transcriptionRuntimeOperationId = null;
2047
2058
  }
2048
2059
  if (generation === transcriptionGeneration) {
2049
2060
  transcriptionActive = false;
@@ -2144,6 +2155,7 @@
2144
2155
  transcriptionGeneration += 1;
2145
2156
  transcriptionAbortController?.abort();
2146
2157
  transcriptionAbortController = null;
2158
+ transcriptionRuntimeOperationId = null;
2147
2159
  recordingRequested = false;
2148
2160
  transcriptionActive = false;
2149
2161
  clearSilenceTimer();
@@ -135,7 +135,7 @@
135
135
  const [
136
136
  {default:MD},
137
137
  {appendTranscription,createSTTActivationController,normalizeVoiceOptions},
138
- {requestAIRuntimeIntent,subscribeAIRuntimeState},
138
+ {continuesAIRuntimeOperation,requestAIRuntimeIntent,subscribeAIRuntimeState},
139
139
  {default:ContinuousVoiceCapture},
140
140
  {getAIProviderRuntime}
141
141
  ]=await Promise.all([
@@ -165,6 +165,7 @@
165
165
  let recorder=null;
166
166
  let state='idle';
167
167
  let stateMessage='';
168
+ let transcriptionRuntimeOperationId=null;
168
169
  let sttRole=unavailableRole('stt');
169
170
  let transcript='';
170
171
  let sessionGeneration=0;
@@ -1121,7 +1122,17 @@
1121
1122
  error.code='ARCANE_VOICE_TRANSCRIPTION_STT_RUNTIME_METHOD_UNAVAILABLE';
1122
1123
  throw error;
1123
1124
  }
1124
- return globalThis.ai.fetchSTT(file,signal);
1125
+ const previousOperationId=providerRuntime.status('stt').operationId;
1126
+ const pending=globalThis.ai.fetchSTT(file,signal);
1127
+ const activeRole=providerRuntime.status('stt');
1128
+ const operationId=activeRole.busy&&activeRole.operationId!==previousOperationId
1129
+ ?activeRole.operationId:null;
1130
+ transcriptionRuntimeOperationId=operationId;
1131
+ try{
1132
+ return await pending;
1133
+ }finally{
1134
+ if(transcriptionRuntimeOperationId===operationId)transcriptionRuntimeOperationId=null;
1135
+ }
1125
1136
  }
1126
1137
 
1127
1138
  function isTranscriptionCancellation(error,controller){
@@ -1417,6 +1428,9 @@
1417
1428
  function synchronizeAIRuntimeState(snapshot){
1418
1429
  const previousRole=sttRole;
1419
1430
  sttRole=snapshot.roles.stt;
1431
+ const continuingTranscription=continuesAIRuntimeOperation(
1432
+ previousRole,sttRole,transcriptionRuntimeOperationId
1433
+ );
1420
1434
  if(sttRole.state==='error'&&previousRole.state!=='error'){
1421
1435
  arcaneLogging.error('Arcane transcription runtime failed.',sttRole.error);
1422
1436
  }
@@ -1438,6 +1452,7 @@
1438
1452
  return;
1439
1453
  }
1440
1454
  if(session.processing==='transcribing'
1455
+ &&!continuingTranscription
1441
1456
  &&(sttRole.state!=='ready'
1442
1457
  ||previousRole.providerId!==sttRole.providerId
1443
1458
  ||previousRole.modelId!==sttRole.modelId)){
@@ -1447,7 +1462,8 @@
1447
1462
  renderContinuousState(session);
1448
1463
  return;
1449
1464
  }
1450
- if(previousRole.state==='ready'&&sttRole.state!=='ready'){
1465
+ if(['ready','recovering'].includes(previousRole.state)
1466
+ &&sttRole.state!=='ready'&&!continuingTranscription){
1451
1467
  if(cancelSTTOperation('runtime-unready')){
1452
1468
  return;
1453
1469
  }