arcane-os 0.88.1 → 0.90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/browser-runtime/ai/browser-decisions.mjs +31 -4
  3. package/browser-runtime/ai/core-local.mjs +105 -25
  4. package/browser-runtime/ai/decision-runtime.mjs +7 -0
  5. package/browser-runtime/ai/decision-worker.mjs +9 -1
  6. package/browser-runtime/core/client.mjs +6 -0
  7. package/browser-runtime/core/contracts.mjs +10 -0
  8. package/docs/reference/ai/browser-decisions.md +89 -7
  9. package/docs/reference/core-client.md +59 -0
  10. package/docs/reference/core-native-packaging.md +62 -10
  11. package/docs/reference/core-repositories.md +104 -8
  12. package/docs/reference/git-text-snapshot.md +16 -1
  13. package/docs/reference/inventory/package-api.json +25 -25
  14. package/docs/reference/inventory/runtime-components.json +1 -1
  15. package/docs/reference/inventory/runtime-modules.json +4 -4
  16. package/docs/reference/local-ai.md +108 -9
  17. package/docs/reference/mcp-stdio.md +76 -1
  18. package/docs/reference/native-app-control.md +17 -5
  19. package/docs/reference/protocols.md +14 -0
  20. package/docs/reference/runtime-modules.md +9 -3
  21. package/docs/reference/sdk-api.md +202 -35
  22. package/package.json +2 -2
  23. package/runtime/arcane/modules/RiskSignalAnalyzer.js +1 -5
  24. package/schemas/arcane-app.schema.json +13 -3
  25. package/schemas/arcane-package.schema.json +10 -1
  26. package/src/app-descriptor.mjs +6 -2
  27. package/src/cli/app-control.mjs +1 -1
  28. package/src/core/hosts/windows/ArcaneHost.cs +273 -5
  29. package/src/core/hosts/windows/ArcaneLauncher.cs +9 -0
  30. package/src/core/hosts/windows/ArcaneWindowControl.cs +7 -7
  31. package/src/core/repositories.mjs +17 -6
  32. package/src/core/services/local-ai.mjs +183 -22
  33. package/src/git-text-snapshot.mjs +4 -2
  34. package/src/installed-sdk-runtime.mjs +2 -0
  35. package/src/local-ai/config.mjs +16 -0
  36. package/src/local-ai/dev.mjs +2 -2
  37. package/src/local-ai/discover.mjs +3 -0
  38. package/src/local-ai/install.mjs +130 -20
  39. package/src/local-ai/llama-execution-windows.ps1 +158 -0
  40. package/src/local-ai/llama-execution.mjs +169 -0
  41. package/src/local-ai/server.mjs +38 -8
  42. package/src/native/portable-layout.mjs +13 -3
  43. package/src/repository.mjs +33 -8
  44. package/src/workspace-runtime.mjs +97 -1
@@ -981,7 +981,7 @@
981
981
  ],
982
982
  "availability": "Browser and supported native WebViews",
983
983
  "transport": "HTMLImport + DOM; injected Arcane/provider modules where listed",
984
- "normalization": "Preference/native appearance behavior mixed; selected button background, text and outline remain visible during hover while the hover fill applies only to unselected enabled buttons; no component-ready contract"
984
+ "normalization": "Preference/native appearance behavior mixed; selected button background, text and outline remain visible during hover while the hover fill applies only to unselected enabled buttons; no component-ready contract; pending choices expose aria-disabled and ignore repeated activation while staying in the keyboard focus flow; completion and failure do not move focus, and an unavailable Skin choice retains native disabling"
985
985
  },
