arcane-os 0.84.0 → 0.85.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.85.0
4
+
5
+ - Add `createModelObservationService({runtime, localAI, image, decisions})`
6
+ through `arcane-os/core/model-observation`, and renderer
7
+ `attachModelObservation` / observer `subscribeModelObservation` through
8
+ `arcane-os/ai/model-observation`. Observe the actual existing Core and
9
+ renderer model owners without creating, loading or replacing a runtime,
10
+ provider or model.
11
+ - Replay complete current owner state and retain its reported errors, with
12
+ explicit opt-in live diagnostics and connection-owned cancellation. Keep
13
+ original client request identity separate from actual internal Core request
14
+ identity. Native `coreListener` composition reuses existing selected services;
15
+ applications explicitly attach their renderer owners.
16
+ - Preserve the complete Windows PnP property batch while narrowing only the
17
+ property cmdlet's internal concurrency. Keep asynchronous shared discovery,
18
+ cancellation, explicit refresh and model/device selection unchanged. This
19
+ addresses the observed missing/duplicate property rows without adding retries
20
+ or another hardware query.
21
+ - These capabilities expose current state and correct the property query;
22
+ they do not claim to repair or verify native model inference. No dependency
23
+ changes or native host rebuild are required for this revision.
24
+
3
25
  ## 0.84.0
4
26
 
5
27
  - Add `startCoreListener({runtime, endpoint, onError})` through
package/README.md CHANGED
@@ -25,7 +25,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
25
25
  share the theme, packaging, event, cancellation,
26
26
  and browser run contracts while retaining their selected application layout.
27
27
 
