arcane-os 0.88.1 → 0.89.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +21 -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 +57 -10
  11. package/docs/reference/core-repositories.md +47 -3
  12. package/docs/reference/inventory/package-api.json +15 -15
  13. package/docs/reference/inventory/runtime-components.json +1 -1
  14. package/docs/reference/inventory/runtime-modules.json +4 -4
  15. package/docs/reference/local-ai.md +86 -3
  16. package/docs/reference/mcp-stdio.md +76 -1
  17. package/docs/reference/native-app-control.md +17 -5
  18. package/docs/reference/protocols.md +14 -0
  19. package/docs/reference/runtime-modules.md +9 -3
  20. package/docs/reference/sdk-api.md +135 -23
  21. package/package.json +2 -2
  22. package/runtime/arcane/modules/RiskSignalAnalyzer.js +1 -5
  23. package/schemas/arcane-app.schema.json +13 -3
  24. package/schemas/arcane-package.schema.json +10 -1
  25. package/src/app-descriptor.mjs +6 -2
  26. package/src/cli/app-control.mjs +1 -1
  27. package/src/core/hosts/windows/ArcaneHost.cs +273 -5
  28. package/src/core/hosts/windows/ArcaneLauncher.cs +9 -0
  29. package/src/core/hosts/windows/ArcaneWindowControl.cs +7 -7
  30. package/src/core/repositories.mjs +11 -1
  31. package/src/core/services/local-ai.mjs +183 -22
  32. package/src/installed-sdk-runtime.mjs +2 -0
  33. package/src/local-ai/config.mjs +16 -0
  34. package/src/local-ai/dev.mjs +2 -2
  35. package/src/local-ai/discover.mjs +3 -0
  36. package/src/local-ai/install.mjs +130 -20
  37. package/src/local-ai/llama-execution-windows.ps1 +158 -0
  38. package/src/local-ai/llama-execution.mjs +169 -0
  39. package/src/local-ai/server.mjs +38 -8
  40. package/src/native/portable-layout.mjs +13 -3
  41. package/src/workspace-runtime.mjs +97 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.89.0
4
+
5
+ - Add explicitly selected clone-local author name/email before the first
6
+ checkout. Existing repositories and omitted settings retain their current
7
+ identity; Git author settings remain distinct from authenticated accounts.
8
+ - Add an app-owned entry point to the packaged native runtime. Applications
9
+ can launch their MCP module through that runtime with complete arguments,
10
+ while ordinary Core startup and app-owned shutdown remain intact.
11
+ - Add native window-state control and Escape app-control support. Use the
12
+ matching Windows host asset and rebuild consumer artifacts through their
13
+ existing public SDK workflow.
14
+ - Support opt-in extension-local decision runtime distribution and projected
15
+ native llama preparation with retained model lifetime and per-host target
16
+ selection. Model acquisition and application preferences remain with their
17
+ existing owners; CUDA acquisition requires its applicable dependency approval.
18
+ - Preserve literal Unicode and whitespace in RiskSignalAnalyzer input.
19
+ - Select the corrected portable Windows x64 ONNX 1.30.0 distribution through
20
+ the matching numeric SDK release. Include its complete native runtime closure
21
+ and notices in the separate retained runtime asset; keep native speech work
22
+ outside this release.
23
+
3
24
  ## 0.88.1
4
25
 
5
26
  - Recover an ordinary workspace-operation lock when its recorded runner is
@@ -88,16 +88,43 @@ class BrowserDecisionModel {
88
88
  throw new TypeError('store must expose fetchResource().');
89
89
  }
90
90
  this.#store = store;
91
+ const local = runtime.local ?? false;
92
+ if (!is.boolean(local)) throw new TypeError('runtime.local must be a boolean.');
91
93
  const moduleUrl = runtime.moduleUrl === undefined
92
- ? DEFAULT_RUNTIME_MODULE
94
+ ? local
95
+ ? new URL('./decisions-runtime/transformers.min.js', import.meta.url).href
96
+ : DEFAULT_RUNTIME_MODULE
93
97
  : runtime.moduleUrl;
98
+ const selectedRuntime = {...runtime, moduleUrl: requiredText(moduleUrl, 'runtime.moduleUrl')};
99
+ if (local) {
100
+ const base = new URL(import.meta.url);
101
+ function localExecutableUrl(value, name) {
102
+ const url = new URL(requiredText(value, name), base);
103
+ if (url.protocol !== base.protocol || url.host !== base.host) {
104
+ throw new TypeError(`${name} must use the decision Worker's local deployment in runtime.local mode.`);
105
+ }
106
+ return url.href;
107
+ }
108
+ const paths = runtime.wasmPaths ?? './decisions-runtime/';
109
+ const wasmPaths = is.string(paths)
110
+ ? {
111
+ mjs: new URL('ort-wasm-simd-threaded.asyncify.mjs', new URL(paths, base)).href,
112
+ wasm: new URL('ort-wasm-simd-threaded.asyncify.wasm', new URL(paths, base)).href
113
+ }
114
+ : paths;
115
+ selectedRuntime.moduleUrl = localExecutableUrl(moduleUrl, 'runtime.moduleUrl');
116
+ selectedRuntime.wasmPaths = {
117
+ mjs: localExecutableUrl(wasmPaths?.mjs, 'runtime.wasmPaths.mjs'),
118
+ wasm: localExecutableUrl(wasmPaths?.wasm, 'runtime.wasmPaths.wasm')
119
+ };
120
+ }
94
121
  this.#configuration = {