986
986
  {
987
987
  "file": "runtime/arcane/components/unified-inbox.html",
@@ -30,8 +30,8 @@
30
30
  "summary": "Owns provider-selectable chat and the one-time caller-authority browser STT/TTS configuration, lifecycle, synthesis, transcription, and playback boundary.",
31
31
  "availability": "Browser + native bridge + TWiN Cloud",
32
32
  "protocol": "arcane-ai-browser-speech-configuration/1, AIProviderRuntime arcane-ai-provider/2 routes, globalThis.arcaneEvents, TWiN Cloud HTTPS, Arcane.ollama, Arcane.speech, Android WebView bridge",
33
- "normalization": "Complete mutable caller-owned browser speech authority, SDK-owned provider registration/replacement/disposal, explicit STT/TTS activation, normalized Whisper STT and Kokoro TTS execution with default capacities 1 and 4 respectively, explicit provider-neutral execution snapshots through status(role,{execution:true}), sticky readiness, canonical window.user readiness without compatibility-event object-identity admission or polling, exact ordered structural calls with required arguments.message, atomic nonblank all-ID tool-result sequencing, complete all-choice streaming/data validation, native Ollama structural adaptation, complete-response then all validated tool callbacks then completion ordering, observed async callbacks, Blob/File transcription, default repeated-formatting-mark removal from cloned outbound TTS input, explicit textFormat:'plain' preservation for complete synthesis/preparation while streaming cleanup remains automatic and caller/visible content stays exact, immediate exact-segment synthesis, per-call voice/speed capture, optional final-segment pause on the existing audio clock, optional per-call terminal playback completion, detached preparation with complete semantic DBOPFS audio reuse and same-owner pending request sharing; each preparation owns its pending activation interest, final-interest cancellation aborts that activation while other interests preserve it, complete cache hits start no model, and cancellation after readiness leaves the ready model loaded; independent prepared playback without a model load for cached audio, ordered audio-clock scheduling, playable audio, active-generation TTS operation-failure routing, mute, and cancellation are normalized; provider/model/runtime/voice policy remains caller-owned. Both speech roles default execution.device to auto: webnn-npu when navigator.ml.createContext is exposed, then webgpu when navigator.gpu is exposed, then wasm for CPU execution. Failed backend loads clean up the pool before trying fresh Workers with the same prepared model and dtype; explicit devices never fall back. STT capacity only accepts 1; TTS accepts 1 through 4. Execution snapshots expose requestedDevice, selectedDevice, maxConcurrentRequests, and activeRequestCount for both roles; selectedDevice is null while unloaded and names the successful upstream session backend request when loaded. It does not prove every operation used the physical NPU: WebNN unsupported operations may use WASM, and compatibility depends on the browser, driver, hardware, and graph.",
34
- "surface": "Native fetchTTS(payload,signal) and fetchSTT(audio,signal) forward caller cancellation separately from their complete Core speech payloads, normalize aborts to AbortError and suppress late results; actual engine interruption remains service-owned. prepareTTSPlayback(payload,signal) silently returns a real Blob or native-speech descriptor; native controls expose finished/released/state/error/pause/resume/stop, and actual release owns the playback lane. fetchTTS and durable prepareTTS report ARCANE_AI_TTS_AUDIO_EXPORT_UNAVAILABLE for native voices. Request-local model overrides remain exact and never mutate the configured model. Browser-speech protocol/event/error/reason constants; default `AI`; read-only `providerRuntime`, `browserSpeechConfiguration`, and `browserSpeechDescriptor`; explicit `providerRuntime.status(role,{execution:true})` snapshots; `streamTTS(text,end,options={})` with optional voice, speed, pauseAfterMs, and waitForPlayback; default speech-input cleanup with SDK-internal preparation metadata and explicit complete-call textFormat:'plain' consumed before engine dispatch; `prepareTTS({parts,storage,identity,textFormat,signal,onState})` returning ordered segments, state, ready, getAudio(index), and cancel(); async `readPreparedTTS({parts,textFormat,storage,identity,signal,onState})` returning a complete ready handle or null through the same semantic matcher, with no activation, unmute, synthesis, playback, storage writes, or partial repair; manifest, audio availability, and returned-handle reads use DBOPFS createTable:false so absent tables remain absent; supplied adapters own that read-existing behavior; missing matches/audio return null, malformed metadata and storage failures reject, and omitted storage sees only completed matching same-AI in-memory audio; hits replay ready state, misses emit nothing, and the lookup signal detaches on settlement while returned-handle cancellation stays independent; `prepareAudioOutput()` returning the persistent WebAudio handle {kind,state,contextState,error,subscribe,resume,dispose} without resume, unmute, model load, sources, sound, or gesture listeners; current-state replay and output-specific canonical `AI_AUDIO_OUTPUT_STATE_EVENT` (`ai-audio-output-state`), with ready only for an actually running context; explicit owner disposal observes context-bound cleanup and close; `playPreparedTTS(prepared,{signal,onState,audioOutput})` returning state, error, finished, pause(), resume(), and stop() with one playback lane per AI and observational state/error callbacks; explicit live same-AI audioOutput borrows its context until owner disposal, while omission or null preserves per-playback terminal close; configure/dispose speech, route lifecycle, declaration-validated chat/stream requests with exact ordered calls; optional streamRequest toolText:{name,field} and onToolText(text,call,displayId) for decoded selected argument text with actual call identity, distinct from ordinary prose and terminal tool execution; synthesis/transcription and playback controls; initializes from current canonical user readiness, installs `window.ai`, and projects `ai-ready` plus active-generation `ai-tts-failure`."
33
+ "normalization": "Complete mutable caller-owned browser speech authority, SDK-owned provider registration/replacement/disposal, explicit STT/TTS activation, normalized Whisper STT and Kokoro TTS execution with default capacities 1 and 4 respectively, explicit provider-neutral execution snapshots through status(role,{execution:true}), sticky readiness, canonical window.user readiness without compatibility-event object-identity admission or polling, exact ordered structural calls with required arguments.message, atomic nonblank all-ID tool-result sequencing, complete all-choice streaming/data validation, native Ollama structural adaptation, complete-response then all validated tool callbacks then completion ordering, observed async callbacks, Blob/File transcription, default repeated-formatting-mark removal from cloned outbound TTS input, explicit textFormat:'plain' preservation for complete synthesis/preparation while streaming cleanup remains automatic and caller/visible content stays exact, immediate exact-segment synthesis, per-call voice/speed capture, optional final-segment pause on the existing audio clock, optional per-call terminal playback completion, detached preparation with complete semantic DBOPFS audio reuse and same-owner pending request sharing; each preparation owns its pending activation interest, final-interest cancellation aborts that activation while other interests preserve it, complete cache hits start no model, and cancellation after readiness leaves the ready model loaded; independent prepared playback without a model load for cached audio, ordered audio-clock scheduling, playable audio, active-generation TTS operation-failure routing, mute, and cancellation are normalized; provider/model/runtime/voice policy remains caller-owned. Both speech roles default execution.device to auto: webnn-npu when navigator.ml.createContext is exposed, then webgpu when navigator.gpu is exposed, then wasm for CPU execution. Failed backend loads clean up the pool before trying fresh Workers with the same prepared model and dtype; explicit devices never fall back. STT capacity only accepts 1; TTS accepts 1 through 4. Execution snapshots expose requestedDevice, selectedDevice, maxConcurrentRequests, and activeRequestCount for both roles; selectedDevice is null while unloaded and names the successful upstream session backend request when loaded. It does not prove every operation used the physical NPU: WebNN unsupported operations may use WASM, and compatibility depends on the browser, driver, hardware, and graph. High-level LLM selection defaults to eager activation; startLanguageModel:false preserves the selected tuple and registered adapters without preloading through later readiness callbacks. Caller cancellation waits for accepted old-role cleanup and prevents later configuration/preload, or reaches the first owned load after selection commits. Newer accepted selections supersede older continuations; deferred or cancelled activation stays inactive until explicit load/startup, and a caller abort after successful settlement never unloads the ready model. Separate callers joining an existing runtime load retain their current cancellation ownership.",
34
+ "surface": "Native fetchTTS(payload,signal) and fetchSTT(audio,signal) forward caller cancellation separately from their complete Core speech payloads, normalize aborts to AbortError and suppress late results; actual engine interruption remains service-owned. prepareTTSPlayback(payload,signal) silently returns a real Blob or native-speech descriptor; native controls expose finished/released/state/error/pause/resume/stop, and actual release owns the playback lane. fetchTTS and durable prepareTTS report ARCANE_AI_TTS_AUDIO_EXPORT_UNAVAILABLE for native voices. Request-local model overrides remain exact and never mutate the configured model. Browser-speech protocol/event/error/reason constants; default `AI`; read-only `providerRuntime`, `browserSpeechConfiguration`, and `browserSpeechDescriptor`; explicit `providerRuntime.status(role,{execution:true})` snapshots; `streamTTS(text,end,options={})` with optional voice, speed, pauseAfterMs, and waitForPlayback; default speech-input cleanup with SDK-internal preparation metadata and explicit complete-call textFormat:'plain' consumed before engine dispatch; `prepareTTS({parts,storage,identity,textFormat,signal,onState})` returning ordered segments, state, ready, getAudio(index), and cancel(); async `readPreparedTTS({parts,textFormat,storage,identity,signal,onState})` returning a complete ready handle or null through the same semantic matcher, with no activation, unmute, synthesis, playback, storage writes, or partial repair; manifest, audio availability, and returned-handle reads use DBOPFS createTable:false so absent tables remain absent; supplied adapters own that read-existing behavior; missing matches/audio return null, malformed metadata and storage failures reject, and omitted storage sees only completed matching same-AI in-memory audio; hits replay ready state, misses emit nothing, and the lookup signal detaches on settlement while returned-handle cancellation stays independent; `prepareAudioOutput()` returning the persistent WebAudio handle {kind,state,contextState,error,subscribe,resume,dispose} without resume, unmute, model load, sources, sound, or gesture listeners; current-state replay and output-specific canonical `AI_AUDIO_OUTPUT_STATE_EVENT` (`ai-audio-output-state`), with ready only for an actually running context; explicit owner disposal observes context-bound cleanup and close; `playPreparedTTS(prepared,{signal,onState,audioOutput})` returning state, error, finished, pause(), resume(), and stop() with one playback lane per AI and observational state/error callbacks; explicit live same-AI audioOutput borrows its context until owner disposal, while omission or null preserves per-playback terminal close; configure/dispose speech, route lifecycle, declaration-validated chat/stream requests with exact ordered calls; optional streamRequest toolText:{name,field} and onToolText(text,call,displayId) for decoded selected argument text with actual call identity, distinct from ordinary prose and terminal tool execution; synthesis/transcription and playback controls; initializes from current canonical user readiness, installs `window.ai`, and projects `ai-ready` plus active-generation `ai-tts-failure`. `setAI(llmService,sttService,ttsService,model,modelTTS,modelSTT,options)` and `transitionAI(llmService,sttService,ttsService,model,modelTTS,modelSTT,options)` preserve six positional preferences; `configureProviders(selections,options)` and `transitionProviders(selections,options)` accept the same optional {signal:null,startLanguageModel:true} defaults. setAI retains its synchronous boolean and configureProviders its synchronous route result. Explicit `providerRuntime.load('llm',{signal})` or `startProviders({startLanguageModel:true,signal})` activates a deferred selected route."
35
35
  },
