arcane-os 0.66.0 → 0.68.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 (38) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +1 -1
  3. package/browser-runtime/ai/core-image.mjs +84 -34
  4. package/browser-runtime/ai/core-local.mjs +195 -43
  5. package/browser-runtime/ai/core-onnx.mjs +71 -12
  6. package/docs/reference/README.md +6 -0
  7. package/docs/reference/availability-and-normalization.md +1 -0
  8. package/docs/reference/core-native-packaging.md +11 -4
  9. package/docs/reference/core-repositories.md +93 -9
  10. package/docs/reference/core-runtime.md +46 -5
  11. package/docs/reference/inventory/package-api.json +62 -20
  12. package/docs/reference/inventory/runtime-components.json +2 -1
  13. package/docs/reference/inventory/runtime-modules.json +3 -3
  14. package/docs/reference/local-ai.md +112 -2
  15. package/docs/reference/local-image-generation.md +15 -4
  16. package/docs/reference/model-assets.md +50 -3
  17. package/docs/reference/native-decisions.md +181 -0
  18. package/docs/reference/runtime-components.md +62 -5
  19. package/docs/reference/runtime-modules.md +44 -7
  20. package/docs/reference/sdk-api.md +229 -19
  21. package/package.json +5 -2
  22. package/runtime/arcane/components/theme-switcher.html +112 -13
  23. package/runtime/arcane/modules/PreferenceStore.js +18 -13
  24. package/runtime/arcane/modules/SystemAppearance.js +24 -6
  25. package/src/core/repositories.mjs +27 -8
  26. package/src/core/runtime.mjs +26 -1
  27. package/src/core/services/decisions.mjs +144 -0
  28. package/src/core/services/local-ai.mjs +31 -9
  29. package/src/core/services/model-assets.mjs +98 -13
  30. package/src/git-identity.mjs +22 -58
  31. package/src/local-ai/decision-tokenizer-worker.mjs +31 -0
  32. package/src/local-ai/decision-tokenizer.mjs +109 -0
  33. package/src/local-ai/decisions.mjs +273 -0
  34. package/src/local-ai/dev.mjs +8 -2
  35. package/src/local-ai/onnx-worker.mjs +67 -7
  36. package/src/local-ai/onnx.mjs +23 -12
  37. package/src/native/portable-layout.mjs +8 -3
  38. package/src/repository.mjs +144 -4
@@ -69,6 +69,8 @@ runtime layouts.
69
69
  | `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). |
70
70
  | `arcane-os/local-ai` | Official runtime installation and native artifact bundling for selected llama.cpp/Ollama/ONNX/image requirements and NeMo libraries. |
71
71
  | `arcane-os/local-ai/onnx` | Retained native ONNX worker sessions with explicit graph loading and complete tensor inference. |
72
+ | `arcane-os/local-ai/decisions` | Explicit native Laya FP32 decisions using the existing ONNX owner and a tokenizer Worker. |
73
+ | `arcane-os/core/decisions` | Renderer-independent typed-decision service composed with existing local-AI and model-assets owners. |
72
74
  | `arcane-os/ai/core-onnx` | Optional browser access to system ONNX through an existing Core connection. |
73
75
  | `arcane-os/local-ai/image` | Retained native stable-diffusion.cpp context, complete PNG generation and owned worker lifetime. |
74
76
  | `arcane-os/ai/core-image` | Browser image lifecycle and complete PNG Blobs through an available Core service. |
@@ -108,7 +110,7 @@ runtime layouts.
108
110
  | `arcane-os/pwa` | Nonblocking PWA registration, worker updates, native installation state and a dismissible installation component. |
109
111
  | `arcane-os/ai/browser-wasm` | Caller-selected browser-local Wllama inference, complete DBOPFS model storage, streaming, cancellation, and structural tool-call results. |
110
112
  | `arcane-os/ai/core-local` | Browser llama.cpp provider through an available Core connection; browser-local Wllama and ONNX remain independently available. |
111
- | `arcane-os/ai/browser-decisions` | Explicit-load Laya FP16 and Julia-1 FP32 typed decisions through the shared Transformers.js Worker owner. |
113
+ | `arcane-os/ai/browser-decisions` | Explicit-load Laya and Julia-1 typed decisions with caller-selected precision through the shared Transformers.js Worker owner. |
112
114
  | `arcane-os/ai/tool-text-stream` | Shared selected tool-argument text observer for provider integration. |
113
115
  | `arcane-os/ai/twin-cloud` | Stateless chat, System One state/questions and image generation with explicit key/model, complete results and operation-specific retry/cancellation. |
114
116
  | `arcane-os/ai/browser-speech` | Caller-selected Whisper/Kokoro, native browser recognition/synthesis, cloud TTS, upstream assets, role lifecycle and cancellation. |
@@ -169,6 +171,9 @@ The remaining data-only subpaths are eight JSON Schemas and package metadata.
169
171
  | `acceptWebSocket()` | function | `arcane-os`, `arcane-os/websocket` | WebSocket protocol | Node; browser-native client interoperability |
170
172
  | `WS` | class | `arcane-os`, `arcane-os/websocket-client` | Shared WebSocket clients | Node and native-WebSocket browser hosts |
171
173
  | `createBrowserDecisionModel()` | function | `arcane-os/ai/browser-decisions` | Browser typed decisions | Browser module Workers and the caller-selected inference backend |
174
+ | `createNativeDecisionModel()` | function | `arcane-os/local-ai/decisions` | Native typed decisions | Node with an existing native ONNX owner and selected Laya FP32 files |
175
+ | `createNativeDecisionService()` | function | `arcane-os/core/decisions` | Native typed decisions | Node Core with existing local-AI and model-assets services |
176
+ | `createNativeDecisionService default export` | function | `arcane-os/core/decisions` | Native typed decisions | Same native decision service factory |
172
177
  | `fetchRequest()` | function | `arcane-os/ai/twin-cloud` | TWiN Cloud requests | Node and Browser; remote HTTPS provider |
173
178
  | `fetchSystemOneRequest()` | function | `arcane-os/ai/twin-cloud` | TWiN Cloud state-and-questions requests | Node and Browser; remote HTTPS provider |
174
179
  | `generateImages()` | function | `arcane-os/ai/twin-cloud` | TWiN Cloud image generation | Node and Browser with Fetch and Blob; remote HTTPS provider |
@@ -3742,11 +3747,19 @@ Runs one fast-forward-only pull after proving the selected repository worktree i
3742
3747
  ### Signature and result
3743
3748
 
3744
3749
  ```text