95
122
  family,
96
123
  model: requiredText(model, 'model'),
97
124
  revision: requiredText(revision, 'revision'),
98
125
  device: requiredText(device, 'device'),
99
126
  dtype: requiredText(dtype, 'dtype'),
100
- runtime: {...runtime, moduleUrl: requiredText(moduleUrl, 'runtime.moduleUrl')}
127
+ runtime: selectedRuntime
101
128
  };
102
129
  this.#events = createArcaneEventSource(
103
130
  this,
@@ -266,7 +293,7 @@ class BrowserDecisionModel {
266
293
  }
267
294
  const id = this.#nextId++;
268
295
  activation.pending.set(id, activation.load);
269
- if (this.#store) {
296
+ if (this.#store && !this.#configuration.runtime.local) {
270
297
  activation.runtimeController = new AbortController();
271
298
  // Retain the operation before a store progress callback can
272
299
  // synchronously unload this activation.
@@ -315,7 +342,7 @@ class BrowserDecisionModel {
315
342
  );
316
343
  } else {
317
344
  worker.postMessage(
318
- {id, op: 'load', payload: this.#configuration, storedResources: false}
345
+ {id, op: 'load', payload: this.#configuration, storedResources: this.#store !== null}
319
346
  );
320
347
  }
321
348
  } catch (error) {
@@ -22,17 +22,22 @@ function cancelled(reason) {
22
22
  * selects its model; Core owns the running engine and its loaded sessions.
23
23
  * Creating this provider neither connects a transport nor installs a runtime.
24
24
  */
25
- export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.cpp'} = {}) {
25
+ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.cpp', prepareModel} = {}) {
26
26
  if (!is.string(id) || !id.trim()) throw new TypeError('The Core AI provider id must be a nonempty string.');
27
27
  if (suppliedClient !== undefined && suppliedClient !== null
28
28
  && (!is.function(suppliedClient?.invoke) || !is.function(suppliedClient?.events?.on))) {
29
29
  throw new TypeError('The Core AI client must expose invoke and events.on.');
30
30
  }
31
+ if (prepareModel !== undefined && prepareModel !== null && !is.function(prepareModel)) {
32
+ throw new TypeError('Core AI model preparation must be a function.');
33
+ }
31
34
 
32
35
  let runtime = null;
33
36
  let models = [];
34
37
  let selection = null;
35
38
  let selectionConnection = null;
39
+ let acceptedLoad = null;
40
+ let retainedSelection = null;
36
41
  let disposed = false;
37
42
  let loading = false;
38
43
  let unloading = false;
@@ -75,7 +80,7 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
75
80
  }
76
81
 
77
82
  function status() {
78
- const loaded = !disposed && modelReady();
83
+ const loaded = !disposed && Boolean(acceptedLoad) && modelReady();
79
84
  return {
80
85
  state: disposed ? 'disposed' : loading ? 'loading' : loaded ? 'ready'
81
86
  : runtime?.error ? 'error' : runtime?.available === true ? 'unloaded' : 'unavailable',
@@ -86,6 +91,7 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
86
91
  modelId: selection?.modelId ?? null,
87
92
  available: !disposed && runtime?.available === true,
88
93
  installed: runtime?.installed === true,
94
+ execution: runtime?.execution ?? null,
89
95
  error: runtime?.error ?? null
90
96
  };
91
97
  }
@@ -113,6 +119,7 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
113
119
  lifecycleRevision += 1;
114
120
  loading = false;
115
121
  unloading = false;
122
+ acceptedLoad = null;
116
123
  runtime = {installed: false, available: false, state: client ? 'unknown' : 'unavailable', error};
117
124
  models = [];
118
125
  previous?.unsubscribe?.();
@@ -181,7 +188,9 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
181
188
  const starting = !runtime?.error && ['starting', 'loading'].includes(runtime?.state);
182
189
  const released = !runtime?.error && runtime?.released === true && runtime.owned === true
183
190
  && runtime.state === 'stopped' && knownModel;
184
- if (runtime?.error || (runtime?.available !== true && !starting && !released)) {
191
+ const preparable = is.function(prepareModel) && runtime?.installed === true && runtime.managed === true
192
+ && !['closed', 'closing'].includes(runtime.state);
193
+ if (runtime?.error || (runtime?.available !== true && !starting && !released && !preparable)) {
185
194
  return {
186
195
  available: false,
187
196
  code: 'ARCANE_AI_PROVIDER_UNAVAILABLE',
@@ -189,7 +198,7 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
189
198
  error: runtime?.error ?? null
190
199
  };
191
200
  }
192
- if (!starting && !knownModel) {
201
+ if (!starting && !knownModel && !preparable) {
193
202
  return {
194
203
  available: false,
195
204
  code: 'ARCANE_AI_MODEL_UNAVAILABLE',
@@ -206,31 +215,88 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
206
215
  }
207
216
  }
208
217
 
209
- async function load({selection: value, signal, progress} = {}) {
218
+ async function load({selection: value, signal, progress, assetProjectionId, resourcePaths, executionTarget} = {}) {
210
219
  const core = requireClient();
211
220
  const currentConnection = connection;
212
221
  requireSelection(value);
213
222
  if (signal?.aborted) throw cancelled(signal.reason);
223
+ loading?.controller?.abort(cancelled());
214
224
  selection = value;
215
225
  selectionConnection = currentConnection;
216
- const operation = {};
226
+ acceptedLoad = null;
227
+ const operation = ownRequest(signal, null, core);
228
+ let completed = false;
217
229
  loading = operation;
218
230
  unloading = false;
219
231
  lifecycleRevision += 1;
220
- try {
221
- progress?.({phase: 'loading', modelId: value.modelId});
222
- const revision = lifecycleRevision;
223
- const current = await invokeOwned(core, 'llama.load', {model: value.modelId}, signal);
224
- assertCurrentConnection(currentConnection);
225
- if (loading !== operation || selection !== value) throw cancelled();
226
- if (lifecycleRevision === revision) acceptRuntime(current);
227
- if (!modelReady()) throw localAIError('ARCANE_AI_MODEL_NOT_READY', 'Core did not report the selected model loaded and ready.', current?.error);
228
- progress?.({phase: 'ready', modelId: value.modelId});
232
+
233
+ function assertCurrentLoad() {
229
234
  assertCurrentConnection(currentConnection);
235
+ operation.controller.signal.throwIfAborted();
230
236
  if (loading !== operation || selection !== value) throw cancelled();
237
+ }
238
+
239
+ function reportProgress(event) {
240
+ if (disposed || connection !== currentConnection || operation.controller.signal.aborted
241
+ || loading !== operation || selection !== value) return;
242
+ progress?.(event);
243
+ }
244
+
245
+ operation.result = Promise.resolve().then(async function prepareAndLoadModel() {
246
+ let preparation;
247
+ let failed = false;
248
+ let failure;
249
+ try {
250
+ assertCurrentLoad();
251
+ reportProgress({phase: 'loading', modelId: value.modelId});
252
+ assertCurrentLoad();
253
+ if (prepareModel) {
254
+ preparation = await prepareModel({selection: value, signal: operation.controller.signal, progress: reportProgress});
255
+ assertCurrentLoad();
256
+ }
257
+ const projectionId = assetProjectionId === undefined ? preparation?.assetProjectionId : assetProjectionId;
258
+ const paths = resourcePaths === undefined ? preparation?.resourcePaths : resourcePaths;
259
+ const target = executionTarget === undefined ? preparation?.executionTarget : executionTarget;
260
+ const parameters = {model: value.modelId};
261
+ if (projectionId !== undefined) parameters.assetProjectionId = projectionId;
262
+ if (paths !== undefined) parameters.resourcePaths = paths;
263
+ if (target !== undefined) parameters.executionTarget = target;
264
+ const revision = lifecycleRevision;
265
+ retainedSelection = {selection: value, connection: currentConnection};
266
+ const current = await core.invoke('llama.load', parameters, {signal: operation.controller.signal});
267
+ assertCurrentLoad();
268
+ if (lifecycleRevision === revision) acceptRuntime(current);
269
+ if (!modelReady()) throw localAIError('ARCANE_AI_MODEL_NOT_READY', 'Core did not report the selected model loaded and ready.', current?.error);
270
+ } catch (error) {
271
+ failed = true;
272
+ failure = error;
273
+ }
274
+ try {
275
+ // Core has retained the projection before its load settles.
276
+ // A cancelled preparation still releases any late result.
277
+ if (is.function(preparation?.release)) await preparation.release();
278
+ } catch (error) {
279
+ if (failed) throw new AggregateError([failure, error], 'Local model loading and preparation release failed.', {cause: failure});
280
+ throw error;
281
+ }
282
+ if (failed) throw failure;
283
+ assertCurrentLoad();
284
+ if (!modelReady()) throw localAIError('ARCANE_AI_MODEL_NOT_READY', 'Core no longer reports the selected model loaded and ready.', runtime?.error);
285
+ acceptedLoad = operation;
286
+ reportProgress({phase: 'ready', modelId: value.modelId});
287
+ assertCurrentLoad();
288
+ if (!modelReady()) throw localAIError('ARCANE_AI_MODEL_NOT_READY', 'Core no longer reports the selected model loaded and ready.', runtime?.error);
289
+ completed = true;
231
290
  return {authority: authority(value), status: {...status(), state: 'ready', busy: false}};
291
+ });
292
+ try {
293
+ return await operation.result;
232
294
  } finally {
233
- if (loading === operation) loading = false;
295
+ operation.release();
296
+ if (loading === operation) {
297
+ if (!completed) acceptedLoad = null;
298
+ loading = false;
299
+ }
234
300
  }
235
301
  }
236
302
 
@@ -254,7 +320,7 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
254
320
  return request;
255
321
  }
256
322
 
257
- function streamRequest(core, parameters, signal) {
323
+ function streamRequest(core, parameters, signal, assertAcceptedLoad) {
258
324
  const request = ownRequest(signal, parameters.model, core);
259
325
  const chunks = [];
260
326
  const readers = [];
@@ -284,6 +350,7 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
284
350
 
285
351
  request.result = Promise.resolve().then(function invokeStream() {
286
352
  request.controller.signal.throwIfAborted();
353
+ assertAcceptedLoad();
287
354
  return core.invoke('llama.chat', {...parameters, stream: true, streamId}, {signal: request.controller.signal});
288
355
  }).then(function streamCompleted(result) {
289
356
  if (request.controller.signal.aborted) throw cancelled(request.controller.signal.reason);
@@ -329,9 +396,14 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
329
396
  const currentConnection = connection;
330
397
  requireSelection(value);
331
398
  if (signal?.aborted) throw cancelled(signal.reason);
332
- if (loading || unloading || selectionConnection !== connection || selection?.modelId !== value.modelId) {
333
- throw localAIError('ARCANE_AI_MODEL_NOT_READY', 'Load the selected local model before requesting inference.');
399
+ const currentLoad = acceptedLoad;
400
+ function assertAcceptedLoad() {
401
+ if (loading || unloading || !currentLoad || acceptedLoad !== currentLoad
402
+ || selectionConnection !== connection || selection?.modelId !== value.modelId) {
403
+ throw localAIError('ARCANE_AI_MODEL_NOT_READY', 'Load the selected local model before requesting inference.');
404
+ }
334
405
  }
406
+ assertAcceptedLoad();
335
407
  const model = payload?.model ?? value.modelId;
336
408
  if (!models.some(function knownModel(record) { return record.id === model; })) {
337
409
  const revision = lifecycleRevision;
@@ -339,6 +411,7 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
339
411
  assertCurrentConnection(currentConnection);
340
412
  if (lifecycleRevision === revision) acceptRuntime(current);
341
413
  }
414
+ assertAcceptedLoad();
342
415
  if (!models.some(function knownModel(record) { return record.id === model; })) {
343
416
  throw localAIError('ARCANE_AI_MODEL_UNAVAILABLE', 'The requested model is unavailable in the local llama.cpp runtime.');
344
417
  }
@@ -347,11 +420,12 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
347
420
  throw localAIError('ARCANE_AI_MODEL_NOT_READY', 'The requested local runtime or selected model is not ready.');
348
421
  }
349
422
  const parameters = {model, payload};
350
- if (operation === 'stream') return streamRequest(core, parameters, signal);
423
+ if (operation === 'stream') return streamRequest(core, parameters, signal, assertAcceptedLoad);
351
424
  if (operation !== 'chat') throw localAIError('ARCANE_AI_PROVIDER_OPERATION_UNAVAILABLE', 'The local llama.cpp provider supports chat and stream.');
352
425
  const pending = ownRequest(signal, model, core);
353
426
  pending.result = Promise.resolve().then(function invokeChat() {
354
427
  pending.controller.signal.throwIfAborted();
428
+ assertAcceptedLoad();
355
429
  return core.invoke('llama.chat', {...parameters, stream: false}, {signal: pending.controller.signal});
356
430
  }).then(function chatCompleted(result) {
357
431
  if (pending.controller.signal.aborted) throw cancelled(pending.controller.signal.reason);
@@ -361,16 +435,18 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
361
435
  }
362
436
 
363
437
  async function unload({signal} = {}) {
364
- const value = selection;
438
+ const retained = retainedSelection;
439
+ const value = retained?.selection;
365
440
  const core = requireClient();
366
441
  const currentConnection = connection;
367
442
  if (signal?.aborted) throw cancelled(signal.reason);
368
- if (value && value === selection && selectionConnection !== currentConnection) {
443
+ if (retained && retained.connection !== currentConnection) {
369
444
  throw localAIError('ARCANE_AI_CORE_UNAVAILABLE', 'The selected model belongs to a retired Core connection.');
370
445
  }
371
446
  if (value) requireSelection(value);
372
447
  const operation = {};
373
448
  unloading = operation;
449
+ acceptedLoad = null;
374
450
  loading = false;
375
451
  lifecycleRevision += 1;
376
452
  for (const request of requests) request.controller.abort();
@@ -388,6 +464,7 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
388
464
  if (unloading !== operation) throw cancelled();
389
465
  selection = null;
390
466
  selectionConnection = null;
467
+ retainedSelection = null;
391
468
  unloading = false;
392
469
  return status();
393
470
  } catch (error) {
@@ -403,10 +480,12 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
403
480
  function dispose({signal} = {}) {
404
481
  if (disposing) return disposing;
405
482
  // The runtime also supplies its configured selection after unload.
406
- // Only this provider's retained selection owns a native release.
407
- const value = selection;
408
- const core = selectionConnection?.client;
483
+ // Only a dispatched selection owns a native release; preparation alone
484
+ // must not unload a model owned by another provider.
485
+ const value = retainedSelection?.selection;
486
+ const core = retainedSelection?.connection.client;
409
487
  disposed = true;
488
+ acceptedLoad = null;
410
489
  loading = false;
411
490
  unloading = false;
412
491
  lifecycleRevision += 1;
@@ -425,6 +504,7 @@ export function createCoreLocalAIProvider({client: suppliedClient, id = 'llama.c
425
504
  }
426
505
  selection = null;
427
506
  selectionConnection = null;
507
+ retainedSelection = null;
428
508
  return status();
429
509
  }
430
510
  ).catch(
@@ -221,6 +221,13 @@ export async function loadDecisionRuntime(configuration, report, resourceFetch =
221
221
  if (runtime.wasmPaths !== undefined) {
222
222
  namespace.env.backends.onnx.wasm.wasmPaths = runtime.wasmPaths;
223
223
  }
224
+ if (runtime.local) {
225
+ // MV3 executes the packaged module directly. Its upstream cache and
226
+ // nested WASM workers would otherwise create executable Blob URLs.
227
+ namespace.env.useWasmCache = false;
228
+ namespace.env.backends.onnx.wasm.proxy = false;
229
+ namespace.env.backends.onnx.wasm.numThreads = 1;
230
+ }
224
231
  const {AutoTokenizer, AutoModel, PreTrainedModel, Tensor} = namespace;
225
232
  const options = {revision, device, dtype};
226
233
  // No progress_callback: upstream 4.3 otherwise performs file-size
@@ -40,11 +40,17 @@ async function handleDecisionOperation(event) {
40
40
  try {
41
41
  if (op === 'load') {
42
42
  if (event.data.storedResources) {
43
+ const localExecutables = payload.runtime.local
44
+ ? new Set(
45
+ [payload.runtime.moduleUrl, payload.runtime.wasmPaths.mjs, payload.runtime.wasmPaths.wasm]
46
+ )
47
+ : null;
43
48
  // Cover native ONNX support-file fetches as well as env.fetch.
44
49
  // Native module imports remain the browser module loader's job.
45
50
  globalThis.fetch = function fetchDecisionSupport(input, options) {
46
51
  const url = new URL(is.string(input) ? input : input.url ?? input.href, globalThis.location.href);
47
52
  if (url.protocol === 'blob:' || url.protocol === 'data:') return nativeFetch(input, options);
53
+ if (localExecutables?.has(url.href)) return nativeFetch(input, options);
48
54
  return resources.fetch(input, options);
49
55
  };
50
56
  }
@@ -55,7 +61,9 @@ async function handleDecisionOperation(event) {
55
61
  {id, progress}
56
62
  );
57
63
  },
58
- event.data.storedResources ? resources.fetch : null
64
+ event.data.storedResources
65
+ ? payload.runtime.local ? globalThis.fetch : resources.fetch
66
+ : null
59
67
  );
60
68
  globalThis.postMessage(
61
69
  {id, result: {loaded: true}}
@@ -647,6 +647,12 @@ export function createCoreFacade(client){
647
647
  preferences:{list:()=>invoke('preferences.list'),get:key=>invoke('preferences.get',{key}),set:(key,value)=>invoke('preferences.set',{key,value}),
648
648
  setMany:entries=>invoke('preferences.setMany',{entries}),delete:key=>invoke('preferences.delete',{key})},
649
649
  window: {
650
+ state(options) {
651
+ return invoke('window.state', {}, {signal: options?.signal});
652
+ },
653
+ setState(selection, options) {
654
+ return invoke('window.setState', selection, {signal: options?.signal});
655
+ },
650
656
  setTheme(presentation, options) {
651
657
  return invoke(
652
658
  'window.setTheme', presentation,
@@ -43,6 +43,16 @@ export const CORE_METHOD_CONTRACTS={
43
43
  output: '{requestedTarget,resolvedDevice,resolution,reason}',
44
44
  meaning: 'Resolve a saved physical identity. Explicit null selects engine-owned automatic resolution; it does not claim a configured or observed device.'
45
45
  },
46
+ 'window.state': {
47
+ input: '{}',
48
+ output: '{platform,supported,state}',
49
+ meaning: 'Current requesting native window; state may be normal, maximized, fullscreen, or minimized. No saved preference is read.'
50
+ },
51
+ 'window.setState': {
52
+ input: '{state:"normal"|"maximized"|"fullscreen"}',
53
+ output: '{platform,supported,state}',
54
+ meaning: 'Apply to the requesting native window and report its resulting state. No persistence, desktop activation, or Core restart.'
55
+ },
46
56
  'window.setTheme': {
47
57
  input: '{backgroundColor?,textColor?}: null reset or {red,green,blue,alpha}',
48
58
  output: '{platform,supported,applied,unsupported}',
@@ -68,14 +68,16 @@ it does not substitute a quantized graph or another model.
68
68
  | `revision` | Upstream revision, default `main`; select a stable revision when repeatable model selection is needed. |
69
69
  | `device` | Transformers.js backend, default `webgpu`; an explicit supported alternative is caller-owned. No automatic backend fallback. |
70
70
  | `dtype` | Exact upstream precision selection; defaults to `fp16` for Laya and `fp32` for Julia. For Laya FP32, supply `dtype: 'fp32'`. Unsupported selections fail at the selected upstream loader rather than being replaced. |
71
- | `runtime.moduleUrl` | Defaults to the self-contained CDN entry `https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.3.0/dist/transformers.min.js`. The `.web.js` build expects a bundler to resolve its bare ONNX Runtime import and is unsuitable for a native module Worker. An explicit compatible public runtime URL may be supplied. With `store`, the SDK stores and materializes the complete selected entry for that activation. |
72
- | `runtime.wasmPaths` | Optional upstream ONNX WASM location passed to the selected runtime's public environment API. |
73
- | `store` | Optional existing SDK DBOPFS model or speech artifact store exposing `fetchResource()`. The store remains outside Worker configuration; the SDK uses it for the runtime entry and bridges the loader's actual resource requests to it. |
71
+ | `runtime.local` | Default `false`. Explicit `true` selects deployment-local executable modules/WASM without executable Blob preparation; see the MV3 section below. |
72
+ | `runtime.moduleUrl` | Defaults to the self-contained CDN entry `https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.3.0/dist/transformers.min.js`, or `./decisions-runtime/transformers.min.js` relative to this SDK module in local mode. The `.web.js` build expects a bundler to resolve its bare ONNX Runtime import and is unsuitable for a native module Worker. Explicit compatible URLs are retained; local mode requires files in the SDK Worker's deployment. With `store` and ordinary non-local mode, the SDK stores and materializes the complete entry for that activation. |
73
+ | `runtime.wasmPaths` | Optional upstream ONNX WASM location passed to the selected runtime's public environment API. Local mode defaults to the matching asyncify module/WASM under `./decisions-runtime/`; it accepts a directory URL or explicit `{mjs, wasm}` URLs resolved relative to this SDK module. |
74
+ | `store` | Optional existing SDK DBOPFS model or speech artifact store exposing `fetchResource()`. It remains outside Worker configuration and owns actual model resources. Ordinary stored mode also owns the runtime entry; local mode loads only the exact selected executable files directly. |
74
75
 
75
76
  Without `store`, the runtime, tokenizer and weights use normal upstream loading
76
77
  and caching. With `store`, the selected loader still owns filenames and model
77
78
  selection while model resources persist through that existing SDK store. The
78
- SDK does not redistribute upstream runtimes or models. Browser
79
+ SDK package does not contain upstream runtimes or models; the explicit local
80
+ materializer below acquires its selected executable distribution. Browser
79
81
  network, cross-origin and backend availability still apply. WebGPU needs a
80
82
  compatible browser/device context. Unsupported precision or backend errors
81
83
  are returned, not replaced with a different model, backend or precision.
@@ -91,6 +93,85 @@ Julia selects `dtype: 'fp32'`, loading root `model.onnx` with the exact
91
93
  through the public generic `PreTrainedModel` API, not a text-generation
92
94
  pipeline. Existing Wllama, cloud, speech and chat APIs are unchanged.
93
95
 
96
+ ## Extension-local executables (Manifest V3)
97
+
98
+ During the application's ordinary SDK materialization, opt into the upstream
99
+ browser distribution through the public Node API:
100
+
101
+ ```js
102
+ import {materializeInstalledSdkRuntime} from 'arcane-os';
103
+
104
+ await materializeInstalledSdkRuntime({
105
+ workspaceRoot: process.cwd(),
106
+ browserDecisions: true
107
+ });
108
+ ```
109
+
110
+ This acquires the official bundled Transformers.js `4.3.0`
111
+ `transformers.min.js`, matching ONNX Runtime
112
+ `1.31.0-dev.20260914-8d85527a0` `ort-wasm-simd-threaded.asyncify.mjs` and
113
+ `ort-wasm-simd-threaded.asyncify.wasm`, plus `TRANSFORMERS-LICENSE` and
114
+ `ONNX-RUNTIME-LICENSE`. The five complete files are staged under
115
+ `arcane/sdk/ai/decisions-runtime/` before replacing the projected runtime.
116
+ Retain the supplied licenses with the distribution. No bundler, Node engine
117
+ dependency tree, model graph or tokenizer is installed by this option.
118
+ Repeat `browserDecisions: true` on each materialization that should retain
119
+ this closure; whole-tree replacement removes files absent from a later selection.
120
+
121
+ The result's `workspaceRuntime.browserDecisions` contains `directory`,
122
+ `transformersVersion`, `onnxRuntimeVersion` and `files`. The lower-level
123
+ `materializeWorkspaceRuntimeContent()` accepts the same option and returns
124
+ that record directly as `result.browserDecisions`. Omission performs no
125
+ optional network acquisition. The fixed distribution files are acquired
126
+ concurrently, once each per selected materialization. Events
127
+ `workspace.decisions.started` and `workspace.decisions.progress` expose
128
+ `completed`/`total` file counts; each progress event also has `file` and `url`.
129
+ Cancellation and HTTP or observer failure abort/join pending acquisition and
130
+ preserve the prior runtime. An HTTP failure retains the complete response as
131
+ `error.response` (`url`, `status`, `statusText`, `headers`, `content`), including
132
+ inside `AggregateError.errors` when several operations fail.
133
+
134
+ Load from an extension-owned module script, using the deployed SDK path:
135
+
136
+ ```js
137
+ import {createBrowserDecisionModel} from './arcane/sdk/ai/browser-decisions.mjs';
138
+
139
+ const decisions = createBrowserDecisionModel({
140
+ family: 'laya',
141
+ model: 'onnx-community/laya-typed-decisions-ONNX',
142
+ dtype: 'fp16',
143
+ device: 'webgpu',
144
+ runtime: {local: true},
145
+ store // The application's existing SDK DBOPFS model store.
146
+ });
147
+ await decisions.load();
148
+ ```
149
+
150
+ For Julia-1, change the family/model to `julia` /
151
+ `SupersonicLabs/Julia-1-ONNX` and select `dtype: 'fp32'`. Model and tokenizer
152
+ data remain upstream resources served through the same store, preserving its
153
+ complete response, persistent cache, progress and cancellation behavior.
154
+ Construction stays lazy. Only the exact configured local executable URLs use
155
+ native fetch/module loading outside that store; other resources, including
156
+ other same-deployment URLs, retain the model resource bridge.
157
+
158
+ Local mode directly imports the deployment's module and explicit asyncify
159
+ factory, disables the upstream executable WASM cache and proxy, and selects
160
+ one WASM thread to avoid nested executable Blob workers. This leaves
161
+ `device: 'webgpu'` unchanged; it is not a CPU/backend fallback. Compatible
162
+ custom `runtime.moduleUrl` and `runtime.wasmPaths: {mjs, wasm}` selections must
163
+ share the SDK module's protocol and host. Relative selections resolve against
164
+ that module. A `wasmPaths` directory URL selects the same two asyncify filenames.
165
+
166
+ Use external module scripts in extension pages. Chrome's MV3 extension-page
167
+ CSP permits WebAssembly with `script-src 'self' 'wasm-unsafe-eval'; object-src
168
+ 'self'`; executable scripts and Workers remain packaged locally. The extension
169
+ owns the permissions required for its selected upstream model-data endpoints.
170
+ See [Chrome's extension CSP contract](https://developer.chrome.com/docs/extensions/reference/manifest/content-security-policy).
171
+ This source integration does not establish an actual Chrome extension load,
172
+ WebGPU inference, or application acceptance; those outcomes require execution
173
+ in the selected extension environment.
174
+
94
175
  ## Shared DBOPFS model resources
95
176
 
96
177
  ```js
@@ -111,7 +192,7 @@ await decisions.load();
111
192
 
112
193
  The same `store` may serve other SDK model clients. The application supplies no
113
194
  tokenizer/configuration/graph manifest and performs no file routing. Explicit
114
- activation first opens `runtime.moduleUrl` through `store.fetchResource()`.
195
+ activation without `runtime.local` first opens `runtime.moduleUrl` through `store.fetchResource()`.
115
196
  The SDK materializes the complete stored entry as a JavaScript object URL for
116
197
  the dedicated Worker, without rewriting its content. The configured source URL
117
198
  is retained for later activations, so they reuse the same stored resource rather
@@ -122,7 +203,8 @@ actual selected tokenizer, configuration, ONNX graph and external-data requests
122
203
  to `store.fetchResource(input, options)`. Its competing browser, custom and
123
204
  filesystem model caches are disabled only for this explicit stored mode. The
124
205
  default upstream ONNX WASM binary and factory preloads use this same hook.
125
- Native support-file fetches in the dedicated Worker also use the store. The
206
+ Native support-file fetches in the dedicated Worker also use the store, except
207
+ for the exact deployment-local executable selections in local mode. The
126
208
  default runtime entry is self-contained. Entry materialization does not rewrite
127
209
  dependency imports in custom modules or preserve their original `import.meta.url`
128
210
  base. Custom stored entries must work from an object URL; relative imports or
@@ -148,7 +230,7 @@ url}`. `completed` counts closed persistent shards; `total` is `null` until the
148
230
  resource finishes. This progress carries no network-size, rate or ETA fields.
149
231
  Cancellation aborts the owned fetch and joins reader/writer cleanup before the
150
232
  resource owner becomes available to a later request. Interrupted closed shards
151
- are retained. Each activation owns its runtime object URL and revokes it after
233
+ are retained. Each non-local stored activation owns its runtime object URL and revokes it after
152
234
  terminating the Worker on cancellation, failure, unload or disposal. A cancelled
153
235
  runtime-entry fetch is joined before its waiting calls settle; a late response
154
236
  cannot activate a replacement Worker. Runtime-entry HTTP failures retain the
@@ -161,6 +161,65 @@ pre-aborted signal sends no transcription request; an in-flight abort sends
161
161
  service owns interruption and release of its actual engine operation.
162
162
  Its optional request observer uses the same pre-dispatch correlation contract.
163
163
 
164
+ ## Current-window state
165
+
166
+ `Arcane.window.state({signal}?)` reads the native window hosting the calling
167
+ document. `Arcane.window.setState({state}, {signal}?)` selects `normal`,
168
+ `maximized`, or `fullscreen` for that same window. Both use the existing
169
+ request lifetime and return `{platform:'windows', supported:true, state}`.
170
+ The observed `state` can also be `minimized`; minimize remains a native window
171
+ control rather than a selectable state in this operation.
172
+
173
+ ```js
174
+ async function selectObservatoryWindow(state) {
175
+ const result=await Arcane.window.setState({state});
176
+ console.log('Observatory window state:',result.state);
177
+ }
178
+ ```
179
+
180
+ Call this operation when the application's own setting or user action selects
181
+ a state. Initial state belongs to the descriptor's optional
182
+ [`native.window.state`](core-native-packaging.md#initial-native-window-size)
183
+ field. Saved Settings, their loading and the choice to persist a state remain
184
+ application-owned; the SDK adds no saved-state store or page-startup barrier.
185
+
186
+ The Windows host uses ordinary native normal/maximized states. Fullscreen
187
+ removes the window frame and occupies the selected monitor's full bounds,
188
+ including its taskbar area. It retains the preceding normal bounds and frame
189
+ controls for restoration. Selecting normal restores them; selecting maximized
190
+ restores them before maximizing. This is native window fullscreen, separate
191
+ from the browser Fullscreen API. It does not select TopMost, request foreground
192
+ activation, change the profile/origin or restart Core. A minimized fullscreen
193
+ window reports `minimized` until restored or another state is selected.
194
+
195
+ Visible transitions use a temporary native callback confined to this window's
196
+ UI thread and synchronous operation. It prevents activation of this window and
197
+ focus assignment to it or its descendants, then releases the callback. Other
198
+ windows and ordinary input remain untouched. Hidden initial configuration and
199
+ already-selected states allocate no callback; the application's ordinary first
200
+ show keeps its existing startup behavior. State reads observe the native
201
+ minimized/maximized state, rather than merely echoing the requested selection.
202
+
203
+ Malformed selections reject with `INVALID_ARGUMENT`; native operation failures
204
+ reject with `ARCANE_WINDOW_STATE_FAILED`. Complete native error/cause fields
205
+ remain available, with `details.requested`, `details.previous` and
206
+ `details.actual` describing the attempted selection and observed state. A
207
+ partial native change is observable rather than rolled back. Cancellation
208
+ suppresses late responses and does not undo a change the host already applied.
209
+ If native callback cleanup fails, it stops preventing activation immediately,
210
+ its handle remains owned, and the failure is returned. Simultaneous mutation and
211
+ cleanup failures retain both original errors. The owning window makes another
212
+ cleanup attempt during its normal close and reports any remaining failure.
213
+ State changes survive document navigation, while each response remains
214
+ correlated to its originating document. There is no state-change event in this
215
+ contract; `state()` reads the current native state when requested.
216
+
217
+ These methods require the matching Windows host executable. The current macOS,
218
+ Linux and Android hosts have no implementation of these operations; their
219
+ actual unavailable-method errors remain observable. Ordinary browsers have no
220
+ native window transport. The facade supplies no browser or other-host fallback,
221
+ and importing it does not add support to an older native executable.
222
+
164
223
  ## Current-window theme
165
224
 
166
225
  `Arcane.window.setTheme(presentation, {signal}?)` changes only the window