28
- This checkout defines the `0.84.0` SDK contract. Development applications declare
28
+ This checkout defines the `0.85.0` SDK contract. Development applications declare
29
29
  `arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
30
30
  designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
31
31
  to refresh and compare the installed SDK and lockfile with the published channel;
@@ -0,0 +1,315 @@
1
+ import Is from '../dependencies/strong-type/index.js';
2
+ import {CoreError, serializeCoreError} from '../core/contracts.mjs';
3
+ import {getInstalledCoreClient, subscribeCoreClient} from '../core/client.mjs';
4
+
5
+ const is = new Is(false);
6
+
7
+ /** Attach the caller's existing renderer owners to its existing Core client. */
8
+ export function attachModelObservation({client, aiRuntimeState = null, imageRuntime = null,
9
+ decisionModel = null, modelController = null, signal, onError} = {}) {
10
+ requireOwner(aiRuntimeState, ['getAIRuntimeState', 'subscribeAIRuntimeState'], 'aiRuntimeState');
11
+ requireOwner(imageRuntime, ['current', 'subscribe'], 'imageRuntime');
12
+ requireOwner(decisionModel, ['status', 'subscribe'], 'decisionModel');
13
+ requireOwner(modelController, ['status', 'on'], 'modelController');
14
+ const lifetime = observationLifetime({client, signal, onError});
15
+ let attachmentRequestId;
16
+ let coreRequestId;
17
+ let snapshot = null;
18
+ let attached = false;
19
+
20
+ function readOwners() {
21
+ return {
22
+ aiRuntimeState: aiRuntimeState?.getAIRuntimeState() ?? null,
23
+ imageRuntime: imageRuntime?.current() ?? null,
24
+ decisionModel: decisionModel?.status() ?? null,
25
+ modelController: modelController?.status() ?? null
26
+ };
27
+ }
28
+
29
+ function publishOwners() {
30
+ if (!attached || lifetime.stopped) return;
31
+ try {
32
+ snapshot = readOwners();
33
+ lifetime.invoke('model.observation.renderer.publish', {
34
+ attachmentRequestId, coreRequestId, snapshot: transportSnapshot(snapshot)
35
+ });
36
+ } catch (error) {
37
+ lifetime.fail(error);
38
+ }
39
+ }
40
+
41
+ function observeOwners() {
42
+ if (aiRuntimeState) lifetime.cleanup(aiRuntimeState.subscribeAIRuntimeState(
43
+ publishOwners, {emitCurrent: false, signal: lifetime.signal}
44
+ ));
45
+ if (imageRuntime) lifetime.cleanup(imageRuntime.subscribe(
46
+ publishOwners, {replay: false, signal: lifetime.signal}
47
+ ));
48
+ if (decisionModel) lifetime.cleanup(decisionModel.subscribe(
49
+ publishOwners, {emitCurrent: false, signal: lifetime.signal}
50
+ ));
51
+ if (modelController) {
52
+ lifetime.cleanup(modelController.on('statechange', publishOwners));
53
+ lifetime.cleanup(modelController.on('progress', publishOwners));
54
+ }
55
+ }
56
+
57
+ function acknowledgeAttachment(value) {
58
+ if (lifetime.stopped || attached || value?.attachmentRequestId !== attachmentRequestId) return;
59
+ try {
60
+ if (!is.string(value.coreRequestId)) {
61
+ throw observationError('MODEL_OBSERVATION_CONTEXT_UNAVAILABLE',
62
+ 'The renderer acknowledgement omitted the actual Core request identity.');
63
+ }
64
+ coreRequestId = value.coreRequestId;
65
+ attached = true;
66
+ observeOwners();
67
+ // Read again after all subscriptions exist so changes during the
68
+ // attachment handshake are represented by current owner state.
69
+ publishOwners();
70
+ lifetime.acknowledge(value);
71
+ } catch (error) {
72
+ lifetime.fail(error);
73
+ }
74
+ }
75
+
76
+ if (!lifetime.stopped) {
77
+ try {
78
+ snapshot = readOwners();
79
+ lifetime.cleanup(client.events.on('model.observation.renderer.attached', acknowledgeAttachment));
80
+ lifetime.invoke('model.observation.renderer.attach', {snapshot: transportSnapshot(snapshot)}, {
81
+ long: true,
82
+ onRequest: function identifyAttachment(value) { attachmentRequestId = value.requestId; }
83
+ });
84
+ } catch (error) {
85
+ lifetime.fail(error);
86
+ }
87
+ }
88
+
89
+ return {
90
+ ready: lifetime.ready,
91
+ closed: lifetime.closed,
92
+ current: function currentRendererObservation() { return snapshot; },
93
+ close: lifetime.close
94
+ };
95
+ }
96
+
97
+ /** Subscribe through an existing Core client; model diagnostics are explicit. */
98
+ export function subscribeModelObservation({client, signal, onState, onDiagnostic, onError} = {}) {
99
+ if (!is.function(onState)) throw new TypeError('Model observation requires an onState listener.');
100
+ if (onDiagnostic !== undefined && !is.function(onDiagnostic)) {
101
+ throw new TypeError('onDiagnostic must be a function.');
102
+ }
103
+ const lifetime = observationLifetime({client, signal, onError});
104
+ let watchRequestId;
105
+ let snapshot = null;
106
+
107
+ function receiveState(value) {
108
+ if (lifetime.stopped || value?.watchRequestId !== watchRequestId) return;
109
+ if (!value.snapshot || !is.object(value.snapshot)) {
110
+ lifetime.fail(observationError('MODEL_OBSERVATION_STATE_INVALID', 'Core omitted the observation snapshot.'));
111
+ return;
112
+ }
113
+ snapshot = value.snapshot;
114
+ lifetime.acknowledge(snapshot);
115
+ lifetime.notify(onState, snapshot);
116
+ }
117
+
118
+ function receiveDiagnostic(value) {
119
+ if (lifetime.stopped || value?.watchRequestId !== watchRequestId) return;
120
+ lifetime.notify(onDiagnostic, value);
121
+ }
122
+
123
+ if (!lifetime.stopped) {
124
+ try {
125
+ lifetime.cleanup(client.events.on('model.observation.state', receiveState));
126
+ if (onDiagnostic) lifetime.cleanup(client.events.on('model.observation.error', receiveDiagnostic));
127
+ lifetime.invoke('model.observation.watch', {diagnostics: Boolean(onDiagnostic)}, {
128
+ long: true,
129
+ onRequest: function identifyWatch(value) { watchRequestId = value.requestId; }
130
+ });
131
+ } catch (error) {
132
+ lifetime.fail(error);
133
+ }
134
+ }
135
+
136
+ return {
137
+ ready: lifetime.ready,
138
+ closed: lifetime.closed,
139
+ current: function currentModelObservation() { return snapshot; },
140
+ close: lifetime.close
141
+ };
142
+ }
143
+
144
+ function requireOwner(owner, methods, label) {
145
+ if (owner === null) return;
146
+ for (const method of methods) {
147
+ if (!is.function(owner?.[method])) throw new TypeError(`${label} must be an existing owner exposing ${method}().`);
148
+ }
149
+ }
150
+
151
+ function observationError(code, message) {
152
+ return new CoreError({code, message});
153
+ }
154
+
155
+ function cancelledObservation() {
156
+ return new CoreError({name: 'AbortError', code: 'ARCANE_REQUEST_ABORTED', message: 'Model observation was cancelled.'});
157
+ }
158
+
159
+ function isCancellation(error) {
160
+ return error?.name === 'AbortError' || error?.code === 'ARCANE_REQUEST_ABORTED' || error?.code === 'REQUEST_ABORTED';
161
+ }
162
+
163
+ /** Error encoding belongs only to the existing JSON transport boundary. */
164
+ function transportSnapshot(snapshot) {
165
+ return Object.fromEntries(Object.entries(snapshot).map(function transportOwner([name, value]) {
166
+ return [name, value && is.error(value.error) ? {...value, error: serializeCoreError(value.error)} : value];
167
+ }));
168
+ }
169
+
170
+ function reportObservationFailure(error) {
171
+ globalThis.console?.error('Model observation failed.', error);
172
+ }
173
+
174
+ /** Own only observation RPCs and subscriptions, never the supplied owners. */
175
+ function observationLifetime({client, signal, onError = reportObservationFailure}) {
176
+ if (!is.function(client?.invoke) || !is.function(client?.events?.on)) {
177
+ throw new TypeError('Model observation requires an existing Core client.');
178
+ }
179
+ if (!is.function(onError)) throw new TypeError('onError must be a function.');
180
+ const controller = new AbortController();
181
+ const cleanups = [];
182
+ const pending = new Set();
183
+ const failures = [];
184
+ let stopped = false;
185
+ let acknowledged = false;
186
+ let completed = false;
187
+ let resolveReady;
188
+ let rejectReady;
189
+ let resolveClosed;
190
+ let rejectClosed;
191
+ const ready = new Promise(function ownObservationReadiness(resolve, reject) {
192
+ resolveReady = resolve;
193
+ rejectReady = reject;
194
+ });
195
+ const closed = new Promise(function ownObservationClosure(resolve, reject) {
196
+ resolveClosed = resolve;
197
+ rejectClosed = reject;
198
+ });
199
+ // The same failures are reported through onError and retained on these
200
+ // public promises; unused companion promises must not create extra alerts.
201
+ ready.catch(function readinessFailureObservedByLifetime() {});
202
+ closed.catch(function closureFailureObservedByLifetime() {});
203
+
204
+ function recordFailure(error) {
205
+ if (failures.includes(error)) return;
206
+ failures.push(error);
207
+ try {
208
+ Promise.resolve(onError(error)).catch(function reportErrorCallbackFailed(callbackError) {
209
+ reportObservationFailure(callbackError);
210
+ });
211
+ } catch (callbackError) {
212
+ reportObservationFailure(callbackError);
213
+ }
214
+ }
215
+
216
+ function finish() {
217
+ if (!stopped || pending.size || completed) return;
218
+ completed = true;
219
+ if (failures.length === 1) rejectClosed(failures[0]);
220
+ else if (failures.length) rejectClosed(new AggregateError(failures, 'Model observation closed with errors.'));
221
+ else resolveClosed();
222
+ }
223
+
224
+ function close() {
225
+ if (!stopped) {
226
+ stopped = true;
227
+ for (const cleanup of cleanups.splice(0)) {
228
+ try { cleanup(); } catch (error) { recordFailure(error); }
229
+ }
230
+ controller.abort();
231
+ if (!acknowledged) rejectReady(failures[0] ?? cancelledObservation());
232
+ }
233
+ finish();
234
+ return closed;
235
+ }
236
+
237
+ function fail(error) {
238
+ recordFailure(error);
239
+ close();
240
+ }
241
+
242
+ function cleanup(release) {
243
+ if (!is.function(release)) throw new TypeError('An observation subscription must return an unsubscribe function.');
244
+ if (stopped) release();
245
+ else cleanups.push(release);
246
+ }
247
+
248
+ function invoke(method, parameters, {long = false, onRequest} = {}) {
249
+ if (stopped) return;
250
+ // Reserve ownership before invoke: an in-process transport can deliver
251
+ // acknowledgement synchronously while invoke is still on the stack.
252
+ const operation = {};
253
+ pending.add(operation);
254
+ let task;
255
+ try {
256
+ task = client.invoke(method, parameters, {signal: controller.signal, timeoutMs: 0, onRequest});
257
+ } catch (error) {
258
+ task = Promise.reject(error);
259
+ }
260
+ Promise.resolve(task).then(function observationCompleted() {
261
+ pending.delete(operation);
262
+ if (long && !stopped) {
263
+ if (!acknowledged) fail(observationError('MODEL_OBSERVATION_ENDED', 'The observation ended before acknowledgement.'));
264
+ else close();
265
+ }
266
+ finish();
267
+ }, function observationFailed(error) {
268
+ pending.delete(operation);
269
+ if (isCancellation(error)) close();
270
+ else fail(error);
271
+ finish();
272
+ });
273
+ }
274
+
275
+ function acknowledge(value) {
276
+ if (stopped || acknowledged) return;
277
+ acknowledged = true;
278
+ resolveReady(value);
279
+ }
280
+
281
+ function notify(listener, value) {
282
+ if (stopped || !listener) return;
283
+ try {
284
+ // Callback lifetimes belong to callers, who may await close().
285
+ // Observe rejection without making that callback a close barrier.
286
+ Promise.resolve(listener(value)).catch(fail);
287
+ } catch (error) {
288
+ fail(error);
289
+ }
290
+ }
291
+
292
+ function retireObservation() { close(); }
293
+ if (signal) {
294
+ signal.addEventListener('abort', retireObservation, {once: true});
295
+ cleanup(function releaseCallerSignal() { signal.removeEventListener('abort', retireObservation); });
296
+ }
297
+ if (is.function(globalThis.addEventListener)) {
298
+ globalThis.addEventListener('pagehide', retireObservation, {once: true});
299
+ cleanup(function releasePageLifetime() { globalThis.removeEventListener('pagehide', retireObservation); });
300
+ }
301
+ if (getInstalledCoreClient() === client) {
302
+ cleanup(subscribeCoreClient(function installedOwnerChanged(value) {
303
+ if (value.client !== client) {
304
+ if (value.error) fail(value.error);
305
+ else close();
306
+ }
307
+ }, {emitCurrent: false}));
308
+ }
309
+ if (signal?.aborted) close();
310
+
311
+ return {
312
+ ready, closed, signal: controller.signal, cleanup, invoke, acknowledge, notify, fail, close,
313
+ get stopped() { return stopped; }
314
+ };
315
+ }
@@ -245,6 +245,20 @@ listener endpoint has no default and its parent directory must already exist
245
245
  where required by the operating system. `coreListener` and `sharedHost` are
246
246
  alternative selections; supplying both reports `TypeError`.
247
247
 
248
+ That same `coreListener` opt-in registers one
249
+ [`createModelObservationService()`](model-observation.md) after `startCoreHost()`
250
+ and before binding the listener. It receives `host.runtime` and the exact
251
+ already-created service objects exposing `localai.status`, `image.status` and
252
+ `decisions.status`; a configurable decision-service name remains supported.
253
+ Absent owners are `null`. Composition creates no model or second runtime,
254
+ performs no service lookup and adds no model-readiness wait. Ordinary stdio
255
+ without this opt-in and the existing `sharedHost` branches retain their behavior.
256
+ The application separately calls `attachModelObservation()` with its existing
257
+ renderer owners and Core client; native composition cannot infer those objects.
258
+ Observers select `onDiagnostic` to receive complete live model failure frames
259
+ in their developer diagnostic surface. Observation does not start inference or
260
+ establish that an existing model failure has been corrected.
261
+
248
262
  Clients use `connectSharedCoreHost({endpoint})` with `start` omitted and close
249
263
  only their own connection. This listener provides no host-shutdown RPC. Closing
250
264
  the window still drains its Core, including accepted service-owned operations,
@@ -67,7 +67,7 @@ complete diagnostic serialization; the startup promise retains its original
67
67
  rejection value.
68
68
 
69
69
  Each lifecycle hook receives `{application, service, emit, getService}`. Each request handler
70
- receives `(parameters, {application, service, emit, getService, requestId, coreRequestId, signal})`, with
70
+ receives `(parameters, {application, service, emit, getService, requestId, clientRequestId, coreRequestId, 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
 
@@ -133,16 +133,25 @@ availability errors remain observable; unavailable services are not replaced
133
133
  with browser behavior.
134
134
 
135
135
  A transport that assigns internal dispatch IDs may call
136
- `runtime.handle(frame, {contextRequestId})`. This optional nonempty string sets
137
- only the service handler's `context.requestId`. `context.coreRequestId` always
138
- exposes the actual `frame.id`; it distinguishes simultaneous requests whose
139
- clients selected equal IDs. The supplied `frame.id` still
140
- owns active-request state, cancellation lookup, response correlation and the
141
- request event's top-level envelope. Parameters and event data remain unchanged.
142
- Ordinary `handle(frame)` keeps its existing behavior: `context.requestId` equals
143
- `frame.id`, and `coreRequestId` equals that same ID. Stdio and
144
- the borrowed IPC listener use this metadata to preserve the original client ID
145
- in service-facing data while their transport owner routes unique dispatch IDs.
136
+ `runtime.handle(frame, {contextRequestId, contextClientRequestId})`. Each supplied
137
+ metadata value must be a nonempty string. Handler identities are:
138
+
139
+ | Field | Value |
140
+ |---|---|
141
+ | `requestId` | `contextRequestId ?? frame.id`, preserving the transport's established handler contract |
142
+ | `clientRequestId` | `contextClientRequestId ?? contextRequestId ?? frame.id`, exposing the actual incoming client identity |
143
+ | `coreRequestId` | `frame.id`, the actual internal dispatch identity |
144
+
145
+ Ordinary `handle(frame)` exposes all three equal to `frame.id`. Existing callers
146
+ that supply only `contextRequestId` also receive that value as `clientRequestId`.
147
+ Stdio and both IPC listener forms always supply the actual client ID separately.
148
+ Stdio and the borrowed listener preserve it as `requestId` too; the owning
149
+ shared-host adapter retains its established internal `requestId` contract.
150
+
151
+ The supplied `frame.id` still owns active-request state, cancellation lookup,
152
+ response correlation and the request event's top-level envelope. It distinguishes
153
+ simultaneous requests whose clients selected equal IDs. Parameters, results and
154
+ event data remain unchanged; these identity fields are separate context metadata.
146
155
 
147
156
  A request with no registered handler retains code `METHOD_NOT_ALLOWED` and
148
157
  adds the exact `method`, its first dotted `namespace`, and a `reason`.
@@ -58,7 +58,8 @@ the supplied runtime alive. A draining/closed runtime rejects attachment with
58
58
 
59
59
  Stdio and each IPC connection have independent request correlation and
60
60
  cancellation. Handlers reached through stdio or this listener receive the
61
- original client ID in `context.requestId`; `context.coreRequestId` exposes the
61
+ original client ID in both `context.requestId` and `context.clientRequestId`;
62
+ `context.coreRequestId` exposes the
62
63
  fresh internal ID used by runtime dispatch and protocol routing. Responses and
63
64
  top-level event correlation return to their originating connection. The new listener leaves all event data,
64
65
  including any authored `data.requestId`, and all parameters/results unchanged.
@@ -186,8 +187,13 @@ substitutes an internal Core request ID and restores the originating ID on respo
186
187
  the optional top-level event `requestId`. Existing `data.requestId` values that
187
188
  carry that same Core correlation ID are restored too. `parameters`, returned results, schemas,
188
189
  documents, and content remain complete and unchanged. A handler's
189
- `context.requestId` is the Core-side correlation ID; put it in the event's
190
- separate `requestId` field, not inside application content.
190
+ `context.requestId` remains the Core-side correlation ID, equal to
191
+ `context.coreRequestId`; put it in the event's separate `requestId` field, not
192
+ inside application content. The additive `context.clientRequestId` exposes the
193
+ actual incoming client ID independently of that compatibility behavior. Services
194
+ such as [model observation](model-observation.md) can therefore publish explicit
195
+ client-facing acknowledgement fields without changing the existing adapter or
196
+ rewriting application payloads.
191
197
 
192
198
  Responses and request-context events go to the originating connection in
193
199
  order. Core supplies the top-level request correlation outside `data`, so two
@@ -41,6 +41,12 @@ An adapter whose stable identity cannot be established remains in the inventory
41
41
  with `deviceId: null` and a corresponding issue. The SDK does not invent a key
42
42
  from a name, enumeration order, hardware model number or adapter LUID.
43
43
 
44
+ Windows discovery reads the present PCI devices' bus and address properties in
45
+ one batched `Get-PnpDeviceProperty` call with `-ThrottleLimit 1`. This limits
46
+ concurrency inside that query because concurrent CIM operations have been
47
+ observed to duplicate some device results and omit others. Discovery remains
48
+ asynchronous at the host, with the same shared catalog and explicit refresh.
49
+
44
50
  The CPU choice uses `deviceId: 'cpu'` and `identitySource: 'host-cpu'`. It selects
45
51
  the whole-host CPU execution target, without claiming socket or core affinity.
46
52
  Its `processors` entries contain `{deviceId, name, socketDesignation, cores,
@@ -1,17 +1,59 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "sdkVersion": "0.83.0",
3
+ "sdkVersion": "0.83.1",
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",
7
7
  "moduleSystem": "ESM"
8
8
  },
9
- "memberCount": 303,
9
+ "memberCount": 306,
10
10
  "runtimeSubpathPatterns": {
11
11
  "arcane-os/modules/*": "./runtime/arcane/modules/*",
12
12
  "arcane-os/entities/*": "./runtime/arcane/entities/*"
13
13
  },
14
14
  "members": [
15
+ {
16
+ "id": "core-model-observation:createModelObservationService",
17
+ "name": "createModelObservationService",
18
+ "displayName": "createModelObservationService()",
19
+ "kind": "function",
20
+ "signature": "createModelObservationService({runtime,localAI=null,image=null,decisions=null}={})",
21
+ "entrypoints": ["arcane-os/core/model-observation"],
22
+ "primaryImport": "arcane-os/core/model-observation",
23
+ "group": "Existing model observation",
24
+ "summary": "Observes an existing Core runtime and supplied actual native model service owners without creating or starting them.",
25
+ "availability": "Node with an existing Core runtime and optional existing native model services",
26
+ "protocol": "arcane/1 model.observation current, watch, renderer.attach and renderer.publish methods",
27
+ "normalization": "Returns name:model-observation, current(), dispose() and four methods. Complete current snapshots retain core, optional native localAI/image/decisions and request-owned renderer attachments. Missing owners are null; watch diagnostics defaults false and explicit true exposes complete live failed response frames for supplied native method keys without retained failure history. Actual clientRequestId identifies acknowledgements; coreRequestId keys renderer lifetime. Disposal cancels only observation requests/subscriptions, never models or the supplied runtime. No getService, readiness barrier, model creation, inference, polling or fabricated operation identities."
28
+ },
29
+ {
30
+ "id": "ai-model-observation:attachModelObservation",
31
+ "name": "attachModelObservation",
32
+ "displayName": "attachModelObservation()",
33
+ "kind": "function",
34
+ "signature": "attachModelObservation({client,aiRuntimeState=null,imageRuntime=null,decisionModel=null,modelController=null,signal,onError}={})",
35
+ "entrypoints": ["arcane-os/ai/model-observation"],
36
+ "primaryImport": "arcane-os/ai/model-observation",
37
+ "group": "Existing model observation",
38
+ "summary": "Attaches the application's existing renderer model owners through its existing Core client.",
39
+ "availability": "JavaScript with an existing Core client and optional existing renderer owners",
40
+ "protocol": "Request-owned model.observation.renderer attachment and complete snapshot publication",
41
+ "normalization": "Returns ready, closed, current() and close(). Ready acknowledges actual attachmentRequestId/coreRequestId; subscriptions publish complete current owner snapshots and reread after acknowledgement. Existing Core serialization encodes owner Error fields at the JSON boundary without changing other fields. Abort, page retirement, exact installed-client retirement, transport failure or explicit close retires only observation RPCs/subscriptions and leaves models/client owned by the caller. Normal cancellation closes cleanly; pre-acknowledgement cancellation rejects ready. Other failures reject public promises and reach onError or complete console diagnostics. No automatic reattachment, owner creation, model work or page-render barrier."
42
+ },
43
+ {
44
+ "id": "ai-model-observation:subscribeModelObservation",
45
+ "name": "subscribeModelObservation",
46
+ "displayName": "subscribeModelObservation()",
47
+ "kind": "function",
48
+ "signature": "subscribeModelObservation({client,signal,onState,onDiagnostic,onError}={})",
49
+ "entrypoints": ["arcane-os/ai/model-observation"],
50
+ "primaryImport": "arcane-os/ai/model-observation",
51
+ "group": "Existing model observation",
52
+ "summary": "Subscribes to complete existing model-owner snapshots and explicitly selected live Core diagnostics.",
53
+ "availability": "JavaScript with an existing Core client and composed observation service",
54
+ "protocol": "Request-owned model.observation.watch with targeted state and optional error events",
55
+ "normalization": "Requires onState; returns ready, closed, current() and close(). Ready resolves the first current snapshot; onDiagnostic explicitly selects diagnostics:true and receives complete correlated live failed Core response frames, without recovered history or inferred model IDs. Closing, abort, page/client retirement and transport failures retire only observation work. Normal cancellation closes cleanly; other errors reach onError and reject public promises. Callback errors close the observation while callback promises remain caller-owned. No model refresh, inference, polling, host launch, endpoint discovery, reconnect or saved conversation content."
56
+ },
15
57
  {
16
58
  "id": "core-app-control:connectAppControl",
17
59
  "name": "connectAppControl",
@@ -4374,7 +4416,7 @@
4374
4416
  "summary": "Creates an app-neutral Core dispatcher with explicit services, request lifetimes, events and graceful drain.",
4375
4417
  "availability": "Node on Windows, Linux and macOS native hosts; Android requires its host adaptation",
4376
4418
  "protocol": "arcane/1 dispatcher and service lifecycle",
4377
- "normalization": "Starts services independently; runtime.getService(name) and context.getService(name) return the same registered object after only its shared startup, without a renderer or global barrier. Lookup is readiness, not a lifetime lease; new lookups reject during shutdown. runtime.handle(frame,{contextRequestId}) optionally sets handler requestId while coreRequestId remains frame.id; omitted metadata exposes both equal. Internal IDs continue to own cancellation/state/event envelopes and payloads remain unchanged. Preserves complete results/errors, cancels request-lifetime work and drains accepted service work; runtime.replay publishes current dispatcher/service state without restart and preserves reentrant lifecycle publication order. Missing dispatch retains METHOD_NOT_ALLOWED with exact method/namespace and core-namespace-unavailable or core-method-unavailable; named services, registered methods and built-in namespaces establish presence. Handler/startup/permission errors remain unchanged."
4419
+ "normalization": "Starts services independently; runtime.getService(name) and context.getService(name) return the same registered object after only its shared startup, without a renderer or global barrier. Lookup is readiness, not a lifetime lease; new lookups reject during shutdown. runtime.handle(frame,{contextRequestId,contextClientRequestId}) sets requestId=contextRequestId??frame.id, clientRequestId=contextClientRequestId??contextRequestId??frame.id and coreRequestId=frame.id; omitted metadata exposes all three equal. Transports always supply the actual client ID separately, preserving the owning shared-host internal requestId contract. Internal IDs continue to own cancellation/state/event envelopes and payloads remain unchanged. Preserves complete results/errors, cancels request-lifetime work and drains accepted service work; runtime.replay publishes current dispatcher/service state without restart and preserves reentrant lifecycle publication order. Missing dispatch retains METHOD_NOT_ALLOWED with exact method/namespace and core-namespace-unavailable or core-method-unavailable; named services, registered methods and built-in namespaces establish presence. Handler/startup/permission errors remain unchanged."
4378
4420
  },
4379
4421
  {
4380
4422
  "id": "core-stdio:encodeCoreFrame",
@@ -4416,7 +4458,7 @@
4416
4458
  "summary": "Attaches an existing Core runtime to framed input and output with concurrent dispatch and graceful shutdown.",
4417
4459
  "availability": "Node with readable input and writable output streams",
4418
4460
  "protocol": "Content-Length framed arcane/1 stdio",
4419
- "normalization": "Returns {runtime,closed,close}; scopes responses/request events, cancellation and current-state replay to its connection, preserving original service-facing requestId and complete payloads while routing unique internal IDs. Preserves queued output order, reports parser and stream failures, drains accepted runtime work on EOF and never exits the host or ends caller-owned output"
4461
+ "normalization": "Returns {runtime,closed,close}; scopes responses/request events, cancellation and current-state replay to its connection, preserving original service-facing requestId/clientRequestId and complete payloads while coreRequestId identifies unique internal dispatch. Preserves queued output order, reports parser and stream failures, drains accepted runtime work on EOF and never exits the host or ends caller-owned output"
4420
4462
  },
4421
4463
  {
4422
4464
  "id": "native-portable-provider:createPortableNativeProvider",
@@ -4430,7 +4472,7 @@
4430
4472
  "summary": "Creates the SDK-owned provider that assembles one selected app and explicit services into a portable Core payload.",
4431
4473
  "availability": "Node; produces a portable payload rather than an executable host",
4432
4474
  "protocol": "arcane-native-builder/1 portable provider",
4433
- "normalization": "Preserves complete selected app and dependency releases; an explicit services selection overrides appDescriptor.native.services, otherwise the descriptor selection is used; optional appDescriptor.native.window carries positive integral logical client width/height and optional resizable into manifest.window with omitted fields preserved; reports executable:false and never substitutes for an unavailable platform host"
4475
+ "normalization": "Preserves complete selected app and dependency releases; an explicit services selection overrides appDescriptor.native.services, otherwise the descriptor selection is used; optional appDescriptor.native.window carries positive integral logical client width/height and optional resizable into manifest.window with omitted fields preserved. Generated coreListener opt-in composes one passive model-observation service from the existing host.runtime and actual services exposing localai.status/image.status/decisions.status before listener binding, with absent owners null and renderer attachment separate. Ordinary stdio and sharedHost/headless behavior remains unchanged; no model creation, getService or readiness barrier. Reports executable:false and never substitutes for an unavailable platform host"
4434
4476
  },
4435
4477
  {
4436
4478
  "id": "native-portable-provider:arcaneNativeBuilderProvider",
@@ -4640,7 +4682,7 @@
4640
4682
  "summary": "Attaches local IPC to an existing window-owned Core runtime without changing its services or lifetime.",
4641
4683
  "availability": "Node named pipes on Windows and Unix-domain sockets on Linux/macOS; Android requires its host adaptation",
4642
4684
  "protocol": "arcane/1 over local IPC with per-connection correlation, cancellation and current-state replay",
4643
- "normalization": "Returns {endpoint,closed,close}; never constructs, starts or closes the supplied runtime. Explicit close flushes queued output and disconnects its clients while accepted service work remains runtime-owned. Runtime shutdown retains the endpoint through service drain then closes the listener. Preserves parameters/results/event data, original handler requestId and internal coreRequestId; no host shutdown RPC. Bind errors reject startup; transport errors reach onError and terminal errors reject closed. Native launchContext.coreListener:{endpoint} selects the existing stdio branch; no sharedHost/headless selection or inferred endpoint."
4685
+ "normalization": "Returns {endpoint,closed,close}; never constructs, starts or closes the supplied runtime or registers services. Explicit close flushes queued output and disconnects its clients while accepted service work remains runtime-owned. Runtime shutdown retains the endpoint through service drain then closes the listener. Preserves parameters/results/event data, original handler requestId/clientRequestId and internal coreRequestId; no host shutdown RPC. Bind errors reject startup; transport errors reach onError and terminal errors reject closed. Native launchContext.coreListener:{endpoint} selects the existing stdio branch and its existing-instance observation composition; no sharedHost/headless selection or inferred endpoint."
4644
4686
  },
4645
4687
  {
4646
4688
  "id": "core-host:startSharedCoreHost",
@@ -4654,7 +4696,7 @@
4654
4696
  "summary": "Claims an app-selected local endpoint before composing one shared Core runtime.",
4655
4697
  "availability": "Node named pipes on Windows and Unix-domain sockets on Linux/macOS; Android requires its host adaptation",
4656
4698
  "protocol": "arcane/1 over local IPC with per-connection request correlation and current-state replay",
4657
- "normalization": "Returns {runtime,endpoint,closed,close}; configure(runtime,{signal}) registers independently started services after the endpoint claim. Close joins composition, accepted service work, disposal and queued frames before releasing the endpoint. Complete transport/startup/shutdown errors remain observable; an occupied endpoint never invokes the factory"
4699
+ "normalization": "Returns {runtime,endpoint,closed,close}; configure(runtime,{signal}) registers independently started services after the endpoint claim. Preserves internal handler requestId/coreRequestId and the existing matching data.requestId adapter mapping while additive clientRequestId exposes the actual client ID independently. Other payload fields remain unchanged. Close joins composition, accepted service work, disposal and queued frames before releasing the endpoint. Complete transport/startup/shutdown errors remain observable; an occupied endpoint never invokes the factory"
4658
4700
  },
4659
4701
  {
4660
4702
  "id": "core-host:connectSharedCoreHost",