3745
- async repositoryPull({ workspaceRoot=process.cwd(), signal, onEvent, run=runProcess }={})
3750
+ async repositoryPull({ workspaceRoot=process.cwd(), target, signal, onEvent, run=runProcess }={})
3746
3751
  ```
3747
3752
 
3748
3753
  Import it from `arcane-os`. The signature above states whether settlement is synchronous or promise-based. The overview and owning group define result authority, side effects, callbacks, events, cancellation, and lifecycle.
3749
3754
 
3755
+ Optional `target:{remote,ref}` captures exact nonempty native-representable
3756
+ strings before any wait and selects `git pull --ff-only -- <remote> <ref>:`.
3757
+ The current clean branch receives a fast-forward-only integration; the SDK
3758
+ never switches branches, resets or changes configuration. Without a target,
3759
+ the existing configured pull and result remain unchanged. Targeted results
3760
+ add `target`, complete `stdout` and `stderr` to the existing pull result.
3761
+ See [checkout configuration and selected targets](core-repositories.md#existing-checkout-configuration-and-selected-targets).
3762
+
3750
3763
  ### Availability and normalization
3751
3764
 
3752
3765
  **Node; network-assisted Git.** SDK-normalized inputs, errors, events, and documented result. Deep protocol: [Node ESM](protocols.md).
@@ -3770,7 +3783,7 @@ Pushes the selected attached branch through the repository's configured remote a
3770
3783
  ### Signature, parameters, and result
3771
3784
 
3772
3785
  ```text
3773
- async repositoryPush({ workspaceRoot=process.cwd(), signal, onEvent, run=runProcess }={})
3786
+ async repositoryPush({ workspaceRoot=process.cwd(), target, signal, onEvent, run=runProcess }={})
3774
3787
  ```
3775
3788
 
3776
3789
  Import it from `arcane-os`. `workspaceRoot` selects the Git worktree. `signal`
@@ -3781,10 +3794,16 @@ should keep the default.
3781
3794
 
3782
3795
  Before pushing, the SDK runs `git rev-parse --show-toplevel`,
3783
3796
  `git branch --show-current`, and `git status --short --branch` in order. It rejects a
3784
- detached HEAD, then runs plain `git push`; the repository's current branch,
3797
+ detached HEAD, then, without `target`, runs plain `git push`; the repository's current branch,
3785
3798
  configured upstream/remote, Git credentials, hooks, and server policy remain
3786
3799
  authoritative. Unlike `repositoryPull()`, this function does **not** require a
3787
- clean worktree. It does not create a commit or select a remote/refspec for you.
3800
+ clean worktree. It does not create a commit or choose an application destination.
3801
+ Optional `target:{remote,ref}` captures the original nonempty
3802
+ native-representable strings before any wait and runs
3803
+ `git push --no-follow-tags -- <remote> HEAD:<ref>`. This publishes current HEAD
3804
+ to the caller-selected ref without force or implicit configured tag following.
3805
+ It changes no branch or Git configuration; credentials, URL interpretation,
3806
+ hooks and remote rejection remain Git-owned.
3788
3807
 
3789
3808
  The promise resolves to
3790
3809
  `{action:'push', repositoryRoot, branch, output}`. `output` is trimmed stdout,
@@ -3792,6 +3811,9 @@ or trimmed stderr when stdout is empty. A missing Git executable, failed status
3792
3811
  probe, rejected/nonzero push, cancellation, or event-callback failure rejects
3793
3812
  with the normalized process error. Cancellation stops the local process tree;
3794
3813
  it cannot prove that a remote accepted no objects before the interruption.
3814
+ Targeted results also contain the captured `target` and complete
3815
+ `stdout`/`stderr`; omitting the target preserves the existing result shape.
3816
+ See [checkout configuration and selected targets](core-repositories.md#existing-checkout-configuration-and-selected-targets).
3795
3817
 
3796
3818
  ### Availability and normalization
3797
3819
 
@@ -7280,9 +7302,18 @@ createCoreRuntime({application, version, services=[]}={})
7280
7302
  ```
