arcane-os 0.66.0 → 0.67.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.
@@ -1,4 +1,4 @@
1
- import {getInstalledCoreClient} from '../core/client.mjs';
1
+ import {subscribeCoreClient} from '../core/client.mjs';
2
2
  import {createArcaneEventSource} from '../event-manager.mjs';
3
3
  import {encodeTensorMap, decodeTensorMap, encodeTensorFetches} from './onnx-tensors.mjs';
4
4
 
@@ -9,16 +9,26 @@ function unavailable(message) {
9
9
  }
10
10
 
11
11
  /** Optional system ONNX access. Construction neither connects nor installs. */
12
- export function createCoreONNXRuntime({client = getInstalledCoreClient()} = {}) {
12
+ export function createCoreONNXRuntime({client: suppliedClient} = {}) {
13
13
  const owner = {};
14
14
  const events = createArcaneEventSource(owner, {source: 'core-onnx', eventTypes: ['onnx.state']});
15
15
  const lifetime = new AbortController();
16
16
  const pending = new Set();
17
+ let client = null;
18
+ let connection = null;
19
+ let stopInstallation;
17
20
  let state = {id: 'onnx', installed: false, available: false, state: 'unknown', models: []};
18
21
  let revision = 0;
19
22
  let closed = false;
20
23
 
21
- function current() { return {...state, busy: pending.size > 0, closed}; }
24
+ function current() {
25
+ const busy = [...pending].some(
26
+ function currentRequest(request) {
27
+ return request.connection === connection && !request.signal.aborted;
28
+ }
29
+ );
30
+ return {...state, busy, closed};
31
+ }
22
32
  function publish() { events.dispatch('onnx.state', current()); }
23
33
  function accept(value) {
24
34
  state = value ?? {id: 'onnx', installed: false, available: false, state: 'unavailable', models: []};
@@ -26,30 +36,66 @@ export function createCoreONNXRuntime({client = getInstalledCoreClient()} = {})
26
36
  publish();
27
37
  }
28
38
 
29
- const unsubscribe = client?.events.on('localai.state', function localStateChanged(snapshot) {
30
- if (!closed) accept(snapshot?.runtimes?.find(function onnxRuntime(runtime) { return runtime.id === 'onnx'; }));
31
- }) ?? function noCoreSubscription() {};
39
+ function bindClient({client: nextClient, error = null}) {
40
+ if (closed || (connection && nextClient === client)) return;
41
+ const previous = connection;
42
+ client = nextClient;
43
+ const currentConnection = {client, controller: new AbortController(), unsubscribe: null};
44
+ connection = currentConnection;
45
+ revision += 1;
46
+ state = {id: 'onnx', installed: false, available: false, state: client ? 'unknown' : 'unavailable', models: [], error};
47
+ previous?.unsubscribe?.();
48
+ previous?.controller.abort();
49
+ if (client) {
50
+ currentConnection.unsubscribe = client.events.on(
51
+ 'localai.state',
52
+ function localStateChanged(snapshot) {
53
+ if (!closed && connection === currentConnection) {
54
+ accept(snapshot?.runtimes?.find(function onnxRuntime(runtime) { return runtime.id === 'onnx'; }));
55
+ }
56
+ }
57
+ );
58
+ }
59
+ publish();
60
+ if (!closed && connection === currentConnection && client && suppliedClient === undefined) {
61
+ inspect().catch(
62
+ function initialONNXStateUnavailable(error) {
63
+ if (!closed && connection === currentConnection && !currentConnection.controller.signal.aborted) {
64
+ globalThis.console?.error('Core ONNX initial state unavailable.', error);
65
+ }
66
+ }
67
+ );
68
+ }
69
+ }
32
70
 
33
71
  async function invoke(method, parameters, {signal, timeoutMs = 0} = {}) {
34
72
  if (closed) throw unavailable('The Core ONNX accessor is closed.');
35
73
  if (!client) throw unavailable('Local ONNX requires an available Core connection.');
36
- const request = {};
37
- const operationSignal = signal ? AbortSignal.any([signal, lifetime.signal]) : lifetime.signal;
74
+ const currentConnection = connection;
75
+ const operationSignal = AbortSignal.any(
76
+ [lifetime.signal, currentConnection.controller.signal, ...(signal ? [signal] : [])]
77
+ );
78
+ const request = {connection: currentConnection, signal: operationSignal};
38
79
  operationSignal.throwIfAborted();
39
80
  pending.add(request);
40
81
  publish();
41
82
  try {
42
- return await client.invoke(method, parameters, {signal: operationSignal, timeoutMs});
83
+ operationSignal.throwIfAborted();
84
+ const result = await currentConnection.client.invoke(method, parameters, {signal: operationSignal, timeoutMs});
85
+ operationSignal.throwIfAborted();
86
+ return result;
43
87
  } finally {
44
88
  pending.delete(request);
45
- if (!closed) publish();
89
+ if (!closed && connection === currentConnection) publish();
46
90
  }
47
91
  }
48
92
 
49
93
  async function inspect(options) {
50
94
  const before = revision;
95
+ const currentConnection = connection;
51
96
  try {
52
97
  const result = await invoke('onnx.status', {}, options);
98
+ currentConnection.controller.signal.throwIfAborted();
53
99
  if (!closed && revision === before) accept(result);
54
100
  return current();
55
101
  } catch (error) {
@@ -61,14 +107,19 @@ export function createCoreONNXRuntime({client = getInstalledCoreClient()} = {})
61
107
  }
62
108
 
63
109
  async function load({id, model, sessionOptions, signal, timeoutMs} = {}) {
64
- return invoke('onnx.load', {id, model, sessionOptions}, {signal, timeoutMs});
110
+ const currentConnection = connection;
111
+ const result = await invoke('onnx.load', {id, model, sessionOptions}, {signal, timeoutMs});
112
+ currentConnection.controller.signal.throwIfAborted();
113
+ return result;
65
114
  }
66
115
 
67
116
  async function run({id, feeds, fetches, runOptions, signal, timeoutMs} = {}) {
117
+ const currentConnection = connection;
68
118
  const result = await invoke('onnx.run', {
69
119
  id, feeds: encodeTensorMap(feeds),
70
120
  fetches: encodeTensorFetches(fetches), runOptions
71
121
  }, {signal, timeoutMs});
122
+ currentConnection.controller.signal.throwIfAborted();
72
123
  return decodeTensorMap(result);
73
124
  }
74
125
 
@@ -86,11 +137,19 @@ export function createCoreONNXRuntime({client = getInstalledCoreClient()} = {})
86
137
  if (closed) return;
87
138
  closed = true;
88
139
  lifetime.abort();
89
- unsubscribe();
140
+ stopInstallation?.();
141
+ connection?.unsubscribe?.();
142
+ connection?.controller.abort();
90
143
  state = {...state, available: false, state: 'closed'};
91
144
  publish();
92
145
  events.dispose();
93
146
  }
94
147
 
148
+ if (suppliedClient === undefined) {
149
+ stopInstallation = subscribeCoreClient(bindClient, {signal: lifetime.signal});
150
+ } else {
151
+ bindClient({client: suppliedClient});
152
+ }
153
+
95
154
  return {load, run, unload, inspect, current, subscribe, close};
96
155
  }
@@ -70,6 +70,7 @@ alone does not make bare module names resolve in a browser.
70
70
  | Persist application-selected preferences through Core | [Core preferences service](core-preferences.md) |
71
71
  | Read complete selected Git text without changing a checkout | [Git text snapshots](git-text-snapshot.md) |
72
72
  | Own a native working checkout under per-user ArcaneData/Repos | [Native repository workspaces](core-repositories.md) |
73
+ | Read Git identity defaults or select child-only author and credential hints | [Git identity configuration](core-repositories.md#git-identity-configuration) |
73
74
  | Write, commit and push complete selected repository text | [Selected-text repository writing](core-repositories.md#write-commit-and-push-selected-text) |
74
75
  | Build an installed-SDK portable Core payload | [Portable Core packaging](core-native-packaging.md) |
75
76
  | Pass host-selected state defaults and unchanged explicit launch values to services | [Launch-time locations](core-native-packaging.md#launch-time-locations) |
@@ -261,10 +261,17 @@ service exposes retained worker sessions through `onnx.load/run/unload/status`;
261
261
  model files remain caller-selected application resources. See
262
262
  [native ONNX sessions](local-ai.md#native-onnx-sessions).
263
263
 
264
- Selected `stable-diffusion.cpp` additionally composes
265
- `createLocalImageService` from `arcane-os/core/image` and one shared
266
- `createModelAssetService` from `arcane-os/core/model-assets`. The image factory
267
- receives the full authored selection plus that same model-assets instance.
264
+ Selecting ONNX, image generation, or both composes one shared
265
+ `createModelAssetService` from `arcane-os/core/model-assets`. Native application
266
+ services obtain that same definition with `await context.getService('model-assets')`.
267
+ They obtain the selected local-AI owner with `await context.getService('local-ai')`
268
+ and its current native ONNX handle with `getONNXRuntime()`. These lookups await
269
+ only their selected service startup, independently of renderer readiness. See
270
+ [native service ownership and cleanup](local-ai.md#native-service-owners).
271
+
272
+ Selected `stable-diffusion.cpp` additionally composes `createLocalImageService`
273
+ from `arcane-os/core/image`. The image factory receives the full authored
274
+ selection plus that same model-assets instance.
268
275
  Library, Koffi binding and variant paths resolve from the artifact root;
269
276
  ordinary browser imports use `arcane-os/ai/core-image` and
270
277
  `arcane-os/ai/core-model-assets` through the managed import map.
@@ -24,7 +24,8 @@ The Android host also supplies Git/process execution through the existing
24
24
  process-adapter contract; this module does not install Git or choose shared
25
25
  Android storage.
26
26
 
27
- The factory's first actual operation creates necessary parent directories.
27
+ The factory's first operation that prepares a checkout creates necessary parent
28
+ directories. The read-only `configuration` operation never prepares one.
28
29
  No existing data is moved, copied, deleted, renamed or migrated. `appRoot`,
29
30
  `stateRoot`, preferences, OPFS and the existing CLI/workspace defaults remain
30
31
  independent and unchanged.
@@ -47,7 +48,7 @@ await repository.close();
47
48
  ```
48
49
 
49
50
  `createRepositoryWorkspace({name,directory,dataRoot,remote,branch,gitIdentity,onEvent,run}={})`
50
- returns `{directory,open,status,pull,push,write,close,drain,dispose}`. The `directory`
51
+ returns `{directory,open,status,configuration,pull,push,write,close,drain,dispose}`. The `directory`
51
52
  property is the absolute selected working path.
52
53
 
53
54
  - Omit `directory` to select `<repositoriesRoot>/<name>`. The application supplies
@@ -67,15 +68,17 @@ property is the absolute selected working path.
67
68
  authentication remain with Git and the native host. The SDK adds no download,
68
69
  credential store, polling, network retry or process supervisor.
69
70
 
70
- The open/status/pull/push methods accept `{signal}={}` and return promises;
71
+ The open/status/configuration methods accept `{signal}={}` and return promises;
72
+ pull/push accept `{target,signal}={}` with an optional captured destination.
71
73
  `write` accepts the complete file/message request described below:
72
74
 
73
75
  | Method | Result and side effect |
74
76
  |---|---|
75
77
  | `open` | For a missing/empty destination, runs one clone and returns `{directory,cloned:true,stdout,stderr}` with complete process output. For an existing working repository root, returns `{directory,cloned:false}` after read-only inspection. Later opens on that owner reuse preparation. |
76
78
  | `status` | Prepares the checkout if needed, then returns the existing `repositoryStatus` result unchanged. |
77
- | `pull` | Prepares the checkout if needed, then runs the existing clean-checkout, fast-forward-only `repositoryPull` operation unchanged. |
78
- | `push` | Prepares the checkout if needed, then runs the existing `repositoryPush` operation unchanged, preserving its support for unrelated uncommitted working files. |
79
+ | `configuration` | Observes the existing working root, symbolic HEAD, origin and configured upstream. It never clones, initializes or fetches. |
80
+ | `pull` | Prepares the checkout if needed, then runs clean-checkout, fast-forward-only `repositoryPull`, optionally using the operation's selected target. |
81
+ | `push` | Prepares the checkout if needed, then runs `repositoryPush`, optionally using the operation's selected target, preserving support for unrelated uncommitted working files. |
79
82
  | `write` | Writes exact caller-selected text files, stages and commits only their literal paths, then performs one ordinary non-force push. Returns confirmed operation outcomes; see below. |
80
83
  | `close`, `drain`, `dispose` | The same idempotent operation: stop accepting new calls and await accepted work and its process/event cleanup. They retain the repository on disk. |
81
84
 
@@ -87,6 +90,83 @@ A failed or cancelled clone reports the actual failure; any remaining directory
87
90
  is retained for application-owned inspection. There is no automatic deletion or
88
91
  destructive retry. Filesystem failures and complete Git diagnostics propagate.
89
92
 
93
+ ## Existing checkout configuration and selected targets
94
+
95
+ `configuration({signal}={})` observes an existing checkout without invoking
96
+ `open`, creating directories, cloning, initializing, fetching or changing Git
97
+ configuration. A missing repository reports the actual Git failure. A bare
98
+ cache or nested checkout directory reports `ARCANE_REPOSITORY_DIRECTORY_INVALID`.
99
+ Its result is:
100
+
101
+ ```text
102
+ {
103
+ repositoryRoot,
104
+ headRef: fullSymbolicRef | null,
105
+ origin: {urls: [...], pushUrls: [...]},
106
+ upstream: {remoteNames: [...], mergeRefs: [...], urls: [...], pushUrls: [...]} | null
107
+ }
108
+ ```
109
+
110
+ `repositoryRoot` is this owner's absolute selected working root. `headRef` is
111
+ the complete symbolic HEAD, including an unborn branch, or `null` for detached
112
+ HEAD. `upstream` is `null` when HEAD does not name a local branch. Otherwise its
113
+ arrays contain that branch's configured `remote` and `merge` values; `urls` and
114
+ `pushUrls` observe the effective last configured remote name. A `.` remote
115
+ means the local repository and has no remote URL arrays. Origin is observed
116
+ independently, even when it is not the upstream.
117
+
118
+ Arrays retain configured order, repeated values, complete UTF-8 strings and
119
+ empty strings. `[]` means unset; `['']` means an explicit empty value. The
120
+ reader honors configured includes and ordinary Git configuration precedence.
121
+ Unset push URLs do not imply that Git cannot push: Git retains its normal URL
122
+ fallback and rewrite behavior. These are configuration observations, not
123
+ resolved endpoints, authentication, account identity or ownership proof.
124
+ The SDK neither queries a credential helper nor changes configuration here.
125
+
126
+ Metadata uses the shared raw-Git-text reader. Lifecycle/stderr events remain
127
+ with the process owner, raw stdout remains parser-owned, and undecodable text
128
+ reports `ARCANE_GIT_CONFIGURATION_NOT_TEXT`. Failures retain complete raw
129
+ stdout, process diagnostics and causal errors using the identity reader's
130
+ documented attachment behavior. One observation uses four read-only Git
131
+ commands for an attached local branch, or three otherwise. Results are not a
132
+ transaction with later work or external Git activity. The app owns any decision
133
+ that the current checkout corresponds to its selected repository and branch.
134
+
135
+ The optional `target:{remote,ref}` on `pull`, `push` and `write` selects that
136
+ operation's Git destination. Both fields are nonempty strings representable by
137
+ the native process transport. The SDK captures their exact strings when the
138
+ call is accepted; later edits to the target object or UI selection cannot
139
+ redirect queued or active work. Git owns ref parsing, URL interpretation,
140
+ credentials, hooks and remote outcomes. This target is independent of the
141
+ factory's `remote`/`branch`, which remain initial-clone inputs only.
142
+
143
+ ```javascript
144
+ const observed = await repository.configuration({signal});
145
+ // The app decides whether observed.headRef and configured URLs match its choice.
146
+ const target = {remote: connection.locator, ref: connection.ref};
147
+ await repository.pull({target, signal});
148
+ const result = await repository.write({files: selectedFiles, message, target, signal});
149
+ ```
150
+
151
+ A targeted pull uses `git pull --ff-only -- <remote> <ref>:`. The trailing colon
152
+ is Git's source-only fetch framing; the fetched history is integrated into the
153
+ current branch. It does not switch branches or reset files/history. The existing
154
+ clean-checkout requirement remains. A targeted push uses
155
+ `git push --no-follow-tags -- <remote> HEAD:<ref>`: current HEAD is published to
156
+ the selected ref, without force or implicit configured tag following. Existing
157
+ history is still sent when Git needs it. Ordinary conflicting Git settings or
158
+ remote rejection remain actual failures; no settings are rewritten.
159
+
160
+ Omitting `target` preserves ordinary configured pull/push behavior and its
161
+ existing result shape. Targeted pull/push results add the captured `target` and
162
+ complete `stdout`/`stderr` alongside the existing `action`, `repositoryRoot`,
163
+ `branch` and compatibility `output` field. Writer results and failure outcomes
164
+ add that same captured `target`. There is no hidden pre-write pull, branch
165
+ selection, retry, reset, force, reversal or recommit. The app decides when to
166
+ observe, refresh, write and publish. See Git's [pull](https://git-scm.com/docs/git-pull),
167
+ [push](https://git-scm.com/docs/git-push) and [configuration](https://git-scm.com/docs/git-config)
168
+ contracts.
169
+
90
170
  ## Git identity configuration
91
171
 
92
172
  ```javascript
@@ -215,7 +295,7 @@ const result = await repository.write({
215
295
  });
216
296
  ```
217
297
 
218
- `write({files:[{path,content}],message,signal}={})` requires at least one explicit
298
+ `write({files:[{path,content}],message,target,signal}={})` requires at least one explicit
219
299
  file, a nonempty commit message and string content (including an empty string).
220
300
  Paths name working files relative to this repository, not its root, outside
221
301
  paths or Git-owned `.git` metadata. Strings must be representable as UTF-8;
@@ -239,9 +319,10 @@ native host.
239
319
  One `git --literal-pathspecs add -- <paths>` stages only those exact names.
240
320
  One `git --literal-pathspecs commit --only --cleanup=verbatim --file - -- <paths>`
241
321
  commits only those paths, leaving unrelated staged paths staged. The original
242
- message is streamed unchanged through standard input. One ordinary `git push`
243
- then uses that repository's existing remote/ref configuration, the optional
322
+ message is streamed unchanged through standard input. Without `target`, one
323
+ ordinary `git push` then uses that repository's existing remote/ref configuration, the optional
244
324
  process-local identity selection above, existing credentials and hooks.
325
+ With `target`, the final push uses the selected current-HEAD mapping above.
245
326
  There is no implicit pull, branch switch, forced push,
246
327
  hook bypass, automatic retry, rollback or reset. If Git reports no changes to
247
328
  commit, that actual failure is returned; the SDK creates no empty commit.
@@ -252,7 +333,8 @@ Existing Git attributes, clean filters, EOL settings and hooks continue to apply
252
333
  when Git stages/commits content. The filesystem write preserves the original
253
334
  text; this API does not claim that repository-configured filters leave Git blob
254
335
  text identical. It preserves those settings. A push may also send existing history or other
255
- refs selected by the repository's ordinary push configuration.
336
+ refs selected by the repository's ordinary push configuration when no explicit
337
+ target is supplied.
256
338
 
257
339
  ### Result and failure outcomes
258
340
 
@@ -275,6 +357,8 @@ Successful completion returns:
275
357
 
276
358
  Each output record retains complete stdout/stderr. `code` and the process
277
359
  termination `signal` are included when a completed process result is available.
360
+ When supplied, `target:{remote,ref}` is included in the result or failure outcome
361
+ with the original accepted strings.
278
362
  An in-progress write also records its current `path` on a failure outcome.
279
363
  `writtenPaths` records successful filesystem writes in input order; `written`
280
364
  becomes true only after every requested write succeeds. The other booleans
@@ -66,11 +66,34 @@ service's startup before invoking its handler. State errors use the shared
66
66
  complete diagnostic serialization; the startup promise retains its original
67
67
  rejection value.
68
68
 
69
- Each lifecycle hook receives `{application, service, emit}`. Each request handler
70
- receives `(parameters, {application, service, emit, requestId, signal})`, with
69
+ Each lifecycle hook receives `{application, service, emit, getService}`. Each request handler
70
+ receives `(parameters, {application, service, emit, getService, requestId, signal})`, with
71
71
  `this` bound to its service definition. A hook or handler may publish a complete
72
72
  service-owned event through `emit(event, data)`.
73
73
 
74
+ ### Native service composition
75
+
76
+ `await runtime.getService(name)` and `await context.getService(name)` return the
77
+ actual registered service definition after that service's shared startup
78
+ promise. A lookup can start its selected dependency before `runtime.start()`;
79
+ it waits for that dependency alone, with no renderer, transport request or
80
+ all-service readiness barrier. Concurrent lookups reuse the same startup and
81
+ return the same object. Applications keep their service dependencies acyclic.
82
+
83
+ Use the returned service's documented native members, such as
84
+ [`getONNXRuntime()`](local-ai.md#native-service-owners), for same-process
85
+ composition. RPC handlers remain owned by the dispatcher; calling entries in
86
+ `service.methods` directly does not supply request tracking or cancellation.
87
+ No service lookup is exposed over the browser protocol.
88
+
89
+ An unregistered name rejects with `CORE_SERVICE_UNAVAILABLE`. Startup failure
90
+ rejects with the original error. New lookups reject with `CORE_CLOSING` once
91
+ shutdown begins; an already accepted lookup still settles with its startup.
92
+ Lookup provides readiness, not a lifetime lease or ownership transfer. Retain
93
+ each acquired native owner according to its own documented cleanup contract,
94
+ and use that retained handle during disposal rather than requesting a new one.
95
+ Independent services still dispose concurrently.
96
+
74
97
  ## State and frames
75
98
 
76
99
  `runtime.current()` returns dispatcher state, application, version, per-service
@@ -133,9 +156,13 @@ the handler runs. A service that does not finish or respond to cancellation is
133
156
  still unfinished; the SDK does not report a forced timeout as successful cleanup.
134
157
 
135
158
  Use `lifetime:'service'` for accepted work, such as a save, that must survive a
136
- renderer cancellation or disconnect. These operations are not aborted by the
137
- request cancellation controls or by normal Core shutdown. The service still
138
- owns its persistence, queueing and failure semantics.
159
+ renderer cancellation or disconnect. The dispatcher does not abort these
160
+ operations through request cancellation controls or its own shutdown. The
161
+ service still owns its persistence, queueing and failure semantics. A composing
162
+ host or native service can abort its own lifetime signal during shutdown;
163
+ in-flight native inference can therefore be cancelled even when the dispatcher
164
+ retains a service-lifetime response. Service lifetime does not promise survival
165
+ of native host shutdown.
139
166
 
140
167
  `runtime.close()` is idempotent and retains the same completion promise. It stops
141
168
  accepting new requests, cancels request-lifetime operations, waits for accepted
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "sdkVersion": "0.65.0",
3
+ "sdkVersion": "0.66.0",
4
4
  "environment": {
5
5
  "runtime": "Node.js for Node entrypoints; browser for browser-only entrypoints",
6
6
  "minimumVersion": "22.23.2 for Node entrypoints",
@@ -164,7 +164,7 @@
164
164
  "summary": "Owns one selected native working checkout with ArcaneData/Repos defaults and existing Git/process lifecycle.",
165
165
  "availability": "Node with Git on Windows, Linux and macOS; Android requires host process adaptation",
166
166
  "protocol": "Git working repository and SDK process adapter",
167
- "normalization": "Returns directory, open, status, pull, push, write and identical close/drain/dispose hooks. Only missing or empty destinations clone; existing roots are inspected without rewriting, resetting or pulling. Explicit paths remain selected. Same-directory operations serialize within the process; cancellation and shutdown join accepted process work. write snapshots exact selected text and paths, writes files, stages/commits only literal owned paths and non-force pushes, preserving unrelated staged files and ordinary Git filters. Complete outputs and causal failures retain confirmed local/committed/pushed and uncertain outcomes. Optional gitIdentity captures name/email/username for child Git operations only; omitted fields remain inherited and no config file is written."
167
+ "normalization": "Returns directory, open, status, configuration, pull, push, write and identical close/drain/dispose hooks. Only missing or empty destinations clone; existing roots are preserved. configuration observes the existing root, symbolic HEAD, origin URL/pushURL arrays and branch remote/merge/upstream URL arrays without clone/init/fetch, preserving unset, empty and repeated values. Explicit paths remain selected. Same-directory operations serialize within the process; cancellation and shutdown join accepted process work. pull/push/write accept a captured target {remote,ref} for current-branch fast-forward pull or non-force current-HEAD selected-ref push; omission retains ordinary configured behavior. write snapshots exact text and paths, stages/commits only literal owned paths and preserves unrelated staged files and Git filters. Complete outputs and causal failures retain confirmed local/committed/pushed and uncertain outcomes plus the supplied target. Optional gitIdentity captures name/email/username for child Git operations only; omitted fields remain inherited and no config file is written."
168
168
  },
169
169
  {
170
170
  "id": "core-repositories:readGitIdentity",
@@ -493,14 +493,14 @@
493
493
  "name": "createBrowserDecisionModel",
494
494
  "displayName": "createBrowserDecisionModel()",
495
495
  "kind": "function",
496
- "signature": "createBrowserDecisionModel({family,model,revision,device,runtime})",
496
+ "signature": "createBrowserDecisionModel({family,model,revision,device,dtype,runtime}={})",
497
497
  "entrypoints": ["arcane-os/ai/browser-decisions"],
498
498
  "primaryImport": "arcane-os/ai/browser-decisions",
499
499
  "group": "Browser typed decisions",
500
- "summary": "Explicit-load Laya FP16 and Julia-1 FP32 decisions with complete inputs and graph results.",
500
+ "summary": "Explicit-load Laya and Julia-1 decisions with caller-selected precision, complete inputs and graph results.",
501
501
  "availability": "Browser module Workers and the selected Transformers.js backend; import/construction starts no model load",
502
502
  "protocol": "Caller-owned state/question/options rows; five-input decision graph; native Worker request/result lifecycle",
503
- "normalization": "Full field tokenization, raw logits and unrounded softmax; no policy thresholds, storage or precision fallback"
503
+ "normalization": "Full field tokenization, raw logits and unrounded softmax; dtype defaults to fp16 for Laya and fp32 for Julia, with explicit values forwarded unchanged to the upstream loader; no policy thresholds, storage or precision fallback"
504
504
  },
505
505
  {
506
506
  "id": "root:acceptWebSocket",
@@ -2106,13 +2106,13 @@
2106
2106
  "name": "repositoryPull",
2107
2107
  "displayName": "repositoryPull()",
2108
2108
  "kind": "function",
2109
- "signature": "async repositoryPull({ workspaceRoot=process.cwd(), signal, onEvent, run=runProcess }={})",
2109
+ "signature": "async repositoryPull({ workspaceRoot=process.cwd(), target, signal, onEvent, run=runProcess }={})",
2110
2110
  "entrypoints": [
2111
2111
  "arcane-os"
2112
2112
  ],
2113
2113
  "primaryImport": "arcane-os",
2114
2114
  "group": "Workspace, doctor, repository, and server",
2115
- "summary": "Runs one fast-forward-only pull after proving the selected repository worktree is clean.",
2115
+ "summary": "Runs one fast-forward-only pull into the selected clean worktree, optionally from a captured remote/ref target.",
2116
2116
  "availability": "Node; network-assisted Git",
2117
2117
  "protocol": "Node ESM",
2118
2118
  "normalization": "SDK-normalized inputs, errors, events, and documented result"
@@ -2122,13 +2122,13 @@
2122
2122
  "name": "repositoryPush",
2123
2123
  "displayName": "repositoryPush()",
2124
2124
  "kind": "function",
2125
- "signature": "async repositoryPush({ workspaceRoot=process.cwd(), signal, onEvent, run=runProcess }={})",
2125
+ "signature": "async repositoryPush({ workspaceRoot=process.cwd(), target, signal, onEvent, run=runProcess }={})",
2126
2126
  "entrypoints": [
2127
2127
  "arcane-os"
2128
2128
  ],
2129
2129
  "primaryImport": "arcane-os",
2130
2130
  "group": "Workspace, doctor, repository, and server",
2131
- "summary": "Pushes the selected attached branch through the repository's configured remote and credentials.",
2131
+ "summary": "Pushes the attached branch through ordinary configured Git or an explicit captured current-HEAD destination without force.",
2132
2132
  "availability": "Node; network-assisted Git",
2133
2133
  "protocol": "Node ESM",
2134
2134
  "normalization": "SDK-normalized inputs, errors, events, and documented result"
@@ -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