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
@@ -174,6 +174,34 @@ This factory accepts chat/ONNX requirements; the separate
174
174
  owns image requirements. The development and generated native composition
175
175
  split the shared application record accordingly.
176
176
 
177
+ ### Native service owners
178
+
179
+ A native application service uses its Core lifecycle or request context to
180
+ obtain the existing selected owner:
181
+
182
+ ```js
183
+ const localAI = await context.getService('local-ai');
184
+ const onnx = localAI.getONNXRuntime();
185
+ const modelAssets = await context.getService('model-assets');
186
+ ```
187
+
188
+ `getService()` awaits only that service's existing startup. The synchronous
189
+ `getONNXRuntime()` returns its actual `load/run/unload/current/subscribe/close`
190
+ owner; it neither prepares another runtime nor loads a model. An unselected
191
+ engine reports `LOCAL_AI_RUNTIME_NOT_SELECTED`; an unavailable or recovering
192
+ owner reports `LOCAL_AI_RUNTIME_UNAVAILABLE`, and an aborted service lifetime
193
+ retains its abort reason. Development and generated native compositions register
194
+ one shared model-assets service when ONNX or image generation is selected.
195
+
196
+ Reacquire `getONNXRuntime()` on each explicit model load because runtime recovery
197
+ replaces that owner. Retain the acquired handle for that session's cleanup and
198
+ await its `unload({id})` without an already-aborted request signal before releasing
199
+ the corresponding model-assets retain handle. The native ONNX owner joins actual
200
+ worker exit; a cancelled request's rejection alone does not establish that exit.
201
+ Core closes services concurrently, and the host can cancel in-flight native work
202
+ at shutdown. These native members require no renderer readiness and introduce no
203
+ second inference engine. See [native Core composition](core-runtime.md#native-service-composition).
204
+
177
205
  `localai.status` returns `{runtimes}` and, when Ollama is selected, the existing
178
206
  `ollama` and `models.ollama` catalog fields. Runtime records distinguish
179
207
  `installed`, `available`, `state`, `models`, ownership and errors. A listening
@@ -198,6 +226,14 @@ background health polling.
198
226
  | `llama.unload` | `{model}` | Current state after release |
199
227
  | `llama.chat` | `{model,payload,stream?,streamId?}` | Complete OpenAI-shaped completion |
200
228
 
229
+ `llama.status` and `llama.models` observe the selected service without loading,
230
+ restarting or waiting for it. Starting, loading, stopped and failed services
231
+ return their retained state/catalog; an available service supplies a fresh
232
+ catalog observation. The llama.cpp runtime record includes `released: true`
233
+ only after Core successfully closes its owned single-model server. Explicit
234
+ load, inference or recovery can resume that engine; observation cannot.
235
+ Status observation adds no background polling.
236
+
201
237
  The `payload` is the complete request. SDK option names are translated only at
202
238
  the upstream protocol boundary; messages, tool definitions and documents remain
203
239
  unchanged. An explicit `payload.model` selects that request's model. Core waits
@@ -213,7 +249,8 @@ observe load, sleep and unload changes. Direct servers expose readiness at
213
249
  request boundaries; they do not offer the router's passive model event stream.
214
250
 
215
251
  Unloading a model from an owned single-model server closes that owned process;
216
- a subsequent load can start it again. Releasing an externally owned direct
252
+ a subsequent load can start it again. Repeating that unload returns the released
253
+ state without restarting the process. Releasing an externally owned direct
217
254
  server returns `released: true, unloaded: false`, retaining the real catalog
218
255
  and leaving that server running.
219
256
 
@@ -289,6 +326,38 @@ does not install software or select a model. A missing Core connection reports
289
326
  unavailability, leaving browser Wllama/ONNX routes available for the application
290
327
  to select.
291
328
 
329
+ Omit `client` to follow the installed Core client through
330
+ [`subscribeCoreClient`](core-client.md): construction may precede installation.
331
+ Each installation reads `llama.status` without selecting, downloading or loading
332
+ a model. Retirement or replacement synchronously revokes this provider's
333
+ `status().loaded`, cancels its owned operations and detaches the old lifecycle
334
+ listener. Late results cannot restore the retired selection. Explicitly load
335
+ the selection against the replacement client before inference. Passing a
336
+ `client`, including `null`, keeps that caller-supplied connection fixed.
337
+
338
+ `status()` is this provider's current state. The `arcane-ai-provider/2` contract
339
+ does not provide a provider-state subscription, so this installation handling
340
+ alone does not update an idle AI runtime's retained role state. The provider
341
+ runtime still reconciles the provider at its existing operation boundaries.
342
+ `inspect()` reports whether an explicit load can proceed, separately from
343
+ `status().loaded`. A genuine `starting` or `loading` service without an error
344
+ permits that load preflight even before its catalog arrives; Core's `llama.load`
345
+ waits for startup and verifies the exact requested model. An owned, successfully
346
+ released service permits loading only a model retained in its actual catalog.
347
+ An available service still requires that catalog membership. These observations
348
+ do not fabricate model records or readiness, and unavailable external services,
349
+ closed services and actual errors remain unavailable.
350
+
351
+ `dispose()` removes installation and service subscriptions and cancels owned
352
+ operations synchronously, then attempts its actually retained model's release
353
+ through its captured client. A configured selection alone does not own a native
354
+ model: disposing before load or after successful unload sends no native release,
355
+ including when the caller explicitly supplied `client: null`. A failed release
356
+ rejects with the actual error even though the provider is disposed; disposal
357
+ does not establish successful native unload.
358
+ It never unloads a retired selection through a replacement client or closes the
359
+ shared Core client.
360
+
292
361
  If a router evicts the primary model while serving a request-specific model,
293
362
  reload the primary selection before its next request. The provider does not
294
363
  silently change the application's saved model choice. Release registration
@@ -316,13 +385,45 @@ try {
316
385
  ```
317
386
 
318
387
  The factory accepts `{modulePath,signal?,onEvent?}` and starts no worker until
319
- `load({id,model,sessionOptions?,signal?})`. The model path is absolute for this
388
+ `load({id,model,sessionOptions?,executionPreference?,signal?})`. The model path is absolute for this
320
389
  direct Node API. Session creation defaults to the CPU execution provider;
321
390
  explicit native session options remain caller-owned. The returned session
322
391
  record contains `id`, `model`, `state`, `loaded`, `error`, input/output names and
323
392
  metadata. A live ID requires explicit unload before replacement. An ID may be
324
393
  loaded again once its previous worker exits after a failed load or unload.
325
394
  A run error retains the loaded session so a corrected request can use it.
395
+ `current().sessions[].stopping` becomes `true` when terminal shutdown begins,
396
+ including worker failure or explicit cancellation/unload. The session is then
397
+ unavailable for new runs even while `loaded` remains `true` until actual exit.
398
+ A recoverable run error keeps `stopping:false` and the loaded session usable.
399
+ `current().sessions[].exited` reports actual worker exit, independently of a
400
+ rejected load or inference. Unload joins worker exit and output delivery even
401
+ when requesting termination fails; complete cleanup errors remain observable.
402
+
403
+ Select `executionPreference: 'gpu'` to try the installed runtime's advertised
404
+ CUDA, TensorRT, DirectML, CoreML and WebGPU providers in that order, followed by
405
+ CPU when no accelerator session can be created. Discovery runs once per load;
406
+ each advertised candidate receives one actual session-creation attempt with CPU
407
+ available for graph nodes that the accelerator cannot handle. No provider is
408
+ downloaded by selection. The upstream `bundled` flag describes packaging and
409
+ does not exclude separately installed CUDA or TensorRT libraries. An explicit
410
+ `sessionOptions.executionProviders`, including an empty array, takes precedence
411
+ and is passed unchanged without automatic discovery or retries. Automatic
412
+ DirectML attempts default `enableMemPattern` to `false` and `executionMode` to
413
+ `'sequential'` as required by that provider; explicit caller values remain
414
+ unchanged, including on the final CPU attempt.
415
+
416
+ Automatic GPU selection adds an `execution` record to the loaded session and
417
+ its ordinary state events: `preference`, complete `supportedBackends`,
418
+ `selectedProviders`, ordered `attempts` with their full errors, `fallback`, and
419
+ `discoveryError`. Each attempt records `executionProviders`, `status`
420
+ (`configured` or `failed`), and `error`. `fallback: true` means the SDK created
421
+ the final CPU session after GPU preference, including when no GPU candidate was
422
+ advertised. Successful creation establishes an accepted provider configuration;
423
+ it does not establish that a GPU executed any graph nodes. If every attempt
424
+ fails, the load error retains the complete attempt errors and `execution`
425
+ record. Inference failures retain their existing behavior and do not replay
426
+ inference on another provider.
326
427
 
327
428
  `run({id,feeds,fetches?,runOptions?,signal?})` returns the complete output-name
328
429
  map of `{type,dims,data}` tensors. Input records use that same shape with native
@@ -383,6 +484,15 @@ Closing the accessor cancels its requests and subscriptions; model unloading
383
484
  is explicit because other accessors may share the Core session. It never
384
485
  closes the shared Core client.
385
486
 
487
+ Omitting `client` follows Core installation events with current-client replay,
488
+ including installation after construction. Each new connection reads
489
+ `onnx.status`; it does not install a runtime or load a model. Retirement or
490
+ replacement immediately clears retained session readiness and cancels this
491
+ accessor's operations. Late old-client state and results are ignored. Passing
492
+ an explicit `client`, including `null`, retains caller ownership and does not
493
+ follow global installation changes. `close()` also removes the installation
494
+ subscription.
495
+
386
496
  Construction neither opens a connection nor installs a runtime. A browser needs
387
497
  an already available native Core service or the explicit development bridge.
388
498
  Unavailable native ONNX leaves browser ONNX/Wllama under the application's
@@ -199,14 +199,16 @@ ordered `Blob` or `File` members after its resumable download. Range support
199
199
  allows concurrent transfer within a member; those transfer parts remain the
200
200
  store's concern. Use `offline: true` to require an already complete stored model.
201
201
  The store does not select or run the image engine. In source development,
202
- load the development Core bootstrap described
203
- in [Local AI through Core](local-ai.md#development) before constructing the
204
- accessor. Native hosts install their Core client through the existing host path.
202
+ use the development Core bootstrap described
203
+ in [Local AI through Core](local-ai.md#development). The accessor can be
204
+ constructed before that client is installed; perform model operations once
205
+ the service is available. Native hosts install their Core client through the
206
+ existing host path.
205
207
  An ordinary browser without Core reports local image generation as unavailable.
206
208
 
207
209
  | Member | Contract |
208
210
  |---|---|
209
- | `createCoreImageRuntime({client?, signal?, onEvent?})` | Uses the installed Core client by default, subscribes to lifecycle events and asynchronously reads one initial status snapshot. It returns immediately. |
211
+ | `createCoreImageRuntime({client?, signal?, onEvent?})` | Follows Core installation and lifecycle events by default, asynchronously reading status for each client. An explicit `client`, including `null`, remains caller-owned. It returns immediately. |
210
212
  | `load({model, context?, assetProjectionId?, resourcePaths?, signal?})` | Loads the selected model and returns authoritative service state after native completion. `context` supplies native context options. |
211
213
  | `generate({model, prompt, parameters?, assetProjectionId?, resourcePaths?, signal?, onProgress?})` | Returns the complete result with `images: [{blob, mediaType, width, height}]`. Additional runtime result metadata remains present. |
212
214
  | `unload({signal?})` | Explicitly unloads the shared selected native context and returns its resulting state. |
@@ -229,6 +231,15 @@ model definitions, including their resource metadata before engine preparation
229
231
  finishes. Installed files do not imply a loaded model. A late status
230
232
  response never overwrites a newer lifecycle event.
231
233
 
234
+ With `client` omitted, the accessor consumes the shared
235
+ [`subscribeCoreClient`](core-client.md) installation stream. A late installation
236
+ attaches the service listener and reads current status; it never installs an
237
+ engine or loads a model. Retirement or replacement synchronously clears
238
+ readiness and transient `Thinking`, cancels owned requests and detaches the
239
+ retired service listener. Old state, progress and results cannot update the
240
+ replacement connection. `close()` also removes the installation subscription;
241
+ it does not claim that the shared native model was unloaded.
242
+
232
243
  Prompts and parameters pass unchanged to the native runtime. Native option
233
244
  names, supported model operations and defaults belong to that runtime and the
234
245
  selected model definition. A prompt containing U+0000 or an unpaired UTF-16
@@ -1,6 +1,6 @@
1
- # Native working files from stored model assets
1
+ # Native working files from model assets
2
2
 
3
- These public APIs were first published in `arcane-os@0.61.0`. Browser
3
+ Browser preparation and native retain/release ownership were first published in `arcane-os@0.61.0`. Browser
4
4
  preparation is exported by `arcane-os/ai/core-model-assets`, and the native
5
5
  service by `arcane-os/core/model-assets`. The image example below uses
6
6
  `arcane-os/ai/core-image`, published in the same release.
@@ -87,6 +87,53 @@ cleanup failures remain observable.
87
87
 
88
88
  ## Native ownership
89
89
 
90
+ Native services can prepare explicitly selected upstream members without a
91
+ renderer or browser store. Obtain the registered shared service through
92
+ `await context.getService('model-assets')`, then use its native preparation API:
93
+
94
+ ```javascript
95
+ const modelAssets = await context.getService('model-assets');
96
+ const projection = await modelAssets.prepare({
97
+ id: selectedOperationId,
98
+ workingDirectory: selectedWorkingDirectory,
99
+ members: selectedMembers, // [{path: 'onnx/model.onnx', url: selectedURL}, ...]
100
+ signal,
101
+ onProgress: renderPreparationProgress
102
+ });
103
+ const use = modelAssets.retain(projection.id);
104
+ await modelAssets.release(projection.id);
105
+ try {
106
+ await ownNativeModelLifetime(use.members);
107
+ } finally {
108
+ // Join actual native unload/worker exit before releasing these files.
109
+ await use.release();
110
+ }
111
+ ```
112
+
113
+ `prepare({id,workingDirectory,members,signal,onProgress})` performs standard
114
+ fetches only when explicitly called. Every member has its original relative
115
+ `path` and selected `url`; the caller supplies the complete companion-file set.
116
+ Independent members download concurrently. Each complete response stream is
117
+ written directly through the existing ordered native file owner, without a
118
+ whole-model buffer or base64 conversion. No model or runtime is selected or
119
+ downloaded by service startup. The result is the ready projection snapshot with
120
+ ordered `members: [{path,nativePath}]`.
121
+
122
+ Progress uses `open`, `download`, `complete` and `ready` phases, with
123
+ `completed`, `total` and `unit:'files'`; member progress additionally includes
124
+ `memberIndex` and `path`. Progress callbacks may return a promise, which the
125
+ preparation observes. They must not await cleanup of the preparation currently
126
+ calling them. Callback failures remain preparation failures. HTTP failures
127
+ retain `url`, `status` and the complete response text in `response`.
128
+
129
+ `release(id)` relinquishes preparation ownership without a request signal.
130
+ For active native downloads, release or disposal aborts their fetches and joins
131
+ the transfer tasks and native writes before deleting only their owned child
132
+ directory. Preparation cancellation and failure follow that same cleanup path;
133
+ the original failures and cleanup failures remain observable. A native retain
134
+ handle continues to protect completed files through actual engine release.
135
+ Native preparation leaves browser DBOPFS originals and unrelated files alone.
136
+
90
137
  ```javascript
91
138
  import {createModelAssetService} from 'arcane-os/core/model-assets';
92
139
 
@@ -103,7 +150,7 @@ try {
103
150
  ```
104
151
 
105
152
  `createModelAssetService({appRoot})` returns a Core service with `current()`,
106
- `retain(id)` and `dispose()`. `retain()` accepts only a completed projection
153
+ `prepare(options)`, `retain(id)`, `release(id)` and `dispose()`. `retain()` accepts only a completed projection
107
154
  and returns `{id,directory,members,release}`. A native engine takes this handle
108
155
  before loading and releases it after actual unload, failed-load cleanup or
109
156
  worker exit. A rejected inference/load promise alone does not establish native
@@ -0,0 +1,181 @@
1
+ # Native Laya typed decisions
2
+
3
+ `arcane-os/local-ai/decisions` and `arcane-os/core/decisions` run the selected
4
+ Laya FP32 graph through the SDK's existing native ONNX owner. Tokenization runs
5
+ in its own Node Worker. A Core service can load and evaluate decisions without
6
+ a renderer, browser session, WebGPU browser context or chat provider.
7
+
8
+ The application owns the model selection, original state text, questions,
9
+ options and interpretation. The SDK owns model preparation, token framing,
10
+ inference and cleanup. This is option scoring, not a conversational LLM, a
11
+ replacement for a configured chat model or the cloud System One API.
12
+
13
+ ## Compose with existing Core services
14
+
15
+ Register a decision service alongside the selected local-AI and model-assets
16
+ services in the application's existing Core runtime:
17
+
18
+ ```js
19
+ import {createNativeDecisionService} from 'arcane-os/core/decisions';
20
+
21
+ const decisions = createNativeDecisionService({
22
+ model: 'onnx-community/laya-typed-decisions-ONNX',
23
+ dtype: 'fp32',
24
+ workingDirectory: '.models/laya'
25
+ }, {appRoot});
26
+
27
+ // Include decisions in the existing createCoreRuntime({services}) selection.
28
+ // That runtime already owns local-ai with ONNX selected and model-assets.
29
+ await runtime.getService('decisions');
30
+ await decisions.load(); // Explicit activation; no model download at registration.
31
+ try {
32
+ const result = await decisions.evaluate([{
33
+ state: 'The moon chef has one kettle and seventeen impatient dragons.',
34
+ question: 'What should the chef prepare?',
35
+ options: ['Tea', 'An inflatable castle']
36
+ }]);
37
+ console.log(result.decisions[0].value);
38
+ } finally {
39
+ await decisions.unload();
40
+ }
41
+ ```
42
+
43
+ Here `runtime` and `appRoot` are the application's existing Core runtime and
44
+ root. Add the service before starting that runtime; the example does not create
45
+ a second Core host or engine. Development and packaged native compositions
46
+ already register one model-assets service when ONNX is selected. The decision
47
+ service obtains those exact owners with `context.getService('local-ai')`,
48
+ `localAI.getONNXRuntime()` and `context.getService('model-assets')` on explicit
49
+ load. It reacquires the current ONNX owner after a later runtime recovery.
50
+ See [Core composition](core-runtime.md#native-service-composition) and
51
+ [native service owners](local-ai.md#native-service-owners).
52
+
53
+ `createNativeDecisionService(configuration, {appRoot})` defaults its service
54
+ name to `decisions`. It exposes `load`, `evaluate`, `classify` (an alias for
55
+ `evaluate`), `current`, `subscribe`, `unload` and `dispose`, plus Core lifecycle
56
+ `start(context)`. Its RPC methods are:
57
+
58
+ | Method | Parameters and result |
59
+ | --- | --- |
60
+ | `decisions.status` | Current lifecycle snapshot; does not load anything. |
61
+ | `decisions.load` | Explicit activation; returns the ready lifecycle snapshot. The request signal cancels activation. |
62
+ | `decisions.evaluate` | `{rows, runOptions?}`; returns `{decisions, outputs}`. The request signal cancels the active model operation. |
63
+ | `decisions.unload` | Releases the activation. Service-lifetime cleanup continues after the caller disconnects. |
64
+
65
+ RPC evaluation uses the SDK's existing complete JSON tensor encoding for
66
+ `outputs` at the transport boundary. Direct native evaluation returns native
67
+ typed tensor records. Service notifications use `decisions.state`; direct
68
+ `subscribe(listener, {emitCurrent:true, signal})` replays current state by
69
+ default and returns an unsubscribe function. Startup stores the service
70
+ context only: importing or registering this service starts no tokenizer,
71
+ model download or inference.
72
+
73
+ ## Direct native model
74
+
75
+ `createNativeDecisionModel(options)` from `arcane-os/local-ai/decisions` returns
76
+ `{load, evaluate, classify, current, status, subscribe, unload, dispose}`.
77
+ `status` aliases `current`; `classify` aliases `evaluate`. Supply the existing
78
+ native `onnx` owner, plus either the existing `modelAssets` owner and selected
79
+ `workingDirectory`, or caller-owned native files:
80
+
81
+ ```js
82
+ import {createNativeDecisionModel} from 'arcane-os/local-ai/decisions';
83
+
84
+ const decisions = createNativeDecisionModel({
85
+ onnx: localAI.getONNXRuntime(),
86
+ paths: {
87
+ model: selectedGraphPath,
88
+ tokenizer: selectedTokenizerPath,
89
+ tokenizerConfig: selectedTokenizerConfigPath
90
+ }
91
+ });
92
+ ```
93
+
94
+ These direct paths are native filesystem paths. Keep the graph's external data
95
+ beside it under the exact filename required by the graph. The Core service
96
+ resolves configured `paths` relative to `appRoot`; it does not copy or remove
97
+ caller-owned files. For SDK-prepared files, the default selected upstream is
98
+ `onnx-community/laya-typed-decisions-ONNX`, revision `main`, with exactly:
99
+
100
+ - `onnx/model.onnx` and `onnx/model.onnx_data`;
101
+ - `tokenizer.json` and `tokenizer_config.json`.
102
+
103
+ `model` selects the upstream Hugging Face repository and `revision` its
104
+ revision. Native preparation performs normal upstream fetches only during
105
+ explicit `load()`. It streams complete members through the existing working
106
+ file owner; it neither vendors the weights nor creates a second persistent
107
+ model store. The approved `@huggingface/tokenizers@0.2.0` dependency reads the
108
+ selected tokenizer files in its Worker. No Transformers or second ONNX runtime
109
+ is loaded by this API.
110
+
111
+ This graph selection requires `dtype:'fp32'`, which is also the default; another
112
+ dtype reports the incompatible selection rather than substituting a graph.
113
+ `sessionOptions` and `runOptions` pass to the existing native ONNX API unchanged.
114
+ `executionPreference` defaults to `gpu` here: actual advertised native GPU
115
+ provider creation is attempted, followed by an honest CPU fallback. Explicit
116
+ `sessionOptions.executionProviders` takes precedence. The underlying generic
117
+ ONNX factory continues to default to CPU. Accepted provider configuration is
118
+ not evidence that GPU nodes executed. Full provider-attempt diagnostics remain
119
+ in `execution`; see [native ONNX sessions](local-ai.md#native-onnx-sessions).
120
+
121
+ ## Complete inputs and outputs
122
+
123
+ `evaluate(rows, {signal, runOptions})` requires an explicitly loaded or loading
124
+ activation; it does not silently load an inactive model. Rows retain their
125
+ caller order, complete `state`, `question` and string-array `options`, plus
126
+ optional `type` (`choice`, `score` or `noul`). Additional row fields remain in
127
+ the returned row. Strings are not trimmed, rewritten or clipped. The shared
128
+ browser/native encoding owner constructs only the model's necessary token
129
+ framing; repeated identical state is tokenized once per batch. Tokenization
130
+ and ONNX execution run outside Core's foreground thread.
131
+
132
+ The result is `{decisions, outputs}`. Each decision contains its original
133
+ `row`, complete option `logits`, unrounded raw-softmax `probabilities`,
134
+ `answerIndex` and `value`. `choice` returns the selected complete option;
135
+ `score` returns the probability-weighted zero-based option index; `noul`
136
+ requires `[falseOption, trueOption]` and returns the probability of option 1.
137
+ When the graph actually supplies `act_logits`, action logits, probabilities
138
+ and `actProbability` are also returned. Every named graph output is retained
139
+ as `{type, dims, data}`, including extra tensors and padding. Raw model scores
140
+ are not claimed calibrated confidence. Results do not enter chat history,
141
+ memory extraction or DBOPFS automatically.
142
+
143
+ The model's real graph/context limits still apply. There is no SDK token cap,
144
+ silent chunking or fabricated result aggregation. Native/browser token-ID or
145
+ numerical parity requires execution evidence for the selected tokenizer and
146
+ runtime; sharing the encoding implementation alone does not establish it.
147
+
148
+ ## Lifetime and cancellation
149
+
150
+ `current()` returns `{family, model, revision, dtype, state, loaded, busy,
151
+ activeRequests, progress, error, execution}`. States include `unloaded`,
152
+ `loading`, `ready`, `unloading`, `disposing`, `disposed` and `error`. Semantic
153
+ progress includes model preparation, tokenization and evaluation; downloaded
154
+ members report files, not byte progress. Complete technical errors belong in
155
+ developer diagnostics, while the application owns its user-facing status.
156
+
157
+ Concurrent callers share one explicit activation. Cancelling a load or
158
+ evaluation cancels that activation and its outstanding operations, because the
159
+ native session and tokenizer have one owner. Use separately owned models when
160
+ independent cancellation is required. The construction `signal` governs the
161
+ whole model lifetime. `unload()` permits another explicit load after successful
162
+ cleanup; `dispose()` is terminal. Readiness is revoked as disposal begins.
163
+
164
+ Retained model files remain owned until the ONNX and tokenizer Workers really
165
+ exit. A rejected load, inference or termination request alone does not permit
166
+ file deletion. Cleanup joins active operations and native output delivery,
167
+ preserves cleanup errors, then releases only its working projection. It never
168
+ deletes supplied `paths`. A failed ordinary row does not destroy a healthy
169
+ loaded session or cancel sibling evaluations. The ONNX owner's `stopping:true`
170
+ retires decision readiness immediately on terminal Worker failure or shutdown,
171
+ even while its physical `loaded` state remains true pending actual exit. Cancelled or
172
+ retired operations cannot return a successful late result.
173
+
174
+ Shared Node/Worker/filesystem code is portable across Windows, Linux and macOS;
175
+ the selected ONNX Runtime build and provider determine actual platform/device
176
+ availability. Android requires the host's supported native adaptation. Static
177
+ source review and synthetic authored fixtures are not model execution or a
178
+ claim of tested platform, precision or accelerator behavior.
179
+
180
+ Upstream selection: [Laya FP32 model files](https://huggingface.co/onnx-community/laya-typed-decisions-ONNX/tree/main),
181
+ [Tokenizer package](https://www.npmjs.com/package/@huggingface/tokenizers).
@@ -112,7 +112,7 @@ snapshot remains owned until the browser's `afterprint` event.
112
112
  | [`task-progress.html`](#task-progresshtml) | Runs and displays a task list with started/change/complete/error state. | `configure()`<br>`setTasks()`<br>`updateTask()`<br>`runTasks()`<br>`clear()`<br>`destroy()` | `task-progress-ready`<br>`task-progress-started`<br>`task-progress-change`<br>`task-progress-complete`<br>`task-progress-error` | Task state normalized; injected task results mixed |
113
113
  | [`terminal-workspace.html`](#terminal-workspacehtml) | Presents multiple terminal sessions, output, active selection, theme, and terminal actions. | `configure()`<br>`addSession()`<br>`removeSession()`<br>`activateSession()`<br>`append()`<br>`clear()`<br>`setState()`<br>`setTheme()`<br>`focus()`<br>`destroy()` | `terminal-workspace-ready`<br>`terminal-submit`<br>`terminal-interrupt`<br>`terminal-clear`<br>`terminal-session-new`<br>`terminal-session-close`<br>`terminal-session-select`<br>`terminal-settings` | UI/session state normalized; native command results supplied externally |
114
114
  | [`theme-editor.html`](#theme-editorhtml) | Edits, previews, saves, and resets semantic custom theme tokens. | `configure()`<br>`getTheme()`<br>`setTheme()`<br>`setBusy()`<br>`setStatus()`<br>`destroy()` | `theme-editor-ready`<br>`theme-preview`<br>`theme-save`<br>`theme-reset` | Fully normalized Theme values |
115
- | [`theme-switcher.html`](#theme-switcherhtml) | Selects and refreshes system, light, dark, or custom theme mode. | `setMode()`<br>`refresh()` | No component-specific event | Preference/native appearance behavior mixed; no component-ready contract |
115
+ | [`theme-switcher.html`](#theme-switcherhtml) | Selects and refreshes system, light, dark, or custom theme mode with per-instance choices and labels. | `configure()`<br>`setMode()`<br>`refresh()` | No component-specific event | Preference/native appearance behavior mixed; no component-ready contract |
116
116
  | [`unified-inbox.html`](#unified-inboxhtml) | Displays provider-neutral communication threads with active/loading state. | `configure()`<br>`setThreads()`<br>`setActive()`<br>`setLoading()`<br>`destroy()` | `unified-inbox-ready`<br>`inbox-refresh`<br>`thread-select` | DOM-normalized |
117
117
  | [`voice-transcription.html`](#voice-transcriptionhtml) | Records manual clips or optional continuous microphone audio with pause/periodic segments, ordered transcription and saves, retained failures, and complete transcript delivery. | `configure()`<br>`requestSTTActivation()`<br>`startRecording()`<br>`stopRecording()`<br>`retryTranscription()`<br>`cancelRecording()`<br>`save()`<br>`completeTranscription()/complete()`<br>`clear()`<br>`reset()`<br>`destroy()` | `voice-transcription-ready`<br>`voice-transcription-state`<br>`voice-transcription-segment`<br>`voice-transcription-empty`<br>`voice-transcription-error`<br>`voice-transcription-change`<br>`voice-transcription-complete`<br>`speech-transcription-complete`<br>`speech-transcription-cancelled`<br>`speech-stt-activation-request`<br>`speech-stt-activation-error` | Sticky STT readiness, capture/queue lifecycle, retry, cancellation, and complete state/text are normalized; media/provider behavior remains external |
118
118
  | [`weather-widget.html`](#weather-widgethtml) | Displays normalized current and daily weather with refresh intent. | `setWeather()`<br>`clear()`<br>`destroy()` | `weather-widget-ready`<br>`weather-refresh` | Display normalized; provider supplied externally |
@@ -2015,11 +2015,61 @@ Shared dependencies: [`Theme.js`](runtime-entities.md#themejs).
2015
2015
 
2016
2016
  ### Overview
2017
2017
 
2018
- Selects and refreshes system, light, dark, or custom theme mode.
2018
+ Selects and refreshes system, light, dark, or custom theme mode. Native buttons
2019
+ retain complete readable labels and wrap when their component runs out of room,
2020
+ independently of viewport width. Each button has a `2.75rem` minimum target in
2021
+ both dimensions. Selected buttons retain `aria-pressed` and an inset outline;
2022
+ disabled choices and keyboard focus have distinct presentation.
2019
2023
 
2020
2024
  ### Public surface
2021
2025
 
2022
- Methods/properties: `setMode()`, `refresh()`.
2026
+ Methods/properties: `configure(options)`, `setMode(mode='system')`, `refresh()`.
2027
+
2028
+ `configure({modes,labels})` changes only this component's presentation:
2029
+
2030
+ - `modes` is an ordered array of the available identifiers `system`, `light`,
2031
+ `dark`, and `custom`. It selects both visible choices and DOM/keyboard order.
2032
+ Omitted `modes` retains the current presentation; an empty array displays no
2033
+ choices. Unknown or repeated identifiers throw `TypeError` before changing
2034
+ the presentation.
2035
+ - `labels` is a partial record of complete string labels keyed by those mode
2036
+ identifiers. Omitted labels keep their current text. Text is assigned through
2037
+ `textContent`, never interpreted as markup or reduced to initials. A label for
2038
+ a built-in mode that is not a string throws `TypeError`.
2039
+ - The return value is `{modes,labels}` containing independent mutable copies of
2040
+ the current presentation. Later changes to supplied/returned records do not
2041
+ reconfigure the component.
2042
+
2043
+ Label-only and unchanged-order configuration retain the existing buttons in
2044
+ place. An actual reorder restores keyboard focus to the previously focused
2045
+ button when that choice remains visible and enabled, without scrolling.
2046
+
2047
+ The default remains Auto / Light / Dark / Skin, using identifiers
2048
+ `system`, `light`, `dark`, and `custom`. A caller may omit Skin or change Auto's
2049
+ label to System without removing custom-theme support from other instances.
2050
+ Configuration never changes preferences, activates a theme, or replaces the
2051
+ current selected mode. If the active mode is omitted, no visible button claims
2052
+ to be selected. Restoring that choice restores its existing pressed and disabled
2053
+ state. Skin remains disabled until the manager has a saved custom theme.
2054
+
2055
+ For the initial presentation, `data-modes` accepts whitespace-separated mode
2056
+ identifiers and `data-system-label` supplies the complete system-mode label.
2057
+ These attributes are read once before the asynchronous ThemeManager import and
2058
+ preference refresh, so initial choices do not wait for `html-import-ready`.
2059
+ Use `configure()` after that event for subsequent presentation changes;
2060
+ changing these attributes later does not reconfigure an existing instance.
2061
+
2062
+ `setMode()` updates pressed state and the host's `data-mode` only. `refresh()`
2063
+ loads the existing ThemeManager state, updates selection and Skin availability,
2064
+ and resolves with the complete manager state. Clicking a choice uses the same
2065
+ manager persistence/activation operation; buttons remain disabled while that
2066
+ operation is pending. Presentation configuration preserves that pending state.
2067
+
2068
+ The host inherits text color. Existing `--app-bar-border` (falling back to
2069
+ `--border-color`), `--app-bar-accent` (then `--secondary-color`),
2070
+ `--app-bar-active-text` (then `--primary-color`), and `--focus-color` overrides
2071
+ remain supported. Applications own their palette and surrounding placement;
2072
+ the component adds no application-specific palette or settings section.
2023
2073
 
2024
2074
  This fragment declares no component-specific readiness or action event; use the loader's `html-import-ready` only to know that import execution completed.
2025
2075
 
@@ -2033,11 +2083,18 @@ Shared dependencies: [`ThemeManager.js`](runtime-modules.md#thememanagerjs).
2033
2083
 
2034
2084
  ```html
2035
2085
  <html-import
2036
- id="theme-switcher.html"
2037
- href="/arcane/components/theme-switcher.html">
2086
+ id="theme-switcher"
2087
+ href="/node_modules/arcane-os/runtime/arcane/components/theme-switcher.html"
2088
+ data-modes="light dark system"
2089
+ data-system-label="System">
2038
2090
  </html-import>
2039
2091
  ```
2040
2092
 
2093
+ This instance displays Light / Dark / System in that order. An instance without
2094
+ the two configuration attributes retains all four default choices. Both use
2095
+ the existing shared preference owner; presentation does not create a new store
2096
+ or migrate saved themes.
2097
+
2041
2098
  ## unified-inbox.html
2042
2099
 
2043
2100
  ### Overview
@@ -120,7 +120,7 @@ own asynchronous work, cancellation, and backpressure.
120
120
  | [`OllamaSettings.js`](#ollamasettingsjs) | esm | Defines complete runtime/service preference schemas and deterministic Arcane brain alias names. | Cross-host | Fully normalized settings/name contract. |
121
121
  | [`OpenMeteoWeatherProvider.js`](#openmeteoweatherproviderjs) | esm | Searches and loads Open-Meteo data into complete mutable Arcane weather entities. | Browser / native WebView / server with fetch + cloud | Provider data normalized to mutable entities; transport errors mixed. |
122
122
  | [`PersistentAIChatSession.js`](#persistentaichatsessionjs) | esm | Adds explicit retained-history/memory policy to complete configured chat without changing DBOPFS or ChatEntity semantics. | Browser / native WebView with DBOPFS and configured chat | Retained context commits atomically; `persist:false` turns are one-operation-only. |
123
- | [`PreferenceStore.js`](#preferencestorejs) | esm | Loads and updates schema-defined app preferences through native storage with a narrow browser fallback. | Browser/native hybrid | Complete ordinary values remain mutable; setAll uses one optional atomic adapter batch for every selected value when advertised, otherwise performs complete ordered serial writes, and only exact unsupported native capability changes future operations to the browser fallback. |
123
+ | [`PreferenceStore.js`](#preferencestorejs) | esm | Loads and updates schema-defined app preferences through native storage with a narrow browser fallback. | Browser/native hybrid | Complete values and operation context retained; exact optional namespace absence or existing Android unsupported capability selects the existing local adapter; partial-service and genuine failures remain errors; an advertised rejected batch is never retried serially. |
124
124
  | [`PreparedSpeech.js`](#preparedspeechjs) | esm | Owns detached ordered preparation, semantic audio reuse, and per-caller cancellation behind AI.prepareTTS. | Browser / native WebView with injected synthesis and optional DBOPFS | Complete original inputs, ordered audio metadata, durable reuse, and observable preparation results. |
125
125
  | [`PrintView.js`](#printviewjs) | esm | Prints current rendered content and title through one document-owned print lifecycle. | Browser / supported native WebView print implementation | Complete rendered snapshot, preparation cancellation, resource lifetime, and honest request result. |
126
126
  | [`QRCode.min.js`](#qrcodeminjs) | classic-script | Vendored QRCode generator for DOM, canvas, SVG, and image output. | Browser vendor script | Vendor-native. |
@@ -134,7 +134,7 @@ own asynchronous work, cancellation, and backpressure.
134
134
  | [`ScreenCapture.js`](#screencapturejs) | esm | Captures a display surface as image, video, or GIF with explicit lifecycle events. | Browser / native WebView | State/events normalized; permission and codec errors mixed. |
135
135
  | [`SpeechPlayback.js`](#speechplaybackjs) | esm | Admits complete speech segments to a capacity-advertising provider immediately, retains serialized native/custom lookahead, and plays every result in exact indexed order. | Browser + native bridge | Stored part text stays exact; the outbound speech-input copy receives automatic formatting-mark cleanup; provider/media failures remain mixed. |
136
136
  | [`StaticDocumentCatalog.js`](#staticdocumentcatalogjs) | esm | Loads a positive static document inventory with cache, search, and complete context. | Browser / native WebView / server with fetch | Mutable complete catalog/content normalization; malformed data and transport failures remain visible. |
137
- | [`SystemAppearance.js`](#systemappearancejs) | esm | Reads or applies native appearance, returning an explicit unsupported browser state when no bridge exists. | Browser/native hybrid | Absent bridge normalized; native result/error preserved. |
137
+ | [`SystemAppearance.js`](#systemappearancejs) | esm | Reads or applies native appearance, returning an explicit unsupported result when the bridge or optional namespace is absent. | Browser/native hybrid | Exact absent optional namespace normalized with its complete error; partial-method and genuine native errors preserved. |
138
138
  | [`SystemPlatformPresentation.js`](#systemplatformpresentationjs) | classic-script | Maps kernel names to presentation labels/classes without granting platform authority. | Browser / native WebView classic script | Fully normalized presentation only. |
139
139
  | [`SystemToolRegistry.js`](#systemtoolregistryjs) | esm | Registers validated command builders and constructs command strings without executing them. | Cross-host | Fully normalized definitions/quoting. |
140
140
  | [`TerminalClient.js`](#terminalclientjs) | esm | Maps native terminal sessions and Arcane events into an EventTarget client. | Native bridge | Client events/state normalized; native result/error mixed. |
@@ -3475,9 +3475,31 @@ Exact exports: `PREFERENCE_STORE_ERROR_CODES`,
3475
3475
 
3476
3476
  **Browser/native hybrid.** Complete ordinary values and returned snapshots remain
3477
3477
  mutable after schema normalization. Non-Android
3478
- `Arcane.preferences.setMany()` supplies the optional atomic batch. Only exact
3479
- unsupported native capability changes future operations to app-scoped
3480
- localStorage; an in-flight advertised batch is never downgraded after rejection.
3478
+ `Arcane.preferences.setMany()` supplies the optional atomic batch. The default
3479
+ adapter uses the existing app-scoped localStorage owner when there is no native
3480
+ preferences facade, on the existing Android route, or after either the existing
3481
+ `ANDROID_CAPABILITY_UNSUPPORTED` error or an exact Core lookup error:
3482
+ `code:'METHOD_NOT_ALLOWED'`, `reason:'core-namespace-unavailable'`,
3483
+ `namespace:'preferences'`, and `method:'preferences.<current operation>'`.
3484
+ This marker proves the entire optional namespace was absent before dispatch.
3485
+ A missing method in a partial/read-only service, an older unmarked error,
3486
+ permission denial, persistence failure, transport error, or cancellation never
3487
+ selects local storage. Explicit caller adapters retain their own behavior.
3488
+
3489
+ For `get`, `set`, and `delete`, that positively absent namespace retries the
3490
+ same operation once through the existing local adapter, then keeps that adapter
3491
+ for the store's later operations. Complete arguments and the existing
3492
+ `{operationId,signal}` context are retained. Disposal and caller cancellation
3493
+ are checked before selecting or invoking the local adapter, so a late native
3494
+ absence result cannot start a cancelled local write. No data location, schema,
3495
+ saved value, or migration is introduced.
3496
+
3497
+ An in-flight advertised `setMany` batch is never downgraded to serial writes
3498
+ after rejection. Exact namespace absence selects the local adapter for later
3499
+ operations and preserves that batch's original rejection with no local write.
3500
+ ThemeManager's first Light/Dark/System choice uses two ordinary `set` calls,
3501
+ not a batch: both may complete through the existing local owner before the
3502
+ selected theme is applied. Its persistence-first ordering is unchanged.
3481
3503
  If cancellation settles after a native batch was dispatched, reload the store to
3482
3504
  reconcile any atomic host commit that completed before cancellation. Transport:
3483
3505
  Arcane.preferences or app-scoped localStorage. [Deep protocol details](protocols.md).
@@ -4064,7 +4086,8 @@ console.log(Object.keys(module));
4064
4086
 
4065
4087
  ### Overview
4066
4088
 
4067
- Reads or applies native appearance, returning an explicit unsupported browser state when no bridge exists.
4089
+ Reads or applies native appearance, returning an explicit unsupported result
4090
+ when the bridge or its optional namespace is absent.
4068
4091
 
4069
4092
  ### Public surface
4070
4093
 
@@ -4074,7 +4097,21 @@ Exact exports: `default`.
4074
4097
 
4075
4098
  ### Availability and normalization
4076
4099
 
4077
- **Browser/native hybrid.** Absent bridge normalized; native result/error preserved. Transport: Arcane.appearance. [Deep protocol details](protocols.md).
4100
+ **Browser/native hybrid.** No bridge or callable method retains the existing
4101
+ `{supported:false,platform:'browser'}` result. A callable facade is not proof of
4102
+ an installed service: `available()` reports callable `apply` presence only.
4103
+ `current()` and `apply()` recognize only a matching Core
4104
+ `METHOD_NOT_ALLOWED` error with `reason:'core-namespace-unavailable'`,
4105
+ `namespace:'appearance'`, and the exact requested method. They return
4106
+ `{supported:false,reason:'core-namespace-unavailable',error}`, retaining the
4107
+ complete original error for diagnostics and making no platform or native
4108
+ application claim. Absence is not cached, so later service registration can
4109
+ serve the next operation. Partial-method absence, unmarked permission errors,
4110
+ transport/cancellation failures and all other native errors reject unchanged.
4111
+ Native success values and existing appearance input normalization remain
4112
+ unchanged. ThemeManager may therefore complete a browser color-scheme change
4113
+ when native appearance is explicitly unsupported, while genuine failures stay
4114
+ observable. Transport: Arcane.appearance. [Deep protocol details](protocols.md).
4078
4115
 
4079
4116
  ### Example
4080
4117