36
36
  {
37
37
  "file": "runtime/arcane/modules/AIModelSelectionController.js",
@@ -133,7 +133,7 @@
133
133
  "summary": "Sticky mutable LLM, speech-to-text, and text-to-speech lifecycle snapshots, intents, subscriptions, and startup-settlement barriers.",
134
134
  "availability": "Cross-host in-process state contract; actual role readiness remains provider specific",
135
135
  "protocol": "arcane-ai-runtime-state/1 over the per-realm globalThis.arcaneEvents authority",
136
- "normalization": "Closed monotonic sticky role snapshots, transient lifecycle intents, synchronous current-state replay, AbortSignal cleanup, and startup settlement are normalized; unloaded recovery/error may retain the exact busy operation until settlement, without readiness for new inference.",
136
+ "normalization": "Closed monotonic sticky role snapshots, transient lifecycle intents, synchronous current-state replay, AbortSignal cleanup, and startup settlement are normalized; unloaded recovery/error may retain the exact busy operation until settlement, without readiness for new inference. During unloading, an already loaded role may retain busy:true with its actual operationId while captured requests settle; this grants no readiness or new-request permission and is not a continuesAIRuntimeOperation recovery state.",
137
137
  "surface": "Protocol/event/role/state constants; snapshot, state subscribe/publish, intent request/subscribe, continuesAIRuntimeOperation(previous,current,operationId) for the real continuing runtime operation, and `startAIRuntime({startMuted,startTranscription,signal})` returning `{barrier,settled,cancel}`."
138
138
  },