7281
7303
 
7282
7304
  Import it from `arcane-os/core/runtime`. The runtime exposes `current()`,
7283
- `subscribe()`, `onFrame()`, `emit()`, `registerService()`, `start()`, `handle()`
7305
+ `subscribe()`, `onFrame()`, `emit()`, `registerService()`, `getService()`, `start()`, `handle()`
7284
7306
  and idempotent `close()`.
7285
7307
 
7308
+ `await runtime.getService(name)` and service `context.getService(name)` return
7309
+ the actual registered service after its shared startup. They wait for that
7310
+ dependency alone, may start it before `runtime.start()`, and add no renderer or
7311
+ all-service barrier. Unknown services reject with `CORE_SERVICE_UNAVAILABLE`;
7312
+ new lookups during shutdown reject with `CORE_CLOSING`. Startup failures retain
7313
+ their original error. This is readiness, not a lifetime lease: retain acquired
7314
+ native handles for their actual cleanup rather than looking them up during
7315
+ disposal. See [native service composition](core-runtime.md#native-service-composition).
7316
+
7286
7317
  Services start independently. Request-lifetime work receives cooperative
7287
7318
  cancellation; accepted `lifetime:'service'` work survives renderer cancellation.
7288
7319
  Shutdown stops admission, waits accepted responses, then runs each service's
@@ -7894,8 +7925,8 @@ explicit existing path. The application owns the remote, branch and Core API.
7894
7925
  createRepositoryWorkspace({name,directory,dataRoot,remote,branch,gitIdentity,onEvent,run=runProcess}={})
7895
7926
  ```
7896
7927
 
7897
- Returns `{directory,open,status,pull,push,write,close,drain,dispose}`. Construction does
7898
- no I/O. Operations accept `{signal}`; a missing or empty destination clones once,
7928
+ Returns `{directory,open,status,configuration,pull,push,write,close,drain,dispose}`. Construction does
7929
+ no I/O. Preparing operations accept `{signal}`; a missing or empty destination clones once,
7899
7930
  while existing checkout files, branches, remotes and local changes remain intact.
7900
7931
  Same-directory calls are ordered within the process. Shutdown drains accepted
7901
7932
  work and preserves the checkout. No CLI cwd, launch state, preference, snapshot
@@ -7908,7 +7939,25 @@ Selected author fields override inherited child author/committer fields;
7908
7939
  omitted fields remain inherited. Username remains a credential hint, with
7909
7940
  authentication owned by ordinary Git and its configured helper.
7910
7941
 
7911
- `write({files:[{path,content}],message,signal})` snapshots the exact selected
7942
+ `configuration({signal})` observes an existing working root without clone,
7943
+ initialization or fetch. It returns `{repositoryRoot,headRef,origin,upstream}`:
7944
+ full symbolic HEAD (including unborn, null when detached), origin URL/pushURL
7945
+ arrays and current branch remote/merge/URL/pushURL arrays. Unset, empty and
7946
+ repeated configuration values remain distinct. These are ordinary configured
7947
+ values, not authentication or endpoint proof, and are not atomic with later
7948
+ operations. Raw stdout is decoded without replacement; unrepresentable text
7949
+ reports `ARCANE_GIT_CONFIGURATION_NOT_TEXT` with full diagnostics.
7950
+
7951
+ `pull({target,signal})`, `push({target,signal})` and
7952
+ `write({files:[{path,content}],message,target,signal})` accept optional
7953
+ `target:{remote,ref}`, captured as original strings at acceptance. The target
7954
+ selects a fast-forward-only pull into the current branch or a non-force push
7955
+ of current HEAD to the selected ref, without switching branches or changing
7956
+ configuration. It is independent of initial-clone `remote`/`branch`; omission
7957
+ preserves ordinary configured behavior. See the [complete configuration and
7958
+ target contract](core-repositories.md#existing-checkout-configuration-and-selected-targets).
7959
+
7960
+ `write` snapshots the exact selected
7912
7961
  strings, writes those repository-relative files, commits only their literal
7913
7962
  paths and performs an ordinary non-force push. It retains unrelated staged
7914
7963
  paths and existing Git filters/hooks. Results and causal failures distinguish
@@ -7976,7 +8025,7 @@ Construction selects no model and installs no runtime or transport.
7976
8025
  ### Signature and result
7977
8026
 
7978
8027
  ```text
7979
- createCoreLocalAIProvider({client=getInstalledCoreClient(),id='llama.cpp'}={})
8028
+ createCoreLocalAIProvider({client,id='llama.cpp'}={})
7980
8029
  ```
7981
8030
 
7982
8031
  Import it from `arcane-os/ai/core-local`. The provider implements
@@ -7988,6 +8037,16 @@ payloads and parsed streaming chunks, and cancel on observed readiness loss.
7988
8037
  Missing Core reports unavailability; it does not choose a browser or cloud route.
7989
8038
  Dispose/unregister at the caller's owning lifecycle boundary.
7990
8039
 
8040
+ Omitted `client` follows late installation, replacement and retirement through
8041
+ the shared Core installation subscription. An explicit client, including
8042
+ `null`, remains fixed. Replacement revokes readiness and cancels owned work;
8043
+ late results cannot restore it. `inspect()` distinguishes explicit load
8044
+ capability from loaded readiness, while `status()` remains observational.
8045
+ Disposal releases only an actually retained selection through its captured
8046
+ client, so never-loaded and already-unloaded providers send no native unload.
8047
+ The provider/2 runtime reconciles status at its operation boundaries; this
8048
+ does not add an idle role-state subscription.
8049
+
7991
8050
  ```javascript
7992
8051
  import {createCoreLocalAIProvider} from 'arcane-os/ai/core-local';
7993
8052
  import {getAIProviderRuntime} from 'arcane-os/ai-provider-runtime';
@@ -8020,6 +8079,13 @@ resolved installed runtime records. It returns a Core service definition with
8020
8079
  existing Core lifecycle. Shutdown cancels owned work and joins owned processes;
8021
8080
  an already running external service remains under its external owner.
8022
8081
 
8082
+ After `await context.getService('local-ai')`, `service.getONNXRuntime()` returns
8083
+ that service's actual prepared ONNX owner. It creates no engine and rejects
8084
+ unavailable or closing access. Reacquire on each explicit load after recovery;
8085
+ retain the returned handle for cleanup. Core shutdown cancels request-lifetime
8086
+ work, and independent services dispose concurrently. See
8087
+ [native service owners](local-ai.md#native-service-owners).
8088
+
8023
8089
  Optional `prepare({signal,onEvent})` supplies resolved runtime records during
8024
8090
  this service's startup. Only operations depending on that service wait for its
8025
8091
  preparation; the development listener and independently composed image/model
@@ -8029,7 +8095,10 @@ requirements. The managed development and native entrypoints separate them.
8029
8095
 
8030
8096
  The service provides `localai.status`, explicit `localai.services.recover`,
8031
8097
  `llama.status/models/load/unload/chat`, `onnx.status/load/run/unload`, and the documented native Ollama methods.
8032
- `localai.state` reports runtime/model changes. `llama.chunk` and `ollama.chunk`
8098
+ `localai.state` reports runtime/model changes. `llama.status` and `llama.models`
8099
+ observe current state without starting, restarting or waiting for an engine.
8100
+ Explicit load retains startup and exact catalog-membership checks.
8101
+ `llama.chunk` and `ollama.chunk`
8033
8102
  carry original parsed chunks with `streamId`; terminal responses remain complete.
8034
8103
  There is no background health polling or implicit model choice.
8035
8104
 
@@ -8046,7 +8115,7 @@ and [native bundling](local-ai.md#native-bundling).
8046
8115
 
8047
8116
  `createONNXRuntime({modulePath,signal?,onEvent?})` from `arcane-os/local-ai/onnx`
8048
8117
  owns retained native ONNX sessions in workers. It exposes `load({id,model,
8049
- sessionOptions?,signal?})`, `run({id,feeds,fetches?,runOptions?,signal?})`,
8118
+ sessionOptions?,executionPreference?,signal?})`, `run({id,feeds,fetches?,runOptions?,signal?})`,
8050
8119
  `unload({id,signal?})`, `current()`, replaying `subscribe(listener)` and `close()`.
8051
8120
  The direct model path is absolute. Complete typed tensor records use
8052
8121
  `{type,dims,data}`; callers own the actual graph's inputs and preprocessing.
@@ -8056,11 +8125,22 @@ before replacement. Independent sessions run concurrently; same-session work
8056
8125
  waits at that session's loading/execution boundary. Queued cancellation removes
8057
8126
  that request; active cancellation retires the affected worker and suppresses
8058
8127
  its response. Native code can delay termination; cleanup waits for actual exit.
8128
+ `current().sessions[].stopping` revokes new-run availability at terminal
8129
+ shutdown; `exited` records actual Worker exit. A recoverable run error leaves
8130
+ the loaded session usable. Unload joins exit and output delivery even when a
8131
+ termination request fails, preserving the complete cleanup error.
8132
+
8133
+ The generic load defaults to CPU. `executionPreference:'gpu'` tries advertised
8134
+ CUDA, TensorRT, DirectML, CoreML and WebGPU session creation before CPU.
8135
+ Explicit `sessionOptions.executionProviders` takes precedence unchanged.
8136
+ The complete `execution` record reports discovery, provider attempts and
8137
+ fallback; successful creation establishes configuration, not physical GPU
8138
+ execution. Inference is never replayed on a different provider after failure.
8059
8139
  See [complete signatures, readiness, cancellation and usage](local-ai.md#native-onnx-sessions).
8060
8140
 
8061
8141
  ## createCoreONNXRuntime()
8062
8142
 
8063
- `createCoreONNXRuntime({client=getInstalledCoreClient()}={})` from
8143
+ `createCoreONNXRuntime({client}={})` from
8064
8144
  `arcane-os/ai/core-onnx` accesses an available Core ONNX service. Its `load`,
8065
8145
  `run`, and `unload` mirror the native session selection and accept `signal` and
8066
8146
  `timeoutMs` (default `0`). Relative model paths resolve at Core's `appRoot`.
@@ -8068,7 +8148,11 @@ The accessor owns complete tensor transport encoding/decoding, `inspect()`,
8068
8148
  `current()`, replaying `subscribe(listener)` and `close()`. Closing cancels this
8069
8149
  accessor's requests and subscriptions; unloading shared models is explicit.
8070
8150
  Construction installs no runtime and opens no transport. Existing browser
8071
- ONNX/Wllama remain independent. See [Core and browser usage](local-ai.md#onnx-through-core-and-the-browser).
8151
+ ONNX/Wllama remain independent. Omitted `client` follows late installation and
8152
+ reads each installed connection's current status; explicit clients, including
8153
+ `null`, remain fixed. Retirement or replacement clears readiness and cancels
8154
+ owned work, with late old-client results suppressed. Close also removes the
8155
+ installation subscription. See [Core and browser usage](local-ai.md#onnx-through-core-and-the-browser).
8072
8156
 
8073
8157
  ## createImageRuntime()
8074
8158
 
@@ -8108,12 +8192,18 @@ browser accessor's detach-only ownership below. See the
8108
8192
 
8109
8193
  ## createCoreImageRuntime()
8110
8194
 
8111
- `createCoreImageRuntime({client=getInstalledCoreClient(),signal?,onEvent?})`
8195
+ `createCoreImageRuntime({client,signal?,onEvent?})`
8112
8196
  from `arcane-os/ai/core-image` returns immediately with `load`, `generate`, `edit`,
8113
8197
  `unload`, `inspect`, `current`, `subscribe` and `close`. It subscribes before
8114
8198
  requesting an initial status snapshot so newer lifecycle events win. It opens
8115
8199
  no connection and installs or selects no model.
8116
8200
 
8201
+ Omitted `client` follows late Core installation, replacement and retirement;
8202
+ an explicit client, including `null`, remains fixed. Each installed client is
8203
+ observed before its status request. Retirement cancels owned work and removes
8204
+ old readiness; late results cannot restore it. Close removes both service and
8205
+ installation subscriptions without closing the shared client or model.
8206
+
8117
8207
  `load({model,context?,assetProjectionId?,resourcePaths?,signal?})` and
8118
8208
  `generate({model,prompt,parameters?,assetProjectionId?,resourcePaths?,signal?,
8119
8209
  onProgress?})` use the selected Core service. Generation acknowledges with
@@ -8410,6 +8500,14 @@ directory and complete member files.
8410
8500
 
8411
8501
  `current()` returns `{closing,projections}`. `retain(id)` accepts a completed
8412
8502
  projection and returns `{id,directory,members,release}` for a native owner.
8503
+ Native `prepare({id,workingDirectory,members,signal,onProgress})` accepts
8504
+ ordered `{path,url}` members and returns their ready projection. Explicit calls
8505
+ stream complete upstream responses through the same file owner; startup
8506
+ downloads nothing. Independent members transfer concurrently and progress
8507
+ reports phases and completed files. `release(id)` relinquishes preparation
8508
+ ownership; cancellation, release and disposal join fetches and writes before
8509
+ cleanup. Native retain handles keep completed files alive until actual engine
8510
+ release. See [native preparation](model-assets.md#native-ownership).
8413
8511
  `dispose()` releases preparation ownership and joins file writes, closes and
8414
8512
  all native retain lifetimes before cleanup. The native owner releases its
8415
8513
  handle only after its actual context/worker use ends. Original stored model
@@ -9071,18 +9169,127 @@ See the [mounting contract](pwa.md#mountpwainstallprompt) and
9071
9169
  [component reference](runtime-components.md#pwa-installhtml). The browser owns
9072
9170
  native installation eligibility and URL-bar promotion.
9073
9171
 
9172
+ ## createNativeDecisionModel()
9173
+
9174
+ ### Overview
9175
+
9176
+ Runs explicitly selected Laya FP32 option scoring through an existing native
9177
+ ONNX owner, with tokenization in a Node Worker. It creates no second inference
9178
+ engine and starts no model download at construction.
9179
+
9180
+ ### Signature and result
9181
+
9182
+ ```text
9183
+ createNativeDecisionModel({onnx,modelAssets,workingDirectory,paths,model='onnx-community/laya-typed-decisions-ONNX',revision='main',dtype='fp32',sessionOptions,executionPreference='gpu',signal}={})
9184
+ ```
9185
+
9186
+ Import from `arcane-os/local-ai/decisions`. Supply the existing `onnx` owner and
9187
+ either `modelAssets` plus `workingDirectory`, or native `paths` for the model,
9188
+ tokenizer and tokenizer configuration. The selected graph requires FP32. The
9189
+ handle exposes `load`, `evaluate`, `classify`, `current`, `status`, `subscribe`,
9190
+ `unload` and terminal `dispose`; `classify` aliases `evaluate` and `status`
9191
+ aliases `current`.
9192
+
9193
+ `evaluate(rows,{signal,runOptions})` preserves each complete `state`, `question`,
9194
+ `options` and optional `type` separately. It returns `{decisions,outputs}` with
9195
+ original rows, typed values, raw logits, unrounded probabilities and every
9196
+ named tensor output. It requires a loading or loaded activation. Cancellation
9197
+ retires the shared activation and joins its actual ONNX/tokenizer lifetime;
9198
+ working files remain retained until both Workers exit. Ordinary invalid rows
9199
+ leave a healthy session and sibling evaluations intact.
9200
+
9201
+ ### Availability and normalization
9202
+
9203
+ **Node on Windows, Linux and macOS with the selected ONNX Runtime; Android needs
9204
+ host adaptation.** GPU preference attempts actual advertised providers before
9205
+ CPU fallback; accepted configuration does not prove GPU node execution. Model
9206
+ selection, unchanged payloads, scoring interpretation and persistence remain
9207
+ application-owned. Complete lifecycle, errors and paths are documented in
9208
+ [native typed decisions](native-decisions.md).
9209
+
9210
+ ### Example
9211
+
9212
+ ```javascript
9213
+ import {createNativeDecisionModel} from 'arcane-os/local-ai/decisions';
9214
+
9215
+ function createMoonChefDecisions({onnx, paths}) {
9216
+ return createNativeDecisionModel({onnx, paths});
9217
+ }
9218
+ ```
9219
+
9220
+ The caller explicitly loads the returned model before evaluation and unloads
9221
+ or disposes it at its owning boundary; defining this function downloads nothing.
9222
+
9223
+ ## createNativeDecisionService()
9224
+
9225
+ ### Overview
9226
+
9227
+ Composes the same native decision model into an existing Core runtime without
9228
+ a renderer. The factory starts no inference, tokenizer or model preparation.
9229
+
9230
+ ### Signature and result
9231
+
9232
+ ```text
9233
+ createNativeDecisionService(configuration={}, {appRoot=process.cwd()}={})
9234
+ ```
9235
+
9236
+ Import from `arcane-os/core/decisions`. Configuration selects the model options
9237
+ above and an optional service `name` (default `decisions`); relative supplied
9238
+ `paths` resolve from `appRoot`. Explicit load obtains the registered local-AI
9239
+ and, when needed, model-assets owners through `context.getService`, then
9240
+ reacquires `getONNXRuntime()` after any recovery. Native members are `load`,
9241
+ `evaluate`, `classify`, `current`, `subscribe`, `unload`, `dispose` and Core
9242
+ `start(context)`.
9243
+
9244
+ RPC exposes `decisions.status`, `decisions.load`, `decisions.evaluate` and
9245
+ `decisions.unload`. Evaluation accepts `{rows,runOptions?}` and returns complete
9246
+ `{decisions,outputs}` using the existing tensor codec at the JSON boundary.
9247
+ `decisions.state` and replaying `subscribe(listener,{emitCurrent:true,signal})`
9248
+ report lifecycle. Unload is accepted service-lifetime cleanup; host shutdown
9249
+ still cancels request-lifetime load/evaluation.
9250
+
9251
+ ### Availability and normalization
9252
+
9253
+ **Node Core with an existing local-AI ONNX service and selected model files.**
9254
+ Registration does not wait for all services or open another host. Original
9255
+ inputs, complete outputs, failures and cleanup semantics match the native
9256
+ model. See [composition and example](native-decisions.md#compose-with-existing-core-services).
9257
+
9258
+ ### Example
9259
+
9260
+ ```javascript
9261
+ import {createNativeDecisionService} from 'arcane-os/core/decisions';
9262
+
9263
+ const decisions = createNativeDecisionService({
9264
+ model: 'onnx-community/laya-typed-decisions-ONNX',
9265
+ dtype: 'fp32',
9266
+ workingDirectory: '.models/laya'
9267
+ });
9268
+ // Add decisions to the application's existing Core services; load explicitly.
9269
+ ```
9270
+
9271
+ ## createNativeDecisionService default export
9272
+
9273
+ The default export from `arcane-os/core/decisions` is exactly the named
9274
+ `createNativeDecisionService` factory, with the same arguments and lifecycle.
9275
+
9074
9276
  ## createBrowserDecisionModel()
9075
9277
 
9076
9278
  ### Overview
9077
9279
 
9078
- Creates an explicitly loaded browser model for Laya typed decisions in FP16 or
9079
- Julia-1 in FP32. The shared Worker owns Transformers.js loading, full-input
9280
+ Creates an explicitly loaded browser model for Laya typed decisions or
9281
+ Julia-1. Laya defaults to FP16 and Julia to FP32; callers may select `dtype`
9282
+ explicitly. The shared Worker owns Transformers.js loading, full-input
9080
9283
  encoding, batched inference, complete results, cancellation and resource lifetime.
9081
9284
  Applications own model selection, questions, option meanings and decision policy.
9082
9285
  Existing chat and speech providers are unchanged.
9083
9286
 
9084
9287
  ### Signature and result
9085
9288
 
9289
+ ```text
9290
+ createBrowserDecisionModel({family,model,revision,device,dtype,runtime}={})
9291
+ ```
9292
+
9086
9293
  ```js
9087
9294
  import {createBrowserDecisionModel} from 'arcane-os/ai/browser-decisions';
9088
9295
 
@@ -9107,8 +9314,11 @@ precision, tokenization, output meanings, lifecycle and model limitations.
9107
9314
  ### Availability and normalization
9108
9315
 
9109
9316
  Browser module Workers plus the selected Transformers.js backend are required
9110
- on explicit use. Import and construction do not activate a model. Laya fixes
9111
- `dtype: 'fp16'`; Julia fixes `dtype: 'fp32'`; there is no precision fallback.
9317
+ on explicit use. Import and construction do not activate a model. The optional
9318
+ nonempty `dtype` string defaults to `fp16` for Laya and `fp32` for Julia. An
9319
+ explicit value is forwarded unchanged to the upstream loader; unsupported
9320
+ selections report its failure without precision fallback. For Laya FP32, pass
9321
+ `dtype: 'fp32'`.
9112
9322
  Cancellation terminates that client's Worker and rejects its in-flight work.
9113
9323
  No decisions are inserted into chat history or durable storage.
9114
9324
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.66.0",
3
+ "version": "0.68.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",
@@ -69,6 +69,7 @@
69
69
  "./core/preferences": "./src/core/services/preferences.mjs",
70
70
  "./core/diarization": "./src/diarization/service.mjs",
71
71
  "./core/local-ai": "./src/core/services/local-ai.mjs",
72
+ "./core/decisions": "./src/core/services/decisions.mjs",
72
73
  "./core/image": "./src/core/services/image.mjs",
73
74
  "./core/speech": "./src/core/services/speech.mjs",
74
75
  "./core/model-assets": "./src/core/services/model-assets.mjs",
@@ -76,6 +77,7 @@
76
77
  "./diarization/build": "./src/diarization/build.mjs",
77
78
  "./local-ai": "./src/local-ai/install.mjs",
78
79
  "./local-ai/onnx": "./src/local-ai/onnx.mjs",
80
+ "./local-ai/decisions": "./src/local-ai/decisions.mjs",
79
81
  "./local-ai/image": "./src/local-ai/image-runtime.mjs",
80
82
  "./local-ai/whisper": "./src/local-ai/whisper/index.mjs",
81
83
  "./local-ai/whisper/build": "./src/local-ai/whisper/build.mjs",
@@ -121,7 +123,7 @@
121
123
  "test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
122
124
  "test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
123
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",
124
- "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-development.test.mjs test/core-host.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-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-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",
125
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",
126
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",
127
129
  "check": "node tools/check-source.mjs && npm test",
@@ -158,6 +160,7 @@
158
160
  "tag": "latest"
159
161
  },
