arcane-os 0.83.0 → 0.83.1

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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.83.1
4
+
5
+ - Preserve complete failure-only native ONNX diagnostics in `error.onnxRun`,
6
+ including session and request identity, failure stage, graph metadata,
7
+ provider execution, run options and encoded feed/fetch tensors. Existing
8
+ rejected evaluations and retained decision error state carry the record.
9
+ - Retain the device inventory already consumed by automatic physical selection
10
+ without another hardware query. Successful inference performs no diagnostic
11
+ tensor encoding; original errors and diagnostic-construction failures remain
12
+ observable without changing model inputs, device selection or cancellation.
13
+ - These diagnostics expose evidence for a failed operation; they do not claim
14
+ to correct an application inference failure. No dependency changes.
15
+
3
16
  ## 0.83.0
4
17
 
5
18
  - Add optional `shadowPath` to native app-control inspection and actions. Follow
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.83.0` SDK contract. Development applications declare
28
+ This checkout defines the `0.83.1` 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;
@@ -420,9 +420,13 @@ unchanged, including on the final CPU attempt.
420
420
  Every loaded session includes an `execution` record in its ordinary state
421
421
  events: `preference`, `requestedTarget`, `resolvedDevice`, `resolution`,
422
422
  `reason`, `configuredTarget`, `observedTarget`, complete `supportedBackends`,
423
- `selectedProviders`, ordered `attempts` with their full errors, `fallback`, and
424
- `discoveryError`. Each attempt records provider names in `executionProviders`,
425
- its requested `configuredTarget`, `status` (`configured` or `failed`), and
423
+ `selectedProviders`, ordered `attempts` with their full errors, `fallback`,
424
+ `discoveryError`, and `deviceInventory`. The inventory retains the complete
425
+ `{platform,state,devices,issues}` already obtained for that load's automatic
426
+ physical selection. It is `null` when that load did not gather an inventory;
427
+ retaining it performs no additional discovery. Each attempt records provider
428
+ names in `executionProviders`, its requested `configuredTarget`, `status`
429
+ (`configured` or `failed`), and
426
430
  `error`. The accepted top-level `configuredTarget` is
427
431
  `{source,executionProviders}`; it retains the complete provider options,
428
432
  including native numeric `deviceId` values. Its `source` is `cpu-default`,
@@ -531,6 +535,23 @@ String input containing U+0000 reports an incompatibility at the native tensor
531
535
  boundary: the Node binding uses null-terminated strings and cannot preserve
532
536
  that input completely. The SDK does not alter the supplied string.
533
537
 
538
+ A failed run preserves the original error, stack, cause and additional fields,
539
+ and adds the developer diagnostic `error.onnxRun`. It contains `sessionId`,
540
+ `requestId`, `stage`, the original `model` path and `runtimeModulePath`, the
541
+ session's complete `execution` record, `runOptions`, and input/output names and
542
+ metadata. `stage` distinguishes `feed-conversion`, `fetch-conversion`,
543
+ `session.run`, `output-conversion`, and `result-delivery`. `feeds` and `fetches`
544
+ retain complete tensor types, dimensions and data through the existing Core tensor encoding;
545
+ omitted options remain omitted at the JSON boundary. `feedsSource` and
546
+ `fetchesSource` identify `request` records when conversion did not complete,
547
+ or `converted` values prepared for `session.run`. No partial converted map is
548
+ presented as the complete input. The actual input tensors are preserved without changes
549
+ to inference, and successful runs create no diagnostic tensor copy. If building
550
+ this diagnostic fails, its complete `diagnosticError` accompanies the fields
551
+ obtained before that failure; the original run error remains the failure.
552
+ Session metadata and feeds describe graph inputs, not internal node tensors
553
+ that the native runtime has not reported.
554
+
534
555
  `current()` returns `{sessions,closed}`. `subscribe(listener)` replays current
535
556
  state synchronously and returns an unsubscribe function. Load, readiness,
536
557
  unload and failures remain observable. `unload({id,signal?})` releases that
@@ -336,6 +336,19 @@ progress includes model preparation, tokenization and evaluation; downloaded
336
336
  members report files, not byte progress. Complete technical errors belong in
337
337
  developer diagnostics, while the application owns its user-facing status.
338
338
 
339
+ An ONNX run failure reaches the rejected evaluation and retained `error` in
340
+ `current()`, `decisions.status`, and `decisions.state`, including the complete
341
+ [`error.onnxRun` diagnostic](local-ai.md#native-onnx-sessions). This identifies
342
+ the actual session, request, failure stage, model path, provider execution
343
+ record, graph metadata and complete encoded feed tensors with their dimensions.
344
+ The activation's `execution.execution.deviceInventory` retains the complete
345
+ inventory consumed by automatic physical selection during its load, or `null`
346
+ when that selection path did not gather one.
347
+ Neither observation starts another hardware query or changes model execution.
348
+ Retain these complete records in the selected developer inspection surface;
349
+ ordinary user status remains application-owned. A retained error identifies
350
+ that failed request and does not by itself establish that a later request failed.
351
+
339
352
  Concurrent callers selecting the same family, model, revision, dtype, source,
340
353
  mapping and target share one explicit activation.
341
354
  Cancelling a load or evaluation cancels that activation and its outstanding operations, because the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.83.0",
3
+ "version": "0.83.1",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -1,11 +1,13 @@
1
1
  import {parentPort,workerData} from 'node:worker_threads';
2
2
  import {pathToFileURL} from 'node:url';
3
3
  import Is from 'strong-type';
4
+ import {encodeTensorMap,encodeTensorFetches} from '../../browser-runtime/ai/onnx-tensors.mjs';
4
5
 
5
6
  const is=new Is(false);
6
7
  const imported=await import(pathToFileURL(workerData.modulePath).href);
7
8
  const ort=imported.default??imported;
8
9
  let session;
10
+ let sessionExecution;
9
11
 
10
12
  function describeError(error){
11
13
  if(!(error instanceof Error))return {name:'Error',message:String(error),cause:error};
@@ -27,6 +29,7 @@ async function createSession(request) {
27
29
  resolution: 'automatic',
28
30
  reason: selectProviders ? 'engine-default-required' : 'caller-session-options',
29
31
  ...request.targetResolution,
32
+ deviceInventory: request.deviceInventory ?? null,
30
33
  configuredTarget: null,
31
34
  observedTarget: null,
32
35
  supportedBackends: [],
@@ -225,28 +228,35 @@ function tensorMap(records,inputs=false){
225
228
  }
226
229
 
227
230
  async function handleRequest(request){
231
+ let runStage;
232
+ let feeds;
233
+ let fetches;
228
234
  try{
229
235
  let result;
230
236
  if(request.operation==='load'){
231
- const execution = await createSession(request);
237
+ sessionExecution = await createSession(request);
232
238
  result={
233
239
  inputNames:session.inputNames,
234
240
  outputNames:session.outputNames,
235
241
  inputMetadata:session.inputMetadata,
236
242
  outputMetadata:session.outputMetadata
237
243
  };
238
- if (execution) result.execution = execution;
244
+ if (sessionExecution) result.execution = sessionExecution;
239
245
  }else if(request.operation==='run'){
240
- const feeds=tensorMap(request.feeds,true);
241
- const fetches=request.fetches===undefined||request.fetches===null||is.array(request.fetches)
246
+ runStage='feed-conversion';
247
+ feeds=tensorMap(request.feeds,true);
248
+ runStage='fetch-conversion';
249
+ fetches=request.fetches===undefined||request.fetches===null||is.array(request.fetches)
242
250
  ?request.fetches:tensorMap(request.fetches);
243
251
  // ORT treats an empty fetch map as its options overload and would
244
252
  // ignore the third argument. Both forms select every output.
245
253
  const allOutputs=fetches===undefined
246
254
  ||(fetches!==null&&!is.array(fetches)&&Object.keys(fetches).length===0);
255
+ runStage='session.run';
247
256
  const outputs=allOutputs
248
257
  ?await session.run(feeds,request.runOptions)
249
258
  :await session.run(feeds,fetches,request.runOptions);
259
+ runStage='output-conversion';
250
260
  result=Object.fromEntries(Object.entries(outputs).map(function tensorRecord([name,tensor]){
251
261
  return [name,{type:tensor.type,data:tensor.data,dims:tensor.dims}];
252
262
  }));
@@ -258,9 +268,33 @@ async function handleRequest(request){
258
268
  }
259
269
  // Structured cloning retains typed tensors and BigInt without taking
260
270
  // ownership of the caller's buffers or converting their contents.
271
+ if(request.operation==='run')runStage='result-delivery';
261
272
  parentPort.postMessage({requestId:request.requestId,operation:request.operation,result});
262
273
  }catch(error){
263
- parentPort.postMessage({requestId:request.requestId,operation:request.operation,error:describeError(error)});
274
+ const record=describeError(error);
275
+ if(request.operation==='run'){
276
+ const diagnostic={
277
+ sessionId:workerData.sessionId,requestId:request.requestId,stage:runStage,
278
+ model:workerData.model,runtimeModulePath:workerData.modulePath,
279
+ execution:sessionExecution??null,runOptions:request.runOptions,
280
+ feedsSource:feeds===undefined?'request':'converted',
281
+ fetchesSource:runStage==='feed-conversion'||runStage==='fetch-conversion'?'request':'converted'
282
+ };
283
+ record.onnxRun=diagnostic;
284
+ try{
285
+ diagnostic.inputNames=session?.inputNames??null;
286
+ diagnostic.inputMetadata=session?.inputMetadata??null;
287
+ diagnostic.outputNames=session?.outputNames??null;
288
+ diagnostic.outputMetadata=session?.outputMetadata??null;
289
+ // Copy complete tensors only after failure. The same codec used
290
+ // by Core tensor transport preserves BigInt and typed values.
291
+ diagnostic.feeds=encodeTensorMap(feeds===undefined?request.feeds:feeds);
292
+ diagnostic.fetches=encodeTensorFetches(diagnostic.fetchesSource==='request'?request.fetches:fetches);
293
+ }catch(diagnosticError){
294
+ diagnostic.diagnosticError=describeError(diagnosticError);
295
+ }
296
+ }
297
+ parentPort.postMessage({requestId:request.requestId,operation:request.operation,error:record});
264
298
  }finally{
265
299
  if(request.operation==='unload')parentPort.close();
266
300
  }
@@ -216,7 +216,7 @@ export function createONNXRuntime({modulePath,onEvent,signal,executionDevices:su
216
216
  operationSignal.throwIfAborted();
217
217
  if (entry.stopping || entry.exited || entry.active !== operation) return;
218
218
  operation.payload = {...operation.payload, targetResolution, deviceInventory};
219
- entry.state.execution = {...entry.state.execution, ...targetResolution};
219
+ entry.state.execution = {...entry.state.execution, ...targetResolution, deviceInventory: deviceInventory ?? null};
220
220
  publish(entry);
221
221
  postOperation(entry, operation);
222
222
  }
@@ -348,7 +348,7 @@ export function createONNXRuntime({modulePath,onEvent,signal,executionDevices:su
348
348
  state:{id,model,state:'loading',loaded:false,error:null,inputNames:[],outputNames:[],inputMetadata:[],outputMetadata:[],
349
349
  execution: {requestedTarget: executionTarget ?? null, resolvedDevice: null,
350
350
  resolution: executionTarget === undefined || executionTarget === null ? 'automatic' : null,
351
- reason: 'session-loading', configuredTarget: null, observedTarget: null}},
351
+ reason: 'session-loading', configuredTarget: null, observedTarget: null, deviceInventory: null}},
352
352
  controller: new AbortController(), preparation: null,
353
353
  worker:null,active:null,queue:[],stopping:false,exited:false,stopTask:null,terminalError:null,releaseError:null,terminationRequested:false,outputTasks:[]
354
354
  };
@@ -356,7 +356,7 @@ export function createONNXRuntime({modulePath,onEvent,signal,executionDevices:su
356
356
  sessions.set(id,entry);
357
357
  try{
358
358
  entry.worker=new Worker(new URL('./onnx-worker.mjs',import.meta.url),{
359
- workerData:{modulePath},stdout:true,stderr:true
359
+ workerData:{modulePath,sessionId:id,model},stdout:true,stderr:true
360
360
  });
361
361
  entry.outputTasks.push(
362
362
  captureOutput(entry,entry.worker.stdout,'local-ai.onnx.stdout'),