139
139
  {
@@ -1201,7 +1201,7 @@
1201
1201
  "summary": "Matches configured risk signals and levels against complete text.",
1202
1202
  "availability": "Cross-host",
1203
1203
  "protocol": "In-process only",
1204
- "normalization": "Fully normalized.",
1204
+ "normalization": "Complete literal text; existing non-string conversion only.",
1205
1205
  "surface": "`DEFAULT_LEVELS`, `analyzeRiskSignals()`."
1206
1206
  },
1207
1207
  {
@@ -86,7 +86,8 @@ transport loss retain their owning cancellation behavior. A terminal engine
86
86
  failure reaches the original request as its actual error rather than a
87
87
  fabricated readiness success. See [native speech reconciliation](native-speech.md).
88
88
 
89
- `llamaCpp` accepts `url`, `model`, `modelsDirectory`, and an `args` string array.
89
+ `llamaCpp` accepts `url`, `model`, `alias`, `modelsDirectory`, `executionTarget`,
90
+ and an `args` string array.
90
91
  `model` selects a single GGUF file; `modelsDirectory` selects llama.cpp router
91
92
  mode. Paths resolve from the application root. Ollama accepts `url`,
92
93
  `modelsDirectory`, and `args`. The default endpoints are
@@ -94,6 +95,24 @@ mode. Paths resolve from the application root. Ollama accepts `url`,
94
95
  directories are `.arcane/models/llama.cpp` and `.arcane/models/ollama` inside the
95
96
  application. Applications choose and provision their models explicitly.
96
97
 
98
+ A llama runtime requirement accepts `backend: 'auto' | 'cpu' | 'cuda'`, with
99
+ `auto` preserving the ordinary platform distribution. Explicit `cpu` also
100
+ configures CPU execution, including on a Metal-capable macOS distribution.
101
+ `cuda` currently selects the official Windows x64 CUDA 13.4 engine and its
102
+ matching same-release CUDA companion archive. For example:
103
+
104
+ ```json
105
+ {"runtimes": [{"id": "llama.cpp", "backend": "cuda"}]}
106
+ ```
107
+
108
+ Development prepares this explicit distribution instead of treating an unknown
109
+ PATH executable or external listener as proof of its backend. Native packaging
110
+ bundles the selected runtime tree and companion libraries through the existing
111
+ runtime workflow. A custom CUDA `url` also needs its explicit `companionUrl`;
112
+ the SDK does not guess a companion location. Installation records distinguish
113
+ the distribution's `backend` from its `requestedBackend` execution mode.
114
+ Selecting an unavailable distribution reports its actual error.
115
+
97
116
  ## Development
98
117
 
99
118
  ```sh
@@ -119,10 +138,23 @@ Zstandard archives use the supported Node runtime's decompressor.
119
138
 
120
139
  Selected ONNX preparation installs `onnxruntime-node@1.30.0` by default into a
121
140
  managed npm prefix, retaining its complete published runtime dependency tree.
122
- It uses the upstream CPU installation option to skip supplementary GPU downloads.
141
+ The npm installation skips supplementary upstream GPU downloads. On Windows
142
+ x64, an omitted version or explicit `1.30.0`, with no explicit `url`, also
143
+ selects the SDK's corrected native distribution, revision
144
+ `1.30.0-directml-reshape-1`. The installer downloads
145
+ `arcane-onnx-runtime-windows-x64.tar.gz` from the installed `arcane-os` package's
146
+ numeric GitHub release and extracts its native libraries into the new
147
+ installation. Its URL is
148
+ `https://github.com/TheWizardNexus/arcane-os-sdk/releases/download/<sdk-version>/arcane-onnx-runtime-windows-x64.tar.gz`.
149
+ The same-version npm JavaScript and binding remain in place. Explicit URLs,
150
+ other ONNX versions and other platform/architecture selections retain their
151
+ upstream package path. For the corrected selection, an existing installation
152
+ without the matching `nativeDistributionRevision` is left intact and a fresh
153
+ installation is prepared; running installations keep their original trees.
123
154
  Node/npm must be available while preparing that runtime; a native app consumes
124
155
  the bundled tree without installing it again. Applications that omit ONNX do
125
- not install its package.
156
+ not install its package. Runtime preparation alone establishes no model or
157
+ device execution result.
126
158
 
127
159
  For the optional development browser connection, load the bootstrap after the
128
160
  managed import map and before creating a Core-backed provider:
@@ -226,7 +258,7 @@ background health polling.
226
258
  | --- | --- | --- |
227
259
  | `llama.status` | `{}` | Runtime state and model catalog |
228
260
  | `llama.models` | `{}` | `{models}` |
229
- | `llama.load` | `{model}` | State after the exact model reports loaded |
261
+ | `llama.load` | `{model,assetProjectionId?,resourcePaths?,executionTarget?}` | State after the exact model reports loaded |
230
262
  | `llama.unload` | `{model}` | Current state after release |
231
263
  | `llama.chat` | `{model,payload,stream?,streamId?}` | Complete OpenAI-shaped completion |
232
264
 
@@ -238,6 +270,36 @@ only after Core successfully closes its owned single-model server. Explicit
238
270
  load, inference or recovery can resume that engine; observation cannot.
239
271
  Status observation adds no background polling.
240
272
 
273
+ For a saved browser GGUF, prepare its complete file through the existing
274
+ [`model-assets` owner](model-assets.md), then call `llama.load` with its
275
+ `assetProjectionId` and `resourcePaths: {model: 'selected.gguf'}`. Core retains
276
+ that member before replacing its owned server, uses the exact `model` value as
277
+ the server alias, and releases the retained use only after actual process exit.
278
+ The preparation owner can release its use when loading settles. Replacement,
279
+ cancelled loading, unload and shutdown all preserve this lifetime. A cancelled
280
+ projected load joins process shutdown before returning. Ordinary router loads
281
+ retain their existing shared-load behavior.
282
+
283
+ `executionTarget: {deviceId}` uses the identifier from this computer's execution
284
+ device catalog. Windows CPU selection disables GPU offload; Windows NVIDIA GPU
285
+ selection requires the CUDA distribution and resolves the current adapter LUID
286
+ to its CUDA UUID. The new server sees that one physical GPU as `CUDA0`.
287
+ No machine-specific device ID is embedded in configuration examples. Each host
288
+ resolves its own saved selection; an absent or unsupported device reports an
289
+ error. Other host adapters remain subject to their declared catalog support.
290
+ `executionTarget: null` uses the configured runtime default. Omission preserves
291
+ an active selection's target. Runtime `execution` separates `requestedTarget`,
292
+ `resolvedDevice` and `configuredTarget`; `observedTarget: null` means actual
293
+ inference placement has not been observed. Configuration is not placement proof.
294
+
295
+ An external server keeps ownership of its model and device. Projected loading
296
+ or explicit target changes report `LOCAL_AI_EXTERNAL_SELECTION_UNSUPPORTED`
297
+ without stopping it or changing a working Core observation. `managed` describes
298
+ whether an installed executable can be managed at this listener; `owned` reports
299
+ an actual SDK-owned process. Recovery retains a live projection across process
300
+ replacement. If the projection was already released after exit, prepare it again;
301
+ resuming never substitutes another model or target.
302
+
241
303
  The `payload` is the complete request. SDK option names are translated only at
242
304
  the upstream protocol boundary; messages, tool definitions and documents remain
243
305
  unchanged. An explicit `payload.model` selects that request's model. Core waits
@@ -365,10 +427,44 @@ runtime still reconciles the provider at its existing operation boundaries.
365
427
  permits that load preflight even before its catalog arrives; Core's `llama.load`
366
428
  waits for startup and verifies the exact requested model. An owned, successfully
367
429
  released service permits loading only a model retained in its actual catalog.
368
- An available service still requires that catalog membership. These observations
430
+ With `prepareModel`, an installed `managed` service also admits an explicit
431
+ load before the requested model exists in its catalog. An available external
432
+ service still requires catalog membership. These observations
369
433
  do not fabricate model records or readiness, and unavailable external services,
370
434
  closed services and actual errors remain unavailable.
371
435
 
436
+ The optional `prepareModel({selection,signal,progress})` callback runs only at
437
+ an actual provider load. Return `{assetProjectionId,resourcePaths,executionTarget,release?}`;
438
+ direct `provider.load` fields override the corresponding callback
439
+ fields, including an explicit null target. The provider awaits `release()`
440
+ after Core loading settles and before reporting readiness. Preparation must
441
+ observe cancellation and settle so even a late returned projection is released.
442
+ Only a dispatched Core selection owns a native unload; cancelled preparation
443
+ alone does not unload another model.
444
+
445
+ ```js
446
+ import {prepareCoreModelAssets} from 'arcane-os/ai/core-model-assets';
447
+
448
+ // modelFile is the complete GGUF Blob/File read from the application's store.
449
+ // selectedDeviceId came from this host's execution-device catalog.
450
+ const provider = createCoreLocalAIProvider({
451
+ client,
452
+ async prepareModel({signal, progress}) {
453
+ const projection = await prepareCoreModelAssets({
454
+ client, workingDirectory: '.arcane/model-work', signal,
455
+ members: [{path: 'moon-raccoon.gguf', file: modelFile}],
456
+ onProgress: progress
457
+ });
458
+ return {
459
+ assetProjectionId: projection.id,
460
+ resourcePaths: {model: 'moon-raccoon.gguf'},
461
+ executionTarget: {deviceId: selectedDeviceId},
462
+ release: projection.release
463
+ };
464
+ }
465
+ });
466
+ ```
467
+
372
468
  `dispose()` removes installation and service subscriptions and cancels owned
373
469
  operations synchronously, then attempts its actually retained model's release
374
470
  through its captured client. A configured selection alone does not own a native
@@ -639,7 +735,7 @@ not execution evidence on each platform.
639
735
 
640
736
  ## Native bundling
641
737
 
642
- The SDK portable native build includes selected official runtime trees under
738
+ The SDK portable native build includes complete selected runtime trees under
643
739
  `runtime/local-ai/<runtime-id>`. Runtime records in the artifact are relative
644
740
  to the artifact root and resolve on the destination machine. The generated
645
741
  Core entry composes the local AI service only when the application selects it.
@@ -649,13 +745,16 @@ Model files remain application-selected native resources.
649
745
  `arcane-os/local-ai`. Both accept `{runtimes,directory,platform,architecture,
650
746
  signal,onEvent}`; bundling also requires `outputRoot`. Platform names follow
651
747
  Node: `win32`, `linux`, `darwin`, and supported Android distributions.
652
- Architectures are `x64` or `arm64`. Availability depends on the upstream
653
- runtime's published platform assets; Ollama has no official Android runtime
748
+ Architectures are `x64` or `arm64`. Availability depends on the selected
749
+ distribution's published platform assets; Ollama has no official Android runtime
654
750
  archive in this installer.
655
751
 
656
752
  Ensure returns absolute `{id,version,platform,architecture,root}` records.
657
753
  Server runtimes also include `executable`; ONNX includes `modulePath` pointing
658
- to its public package entry. Image records include `libraryPath`,
754
+ to its public package entry. The corrected Windows x64 ONNX selection also
755
+ includes `nativeDistributionRevision: '1.30.0-directml-reshape-1'`; bundling
756
+ retains that metadata with the complete native libraries. Image records include
757
+ `libraryPath`,
659
758
  `bindingModulePath`, requested `backend`, installed `variants` and selected
660
759
  `models` metadata. Variant records carry backend, runtime root and library path.
661
760
  Bundle returns `{runtimes,files}`,
@@ -75,7 +75,8 @@ Save this in the consuming application and configure the MCP client's subprocess
75
75
  command as `node` with that application's script path. Client configuration
76
76
  file formats and process launch policy remain client-owned. Change `menu` to
77
77
  change the complete result; no model, repository, or native host is needed for
78
- this example.
78
+ this example. A native package can instead use its
79
+ [existing bundled runtime](#launch-from-a-native-package).
79
80
 
80
81
  The application explicitly converts its domain record into an MCP result.
81
82
  The SDK does not guess from `content`, `message`, or other domain field names.
@@ -255,6 +256,80 @@ The adapter uses `-32700` for malformed JSON, `-32600` for invalid requests,
255
256
  `-32603` for internal failures, and `-32002` for a missing resource. Its
256
257
  `-32000` lifecycle error requests initialization before ordinary operations.
257
258
 
259
+ ## Launch from a native package
260
+
261
+ The generated `runtime/arcane-core.mjs` accepts
262
+ `--arcane-app-entry <app-relative-module>` to import an application-owned entry
263
+ instead of starting ordinary Core. Include the entry and its complete local
264
+ source closure in the application's `package.nativeResources`, for example:
265
+
266
+ ```json
267
+ {
268
+ "package": {
269
+ "nativeResources": {
270
+ "include": ["native", "src", "package.json"]
271
+ }
272
+ }
273
+ }
274
+ ```
275
+
276
+ Save the complete example above as `native/moon-mcp.mjs`. Native packaging copies
277
+ the selected files beneath the artifact's `app/` directory. It does not discover
278
+ entry dependencies from this runtime argument or execute the entry during
279
+ packaging. Public imports such as `arcane-os/mcp/stdio` use the artifact's
280
+ existing SDK package and Node's ordinary package resolution.
281
+
282
+ Configure the MCP client's command and argument array for the selected artifact:
283
+
284
+ | Artifact | Command | Arguments |
285
+ | --- | --- | --- |
286
+ | Windows | `<artifact-root>/runtime/ArcaneCore.exe` | `["--arcane-app-entry", "native/moon-mcp.mjs"]` |
287
+ | macOS | `<artifact-root>/runtime/node` | `["<artifact-root>/runtime/arcane-core.mjs", "--arcane-app-entry", "native/moon-mcp.mjs"]` |
288
+ | Portable | The host's supported Node executable | `["<artifact-root>/runtime/arcane-core.mjs", "--arcane-app-entry", "native/moon-mcp.mjs"]` |
289
+
290
+ These are placeholders for the actual native paths. For macOS, `artifact-root`
291
+ is `<app-id>.app/Contents/Resources`. The portable provider supplies no Node
292
+ executable. Windows uses its existing SEA executable and filesystem loader;
293
+ do not pass `arcane-core.mjs` as a script argument to that executable. These
294
+ commands select the runtime directly, not the GUI launcher, and create no
295
+ window or second Core host.
296
+
297
+ The module path resolves from the artifact's `app/`, independently of the
298
+ client's working directory. Native path separators, spaces, Unicode, `#` and
299
+ `%` retain their filesystem meaning through file-URL conversion. The original
300
+ `process.argv` remains complete and unchanged, including this flag, its path,
301
+ and every app argument. The flag is read from user arguments starting at index
302
+ 2 for both normal Node and SEA. A missing or empty value throws `TypeError`;
303
+ module-loading failures remain actual errors and never start ordinary Core.
304
+
305
+ This is a dynamic import: the entry runs its top-level code and observes its
306
+ own session lifetime, as the example's `server.start()` and `await server.closed`
307
+ do. Do not put its only startup behind `if (import.meta.main)`; the imported
308
+ module is not Node's main entry. The SDK calls no default export or factory.
309
+
310
+ App-entry mode skips ordinary Core launch-context loading, manifest service
311
+ composition, state-directory creation, local-AI startup, shared-host startup,
312
+ and Core signal handlers. If the application wants the existing explicit launch
313
+ JSON or host state-root arguments, append them to the same argument array and
314
+ read them deliberately in its entry:
315
+
316
+ ```javascript
317
+ import {readCoreLaunchContext} from 'arcane-os/core/host';
318
+
319
+ const context = await readCoreLaunchContext();
320
+ ```
321
+
322
+ For example, append `"--arcane-launch-config", "<selected-launch.json>"`.
323
+ Relative launch JSON paths retain their caller-working-directory meaning. The
324
+ application chooses defaults, services and any connection to an existing Core;
325
+ the entry selector neither reads nor applies the manifest's launch defaults.
326
+
327
+ The application owns process signals, graceful MCP drain, and its underlying
328
+ service shutdown. Keep stdout exclusively for MCP JSON-RPC and diagnostics on
329
+ stderr; the ordinary Core `arcane/1` transport must not share those MCP pipes.
330
+ When the flag is absent, ordinary Core startup and service lifecycle are
331
+ unchanged. See [native entry composition](core-native-packaging.md#app-owned-runtime-entry).
332
+
258
333
  ## Evidence boundary
259
334
 
260
335
  `test/mcp-stdio-server.test.mjs` contains focused in-memory stream fixtures for
@@ -73,11 +73,11 @@ connection or other business result.
73
73
  | Member | Contract |
74
74
  | --- | --- |
75
75
  | `connectAppControl({endpoint, signal?, onError?})` | Connect once to the explicit running host. It never launches an app, creates Core, changes a profile or reconnects automatically. |
76
- | `status(options?)` | Returns app identity, platform/host, current URL, document generation, readiness, window title/state/client dimensions and available operations. Readiness describes the document, not models or application services. |
76
+ | `status(options?)` | Returns app identity, platform/host, current URL, document generation, readiness, window title/state/client dimensions and available operations. Window state is `Normal`, `Maximized`, `Minimized`, or `Fullscreen`. Readiness describes the document, not models or application services. |
77
77
  | `inspect(parameters={}, options?)` | Optional `shadowPath` selects an open shadow root. Optional CSS `selector` selects zero or more elements within the selected root; omission selects the whole root. Optional `documentGeneration` selects the expected document. Returns complete HTML/text, live control state and DOM-derived role/name hints. |
78
78
  | `capture(parameters={}, options?)` | Returns `{mimeType:'image/png', data, documentGeneration, url}`. `data` contains the complete base64 PNG. Optional `documentGeneration` selects the expected document. |
79
79
  | `act(parameters, options?)` | Requires `documentGeneration`, one unambiguous CSS `selector` within the root selected by optional `shadowPath`, and the action fields below. |
80
- | `key(parameters, options?)` | Requires `documentGeneration` and `key:'Tab'`, `'Enter'`, or `'Space'`. Optional `shiftKey:true` is supported only for Tab. Sends one ordered press/release pair to the selected WebView's current focus. |
80
+ | `key(parameters, options?)` | Requires `documentGeneration` and `key:'Tab'`, `'Enter'`, `'Space'`, or `'Escape'`. Optional `shiftKey:true` is supported only for Tab. Sends one ordered press/release pair to the selected WebView's current focus. |
81
81
  | `resize(parameters, options?)` | Requires positive integral native client `width` and `height` supported by WinForms. Optional `documentGeneration` selects the expected document. Resizes only a Normal window and returns immediate previous/actual client dimensions plus the subsequently observed CSS viewport. |
82
82
  | `close()` | Closes this caller's connection and cancels its pending requests. It never closes the app or Core. Returns the connection's completion promise. |
83
83
  | `closed` | Connection lifetime promise; actual transport failures remain observable. |
@@ -207,6 +207,8 @@ const moved = await app.key({documentGeneration: page.documentGeneration, key: '
207
207
  console.log(moved.previous.focusPath, moved.actual.focusPath);
208
208
  await app.key({documentGeneration: moved.documentGeneration, key: 'Tab', shiftKey: true});
209
209
  await app.key({documentGeneration: moved.documentGeneration, key: 'Enter'});
210
+ const current = await app.inspect();
211
+ await app.key({documentGeneration: current.documentGeneration, key: 'Escape'});
210
212
  ```
211
213
 
212
214
  Keys use the selected WebView's current focus. No selector, shadow path, frame
@@ -214,7 +216,10 @@ selection, focus assignment or desktop activation is performed. Tab and
214
216
  Shift+Tab use a fixed Tab `rawKeyDown`/`keyUp` pair, with the Shift modifier on
215
217
  both events for Shift+Tab. Enter and Space use `keyDown` with their corresponding
216
218
  text, followed by `keyUp`, preserving browser default actions and application
217
- `preventDefault` handling. Shift+Tab does not send a separate Shift key pair.
219
+ `preventDefault` handling. Escape uses `key:'Escape'`, `code:'Escape'` and native
220
+ virtual key 27 in a `rawKeyDown`/`keyUp` pair, without text fields. It follows the
221
+ selected WebView's normal Escape handling; it does not promise that a particular
222
+ dialog closes. Shift+Tab does not send a separate Shift key pair.
218
223
  The API exposes these fixed keys, not an arbitrary protocol method or script.
219
224
 
220
225
  The result contains `key`, `shiftKey`, `previous` and `actual` document views,
@@ -264,7 +269,7 @@ origin. Read `viewport` for the resulting CSS dimensions instead of assuming a
264
269
  particular DPI or zoom conversion.
265
270
 
266
271
  Immediately before the UI-thread mutation, the host checks the actual
267
- `WindowState` and records `previous:{title,state,width,height}`. It records
272
+ window state and records `previous:{title,state,width,height}`. It records
268
273
  `actual` in the same shape immediately after the native setter, then observes
269
274
  the CSS `viewport` asynchronously. The result also contains `requested`,
270
275
  `resizeAttempted`, `resizeCompleted`, `documentGeneration` and `url`.
@@ -274,13 +279,20 @@ native snapshot and the document observation; these records are not an atomic
274
279
  native/CSS snapshot. Completion does not establish that asynchronous application
275
280
  resize handlers have settled.
276
281
 
277
- Maximized or minimized windows return `ARCANE_APP_CONTROL_FAILED` before any
282
+ Maximized, minimized or fullscreen windows return `ARCANE_APP_CONTROL_FAILED` before any
278
283
  size mutation, with `reason:'unsupported-window-state'`,
279
284
  `supportedWindowState:'Normal'` and the complete current `window` in the error's
280
285
  `data` entries. The host never automatically restores, maximizes or minimizes a
281
286
  window to satisfy this request. Invalid dimensions return `INVALID_ARGUMENT`.
282
287
  The same state rule applies to explicit restoration.
283
288
 
289
+ Native fullscreen reports `Fullscreen`, including while its underlying WinForms
290
+ state is Normal. A minimized fullscreen window reports `Minimized`. The
291
+ requesting document can select its state through
292
+ [`Arcane.window.setState({state})`](core-client.md#current-window-state); app-control
293
+ does not add a separate state-setting operation. That facade uses lowercase
294
+ state values, while app-control preserves its existing capitalized values.
295
+
284
296
  Restore using the actual `previous.width` and `previous.height` from the
285
297
  operation being reversed, never a historical status snapshot or CSS viewport
286
298
  dimensions. Restoration is an explicit second request; cancellation does not
@@ -475,6 +475,20 @@ the selected runtime content. The lock document describes the selected
475
475
  installation and projected roots; it is not a content-identity or admission
476
476
  record.
477
477
 
478
+ Both runtime materializers accept explicit `browserDecisions: true` to acquire
479
+ the upstream bundled Transformers.js 4.3.0 entry, matching ONNX Runtime
480
+ 1.31.0-dev.20260914-8d85527a0 asyncify module/WASM, and license files into
481
+ `arcane/sdk/ai/decisions-runtime/` before replacement. Default `false` performs
482
+ no optional acquisition. The complete selected files remain unchanged;
483
+ `workspace.decisions.started`/`workspace.decisions.progress` count completed
484
+ files. Cancellation, HTTP failures and observer failures join acquisition
485
+ cleanup and preserve the previous projection. Retain the option on each refresh
486
+ that needs the closure. The optional `browserDecisions` result describes its
487
+ directory, versions and filenames; installed materialization nests it under
488
+ `workspaceRuntime`. Model/tokenizer data remain upstream and cache-owned.
489
+ See [extension-local decisions](ai/browser-decisions.md#extension-local-executables-manifest-v3)
490
+ for the separate `runtime.local: true` browser selection.
491
+
478
492
  `upgradeApplication()` and `arcane upgrade` run the application's ordinary
479
493
  `npm upgrade` command. They do not independently reconcile the installed SDK
480
494
  projection, semantic Arcane lock, or managed import map.
@@ -128,7 +128,7 @@ own asynchronous work, cancellation, and backpressure.
128
128
  | [`RecordLinkIndex.js`](#recordlinkindexjs) | esm | Parses record links and builds their normalized index. | Cross-host | Fully normalized. |
129
129
  | [`RecordPassageIndex.js`](#recordpassageindexjs) | esm | Indexes text lines, page markers, dates, rules, and excerpts for record review. | Cross-host | Fully normalized. |
130
130
  | [`RecordReviewStore.js`](#recordreviewstorejs) | esm | Stores normalized record-review decisions through native storage or app-scoped local fallback. | Browser/native hybrid | Complete records preserved; unreadable stored content fails observably. |
131
- | [`RiskSignalAnalyzer.js`](#risksignalanalyzerjs) | esm | Matches configured risk signals and levels against complete text. | Cross-host | Fully normalized. |
131
+ | [`RiskSignalAnalyzer.js`](#risksignalanalyzerjs) | esm | Matches configured risk signals and levels against complete text. | Cross-host | Complete literal text; existing non-string conversion only. |
132
132
  | [`ScamRiskPolicy.js`](#scamriskpolicyjs) | esm | Combines deterministic scam signals with optional Arcane blocked-domain evidence and safety guidance. | Cross-host | Complete mutable results; blocked-domain policy requires `secure:true`. |
133
133
  | [`ScopedOPFSCache.js`](#scopedopfscachejs) | esm | Provides a narrow exact-key JSON cache inside one app-owned OPFS namespace. | Browser / native WebView | Filename-safe keys, complete JSON values, and malformed-cache cleanup normalized; storage errors mixed. |
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. |
@@ -1386,7 +1386,11 @@ transcription-model load.
1386
1386
  **Cross-host state contract.** States are `unavailable`, `unloaded`, `loading`,
1387
1387
  `recovering`, `ready`, `unloading`, `error`, and `disposed`. Recovery remains
1388
1388
  `loaded: false`; `recovering` and `error` may retain `busy: true` with an actual
1389
- `operationId` until the pending request settles. Revisions increase monotonically.
1389
+ `operationId` until the pending request settles. During `unloading`, an already
1390
+ loaded role may retain `busy: true` with its actual `operationId` while captured
1391
+ requests settle. That observation grants no readiness or permission for a new
1392
+ request and is not a `continuesAIRuntimeOperation` recovery state.
1393
+ Revisions increase monotonically.
1390
1394
  The events `arcane-ai-runtime-state`, `arcane-ai-runtime-intent`, and
1391
1395
  `arcane-ai-runtime-startup-settled` normalize observation only: receiving one
1392
1396
  does not grant a native capability, prove browser support, or load a provider.
@@ -4010,9 +4014,11 @@ Matches configured risk signals and levels against complete text.
4010
4014
 
4011
4015
  Exact exports: `DEFAULT_LEVELS`, `analyzeRiskSignals`.
4012
4016
 
4017
+ `analyzeRiskSignals(input, {signals, levels})` scans the complete `String(input ?? '')` without Unicode normalization or whitespace changes. Fullwidth characters and decomposed characters remain as supplied. Each signal's `RegExp.lastIndex` resets before testing; signal order, weights and selected levels determine the mutable `{level, matches, score, textLength}` result. `textLength` is the JavaScript string length after the existing non-string conversion; `null` and `undefined` become an empty string. Ordinary `ScamRiskPolicy.assessScamRisk()` uses this same literal-text path.
4018
+
4013
4019
  ### Availability and normalization
4014
4020
 
4015
- **Cross-host.** Fully normalized. Transport: In-process only. [Deep protocol details](protocols.md).
4021
+ **Cross-host.** Complete literal text; existing non-string conversion only. Transport: In-process only. [Deep protocol details](protocols.md).
4016
4022
 
4017
4023
  ### Example
4018
4024