160
162
  "dependencies": {
163
+ "@huggingface/tokenizers": "0.2.0",
161
164
  "event-pubsub": "6.1.1",
162
165
  "js-queue": "3.1.1",
163
166
  "node-http-server": "10.0.0",
@@ -1,11 +1,62 @@
1
1
  <style>
2
- :host{display:inline-block;color:inherit}
3
- .switcher{align-items:center;display:flex;gap:.15rem;padding:.2rem;border:1px solid var(--app-bar-border,var(--border-color,rgba(255,255,255,.25)));border-radius:999px;background:color-mix(in srgb,currentColor 8%,transparent)}
4
- button{appearance:none;border:0;border-radius:999px;background:transparent;color:inherit;cursor:pointer;font:700 .68rem/1 system-ui,sans-serif;min-height:1.85rem;padding:.42rem .58rem}
5
- button:hover{background:color-mix(in srgb,currentColor 14%,transparent)}
6
- button[aria-pressed="true"]{background:var(--app-bar-accent,var(--secondary-color,#c49a52));color:var(--app-bar-active-text,var(--primary-color,#172238))}
7
- button:focus-visible{outline:.18rem solid var(--focus-color,currentColor);outline-offset:.12rem}
8
- @media(max-width:32rem){button{font-size:0;padding-inline:.5rem}button::first-letter{font-size:.72rem}}
2
+ :host {
3
+ display:inline-block;
4
+ min-inline-size:0;
5
+ max-inline-size:100%;
6
+ color:inherit;
7
+ }
8
+
9
+ .switcher {
10
+ display:flex;
11
+ flex-wrap:wrap;
12
+ align-items:center;
13
+ gap:.15rem;
14
+ box-sizing:border-box;
15
+ max-inline-size:100%;
16
+ padding:.2rem;
17
+ border:1px solid var(--app-bar-border,var(--border-color,rgba(255,255,255,.25)));
18
+ border-radius:1.6rem;
19
+ background:color-mix(in srgb,currentColor 8%,transparent);
20
+ }
21
+
22
+ button {
23
+ appearance:none;
24
+ flex:1 0 auto;
25
+ box-sizing:border-box;
26
+ min-inline-size:2.75rem;
27
+ max-inline-size:100%;
28
+ min-block-size:2.75rem;
29
+ padding:.5rem .75rem;
30
+ border:0;
31
+ border-radius:1.4rem;
32
+ background:transparent;
33
+ color:inherit;
34
+ cursor:pointer;
35
+ font:700 .875rem/1.25 system-ui,sans-serif;
36
+ text-align:center;
37
+ white-space:normal;
38
+ overflow-wrap:anywhere;
39
+ }
40
+
41
+ button:hover:not(:disabled) {
42
+ background:color-mix(in srgb,currentColor 14%,transparent);
43
+ }
44
+
45
+ button[aria-pressed="true"] {
46
+ background:var(--app-bar-accent,var(--secondary-color,rgb(196,154,82)));
47
+ color:var(--app-bar-active-text,var(--primary-color,rgb(23,34,56)));
48
+ box-shadow:inset 0 0 0 .1rem currentColor;
49
+ }
50
+
51
+ button:disabled {
52
+ cursor:not-allowed;
53
+ opacity:.55;
54
+ }
55
+
56
+ button:focus-visible {
57
+ outline:.18rem solid var(--focus-color,currentColor);
58
+ outline-offset:.12rem;
59
+ }
9
60
  </style>
10
61
  <div class="switcher" role="group" aria-label="Color scheme">
11
62
  <button type="button" data-scheme="system" aria-pressed="false" title="Use device theme">Auto</button>
@@ -16,31 +67,79 @@
16
67
  <script type="module">
17
68
  const host=this;
18
69
  const root=host.shadowRoot;
70
+ const group=root.querySelector('.switcher');
19
71
  const buttons=Array.from(root.querySelectorAll('[data-scheme]'));
72
+ let modes=buttons.map(function scheme(button){return button.dataset.scheme;});
73
+ let labels=Object.fromEntries(buttons.map(function label(button){
74
+ return [button.dataset.scheme,button.textContent];
75
+ }));
76
+ let busy=false;
77
+
78
+ host.configure=configure;
79
+ configure({
80
+ modes:host.dataset.modes===undefined?modes:host.dataset.modes.split(/\s+/u).filter(Boolean),
81
+ labels:host.dataset.systemLabel===undefined?{}:{system:host.dataset.systemLabel}
82
+ });
83
+
20
84
  const {default:ThemeManager}=await import('../modules/ThemeManager.js');
21
85
  const manager=new ThemeManager();
22
86
 
23
87
  host.setMode=setMode;
24
88
  host.refresh=refresh;
25
- root.querySelector('.switcher').addEventListener('click',async event=>{
89
+ group.addEventListener('click',async function selectScheme(event){
26
90
  const button=event.target.closest('[data-scheme]');
27
- if(!button) return;
91
+ if(!button||button.disabled) return;
28
92
  setBusy(true);
29
93
  try{
30
94
  const state=button.dataset.scheme==='custom'?await manager.activateCustom():await manager.setScheme(button.dataset.scheme);
31
95
  setMode(state.mode);setCustomAvailable(Boolean(state.theme));
32
96
  }finally{setBusy(false);setCustomAvailable(Boolean(manager.customTheme));}
33
97
  });
34
- globalThis.addEventListener('arcane-theme-change',()=>refresh().catch(()=>{}));
98
+ globalThis.addEventListener('arcane-theme-change',function themeChanged(){
99
+ refresh().catch(function refreshRejected(){});
100
+ });
101
+
102
+ function configure(options={}){
103
+ const nextModes=options.modes===undefined?modes:[...options.modes];
104
+ const nextLabels={...labels,...options.labels};
105
+ const visible=[];
106
+ for(const mode of nextModes){
107
+ const button=buttons.find(function matchingScheme(candidate){return candidate.dataset.scheme===mode;});
108
+ if(!button) throw new TypeError(`Unknown theme-switcher mode: ${mode}`);
109
+ if(visible.includes(button)) throw new TypeError(`Repeated theme-switcher mode: ${mode}`);
110
+ visible.push(button);
111
+ }
112
+ for(const button of buttons){
113
+ const label=nextLabels[button.dataset.scheme];
114
+ if(typeof label!=='string') throw new TypeError('Theme-switcher labels must be strings.');
115
+ }
116
+ modes=nextModes;
117
+ labels=nextLabels;
118
+ for(const button of buttons) button.textContent=labels[button.dataset.scheme];
119
+ const reordered=group.children.length!==visible.length||visible.some(function moved(button,index){
120
+ return group.children[index]!==button;
121
+ });
122
+ if(reordered){
123
+ const focused=root.activeElement;
124
+ group.replaceChildren(...visible);
125
+ if(visible.includes(focused)&&!focused.disabled) focused.focus({preventScroll:true});
126
+ }
127
+ return {modes:[...modes],labels:{...labels}};
128
+ }
35
129
 
36
130
  function setMode(mode='system'){
37
131
  for(const button of buttons) button.setAttribute('aria-pressed',String(button.dataset.scheme===mode));
38
132
  host.dataset.mode=mode;
39
133
  return mode;
40
134
  }
41
- function setBusy(value){for(const button of buttons) button.disabled=Boolean(value);}
42
- function setCustomAvailable(value){root.querySelector('[data-scheme="custom"]').disabled=!value;}
135
+ function setBusy(value){
136
+ busy=Boolean(value);
137
+ for(const button of buttons) button.disabled=busy;
138
+ }
139
+ function setCustomAvailable(value){
140
+ buttons.find(function customScheme(button){return button.dataset.scheme==='custom';}).disabled=busy||!value;
141
+ }
43
142
  async function refresh(){const state=await manager.load();setMode(state.mode);setCustomAvailable(Boolean(state.theme));return state;}
44
143
 
45
- refresh().catch(()=>setMode('system'));
144
+ refresh().catch(function refreshRejected(){setMode('system');});
46
145
  </script>