arcane-os 0.55.0 → 0.56.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,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.56.0
4
+
5
+ - Add explicit `package.nativeResources` selection for native-only application
6
+ files and directories. Browser selection remains unchanged; non-browser
7
+ packaging keeps additional resources outside browser transformations and
8
+ stages them in a separate target output. Applications select their complete
9
+ required source closure, and target adapters receive the effective target
10
+ and browser-PWA selection.
11
+ - Add `getInstalledCoreClient()` to read the exact live SDK-installed client
12
+ without connecting or changing globals. Terminal `failTransport(error)` and
13
+ the owned native failure callback preserve complete errors, settle pending
14
+ requests, and release the failed installation's callbacks and subscriptions.
15
+ - Add opt-in `replayRuntimeState` for Core clients and classic injection. The
16
+ runtime replays its actual readiness and current service state for newly
17
+ connected documents without restarting services or delaying page startup.
18
+
19
+ - Preserve complete UTF-8 process output when a character spans stdout or stderr
20
+ chunks. Independent readable-stream decoders feed the same text to captured
21
+ results and existing event delivery; ordering, backpressure, cancellation,
22
+ graceful drain, nonzero error details and line-event behavior remain unchanged.
23
+ - Include committed Windows and Linux Core process-transport source and the
24
+ generic Windows SEA launcher source as native-host groundwork. These sources
25
+ do not activate a platform host or deliver a public native executable; app
26
+ host composition and the remaining native integration stay independently owned.
27
+ - Clarify development `latest` tracking, production-designated pins, matching
28
+ requester notices, and required documentation/site release follow-through.
29
+ - No new dependency, application service, automatic runtime installation,
30
+ application default change or saved-data migration is introduced.
31
+
3
32
  ## 0.55.0
4
33
 
5
34
  - Add public Core client, protocol contracts and classic-script source generation
package/README.md CHANGED
@@ -20,9 +20,11 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
20
20
  share the theme, packaging, event, cancellation,
21
21
  and browser run contracts while retaining their selected application layout.
22
22
 
23
- This checkout defines the `0.55.0` SDK contract. Applications pin one exact npm
24
- version and lockfile; registry state is deliberately not baked into application
25
- artifacts.
23
+ This checkout defines the `0.56.0` SDK contract. Development applications declare
24
+ `arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
25
+ designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
26
+ to refresh and compare the installed SDK and lockfile with the published channel;
27
+ registry state is deliberately not baked into application artifacts.
26
28
 
27
29
  Standalone browser apps use their repository root and installed npm package directly, without a
28
30
  generated workspace `arcane/` directory or `arcane.lock.json`. Select the
@@ -2,6 +2,7 @@ import {arcaneEvents} from '../event-manager.mjs';
2
2
  import {CORE_PROTOCOL,CORE_READY_EVENTS,CoreError,serializeCoreError} from './contracts.mjs';
3
3
 
4
4
  const CORE_CLIENT_KEY=Symbol.for('arcane-os.core.client');
5
+ const CORE_CLIENT_INSTALLATION=Symbol.for('arcane-os.core.client.installation');
5
6
  const CORE_EVENT='core.rpc.event';
6
7
  const LONG_OPERATION_TIMEOUT=50*60*1000;
7
8
 
@@ -12,6 +13,7 @@ export function createCoreClient({
12
13
  eventOwner=arcaneEvents,
13
14
  eventOwnerReady,
14
15
  autoConnect=true,
16
+ replayRuntimeState=false,
15
17
  onError=error=>global.console?.error('Arcane Core client failed.',error)
16
18
  }={}){
17
19
  const pending=new Map();
@@ -25,8 +27,14 @@ export function createCoreClient({
25
27
  let transport=null;
26
28
  let closed=false;
27
29
  let eventOwnerFailure=null;
30
+ let transportFailure=null;
31
+ let transportFailureReported=false;
28
32
 
29
33
  function report(error){
34
+ if(error===transportFailure){
35
+ if(transportFailureReported)return;
36
+ transportFailureReported=true;
37
+ }
30
38
  try{onError(error instanceof CoreError?error:new CoreError(error));}
31
39
  catch(reportError){global.console?.error('Arcane Core error listener failed.',reportError,error);}
32
40
  }
@@ -260,7 +268,7 @@ export function createCoreClient({
260
268
  throw new CoreError({code:'ARCANE_TRANSPORT_UNAVAILABLE',message:'This interface is not connected to an Arcane native host.'});
261
269
  }
262
270
  function connect(){
263
- if(closed)throw new CoreError({code:'ARCANE_CLIENT_CLOSED',message:'The Core client is closed.'});
271
+ if(closed)throw transportFailure??new CoreError({code:'ARCANE_CLIENT_CLOSED',message:'The Core client is closed.'});
264
272
  if(transport)return transport;
265
273
  const cleanupStart=cleanups.length;
266
274
  try{
@@ -271,7 +279,11 @@ export function createCoreClient({
271
279
  transport=selected;
272
280
  if(typeof transport.subscribe==='function'){
273
281
  const unsubscribe=transport.subscribe(receive);
274
- if(typeof unsubscribe==='function')cleanups.push(unsubscribe);
282
+ if(typeof unsubscribe==='function'){
283
+ if(closed){
284
+ try{unsubscribe();}catch(error){report(error);}
285
+ }else cleanups.push(unsubscribe);
286
+ }
275
287
  }
276
288
  }catch(error){
277
289
  transport=null;
@@ -280,7 +292,11 @@ export function createCoreClient({
280
292
  }
281
293
  throw error;
282
294
  }
295
+ if(closed)throw transportFailure??new CoreError({code:'ARCANE_CLIENT_CLOSED',message:'The Core client is closed.'});
283
296
  complete('transport.ready',{protocol:CORE_PROTOCOL,transport:transport.name});
297
+ if(replayRuntimeState&&!closed)sendControl({protocol:CORE_PROTOCOL,type:'control',control:'runtime.replay'});
298
+ // A native callback may close the client synchronously during replay.
299
+ if(closed)throw transportFailure??new CoreError({code:'ARCANE_CLIENT_CLOSED',message:'The Core client is closed.'});
284
300
  return transport;
285
301
  }
286
302
  function runtimeSnapshot(){
@@ -312,6 +328,7 @@ export function createCoreClient({
312
328
  return true;
313
329
  }
314
330
  function invoke(method,parameters={},options={}){
331
+ if(transportFailure)return Promise.reject(transportFailure);
315
332
  if(eventOwnerFailure)return Promise.reject(eventOwnerFailure);
316
333
  let selected;
317
334
  try{selected=connect();}catch(error){return Promise.reject(error);}
@@ -343,23 +360,37 @@ export function createCoreClient({
343
360
  }catch(error){sendFailed(error);}
344
361
  return promise;
345
362
  }
346
- function close(){
347
- if(closed)return false;
348
- closed=true;
349
- cancelAll();
363
+ function disposeTransport(error){
350
364
  for(const record of acknowledgements.values()){
351
365
  clearTimeout(record.timer);
352
- record.reject(new CoreError({code:'ARCANE_CLIENT_CLOSED',message:'The Core client is closed.'}));
366
+ record.reject(error);
353
367
  }
354
368
  acknowledgements.clear();
355
369
  for(const cleanup of cleanups.splice(0)){
356
370
  try{cleanup();}catch(error){report(error);}
357
371
  }
358
- source?.dispose();
372
+ try{source?.dispose();}catch(disposeError){report(disposeError);}
373
+ source=null;
374
+ transport=null;
359
375
  // Registration closures and already-delivered payloads belong to this
360
376
  // client lifetime and are released when the owner explicitly closes it.
361
377
  eventActions.length=0;
362
378
  completedEvents.clear();
379
+ }
380
+ function failTransport(error){
381
+ if(closed)return false;
382
+ transportFailure=error instanceof CoreError?error:new CoreError(error);
383
+ closed=true;
384
+ for(const id of pending.keys())settle(id,transportFailure);
385
+ disposeTransport(transportFailure);
386
+ report(transportFailure);
387
+ return true;
388
+ }
389
+ function close(){
390
+ if(closed)return false;
391
+ closed=true;
392
+ cancelAll();
393
+ disposeTransport(new CoreError({code:'ARCANE_CLIENT_CLOSED',message:'The Core client is closed.'}));
363
394
  return true;
364
395
  }
365
396
  if(typeof global.addEventListener==='function'){
@@ -369,7 +400,7 @@ export function createCoreClient({
369
400
  if(autoConnect){
370
401
  try{connect();}catch(error){if(error.code!=='ARCANE_TRANSPORT_UNAVAILABLE')report(error);}
371
402
  }
372
- return {protocol:CORE_PROTOCOL,Error:CoreError,invoke,receive,connect,close,cancelAll,events,eventsReady,runtime:{current:runtimeSnapshot},uuid};
403
+ return {protocol:CORE_PROTOCOL,Error:CoreError,invoke,receive,connect,close,failTransport,cancelAll,events,eventsReady,runtime:{current:runtimeSnapshot},uuid};
373
404
  }
374
405
 
375
406
  /** Existing namespace call shapes; method implementations remain host-owned. */
@@ -461,28 +492,58 @@ export function createCoreFacade(client){
461
492
  };
462
493
  }
463
494
 
495
+ /** Reads an installed SDK client without creating a transport or changing globals. */
496
+ export function getInstalledCoreClient(global=globalThis){
497
+ const client=global?.[CORE_CLIENT_KEY];
498
+ const installation=client?.[CORE_CLIENT_INSTALLATION];
499
+ return installation?.global===global&&installation.active?client:null;
500
+ }
501
+
464
502
  /** Installs the same synchronous facade used by classic native-host scripts. */
465
503
  export function installCoreClient(global=globalThis,options={}){
466
504
  if(global[CORE_CLIENT_KEY])return global[CORE_CLIENT_KEY];
467
505
  const client=createCoreClient({...options,global,autoConnect:false});
468
506
  const facade=createCoreFacade(client);
507
+ const installation={global,active:true};
508
+ client[CORE_CLIENT_INSTALLATION]=installation;
509
+ const previousFacade=global.Arcane;
469
510
  global.Arcane=facade;
470
511
  global[CORE_CLIENT_KEY]=client;
471
512
  const previous=global.__arcaneReceive;
472
513
  global.__arcaneReceive=client.receive;
514
+ const previousFailure=global.__arcaneTransportFailed;
515
+ function nativeTransportFailed(error){return client.failTransport(error);}
516
+ global.__arcaneTransportFailed=nativeTransportFailed;
473
517
  const close=client.close;
474
- client.close=function closeInstalledClient(){
475
- const changed=close();
518
+ const failTransport=client.failTransport;
519
+ let installedTransportFailure=null;
520
+ function releaseInstalledClient(){
521
+ installation.active=false;
476
522
  if(global[CORE_CLIENT_KEY]===client)delete global[CORE_CLIENT_KEY];
477
- if(global.Arcane===facade)delete global.Arcane;
523
+ if(global.Arcane===facade){
524
+ if(previousFacade===undefined)delete global.Arcane;
525
+ else global.Arcane=previousFacade;
526
+ }
478
527
  if(global.__arcaneReceive===client.receive){
479
528
  if(previous===undefined)delete global.__arcaneReceive;
480
529
  else global.__arcaneReceive=previous;
481
530
  }
482
- return changed;
531
+ if(global.__arcaneTransportFailed===nativeTransportFailed){
532
+ if(previousFailure===undefined)delete global.__arcaneTransportFailed;
533
+ else global.__arcaneTransportFailed=previousFailure;
534
+ }
535
+ }
536
+ client.close=function closeInstalledClient(){
537
+ installation.active=false;
538
+ try{return close();}finally{releaseInstalledClient();}
539
+ };
540
+ client.failTransport=function failInstalledTransport(error){
541
+ if(installation.active)installedTransportFailure=error instanceof CoreError?error:new CoreError(error);
542
+ releaseInstalledClient();
543
+ return failTransport(installedTransportFailure??error);
483
544
  };
484
545
  try{client.connect();}catch(error){
485
- if(error.code!=='ARCANE_TRANSPORT_UNAVAILABLE'){
546
+ if(error!==installedTransportFailure&&error.code!=='ARCANE_TRANSPORT_UNAVAILABLE'){
486
547
  if(options.onError)options.onError(error);
487
548
  else global.console?.error('Arcane Core transport initialization failed.',error);
488
549
  }
@@ -7,7 +7,8 @@ export const CORE_FRAME_CONTRACTS={
7
7
  response:{type:'response',fields:['protocol','type','id','ok'],success:['result'],failure:['error'],optional:['time']},
8
8
  event:{type:'event',fields:['protocol','type','event','data'],optional:['time']},
9
9
  cancel:{type:'control',control:'request.cancel',fields:['protocol','type','control','requestId']},
10
- cancelAll:{type:'control',control:'requests.cancelAll',fields:['protocol','type','control']}
10
+ cancelAll:{type:'control',control:'requests.cancelAll',fields:['protocol','type','control']},
11
+ replay:{type:'control',control:'runtime.replay',fields:['protocol','type','control']}
11
12
  };
12
13
 
13
14
  /** These names describe existing methods; installed services determine availability. */
@@ -0,0 +1,82 @@
1
+ # Consumer updates
2
+
3
+ Development applications track the published npm `latest` channel. Declare
4
+ `"arcane-os": "latest"` in the application's existing dependency section.
5
+ Preserve an existing package alias where the application needs it; Arcane OS,
6
+ for example, uses `"arcane-sdk": "npm:arcane-os@latest"` to avoid a name collision.
7
+ Pin an application's SDK version only when Roshi explicitly designates it as a
8
+ production build. An exact version recorded in `package-lock.json` records what
9
+ was installed; it does not turn a development application into a production pin.
10
+
11
+ ## Development refresh
12
+
13
+ Each application's existing owner performs its refresh through the public npm
14
+ package workflow in its canonical `main` checkout. Coordinate the dependency
15
+ and Git mutation window before cleanup, preserve active processes and other
16
+ owners' work, and establish applicable dependency permissions before removing
17
+ the installed tree. Respect explicit project stops, including Lifeline.
18
+
19
+ 1. Keep the SDK declaration at `latest` and preserve `package-lock.json`.
20
+ 2. Resolve the exact application path and remove only that application's
21
+ disposable `node_modules`, before pulling. Do not sweep sibling projects or
22
+ shared caches.
23
+ 3. Run `git pull` and observe the result. Preserve existing changes and stop the
24
+ affected operation if the pull fails or leaves conflicts.
25
+ 4. Run `npm i` through the application's supported install workflow, retaining
26
+ its applicable lifecycle-script options.
27
+ 5. Explicitly run `npm update arcane-os` to refresh only the SDK. For an existing
28
+ alias, target its dependency key, such as `npm update arcane-sdk`.
29
+ 6. Before materializing managed files or building, obtain npm's published
30
+ `latest` version with a fresh registry lookup, such as
31
+ `npm view arcane-os@latest version --prefer-online`. Read the actual installed
32
+ package version and the lockfile's resolved SDK version. Both must match that
33
+ published version. If the tag advances during the operation, refresh the SDK
34
+ again and report the actual selected version.
35
+ 7. Update managed SDK projections only through the application's supported
36
+ public package workflow. Commit the intended declaration, retained updated
37
+ lockfile, managed files, and relevant instructions through its normal owner.
38
+
39
+ A `latest` declaration and `npm i` alone are insufficient: an existing lockfile
40
+ can preserve an older resolution. Keep and update the lockfile instead of
41
+ deleting it and re-resolving unrelated dependencies. Surface installation and
42
+ compatibility failures; do not claim current adoption from a manifest label.
43
+
44
+ This dependency-result comparison is part of the requested refresh. It does
45
+ not authorize application tests, unrelated checks or dependency updates,
46
+ recurring polling, broad cleanup, or production administration. A later release
47
+ can advance `latest`; record the exact version selected by each refresh.
48
+
49
+ ## Production builds
50
+
51
+ On Roshi's explicit production designation, record the selected exact SDK
52
+ version consistently in the dependency declaration, lockfile, managed
53
+ projections, and application instructions. Use the supported locked install
54
+ workflow, normally `npm ci`, to reproduce that selection. A production pin
55
+ records reproducibility, not a permanent ceiling on otherwise authorized
56
+ needed updates. Preserve an explicit update deferral or actual compatibility
57
+ constraint.
58
+
59
+ ## Feature requests and release notices
60
+
61
+ Inspect the published SDK's public API and documentation before implementing a
62
+ request. If it already supports the requested behavior, tell the requesting
63
+ application owner which API and usage provide it and link directly to the
64
+ relevant documentation. Do not present unpublished source as available support.
65
+
66
+ After a release's npm version and channel are confirmed, notify only existing
67
+ application owners whose requests it addresses. Notify every requester covered
68
+ by matching, similar, or bundled requests when one shared feature serves them.
69
+ Do not send routine release broadcasts to unrelated consumers.
70
+
71
+ Each notice includes the exact published version and channel, requested
72
+ functionality delivered, its relevance, release notes or a changelog link,
73
+ direct API and usage documentation references, and required compatibility or
74
+ adoption steps. Keep publication evidence distinct from application behavior.
75
+ Deduplicate by version and recipient and retain send outcomes in the release
76
+ handoff. Report unreachable owners and resume missing deliveries.
77
+
78
+ After every release and applicable requester notices, update the documentation
79
+ and existing SDK website for all affected public contracts, including releases
80
+ with no requesters. Record those delivery outcomes separately. The SDK owner
81
+ retains this follow-through; application upgrades and tests remain with their
82
+ owners and never gate SDK publication. See [npm publication](publishing.md).
@@ -11,8 +11,12 @@ the npm `dev` dist-tag, while bare numeric stable versions select
11
11
 
12
12
  The package version and `publishConfig.tag` must agree exactly: `-dev` uses
13
13
  `dev`, and a bare numeric stable version uses `latest`. npm is the canonical
14
- SDK distribution: application repositories add an exact `arcane-os` project
15
- dependency and invoke its local CLI with `npm exec -- arcane`. A separate
14
+ SDK distribution: development applications declare `arcane-os` as `latest` and
15
+ invoke its local CLI with `npm exec -- arcane`. An exact application SDK pin
16
+ begins only when Roshi designates that application as a production build.
17
+ Keep the lockfile and explicitly refresh and compare its resolution and the
18
+ installed SDK with npm's published version before materializing or building;
19
+ see [Consumer updates](consumer-updates.md). A separate
16
20
  global installer, standalone SDK executable, NuGet package, Homebrew formula,
17
21
  or OS package is not part of this release surface.
18
22
 
@@ -30,8 +34,8 @@ agreement, and required license notices. One unprivileged producer packs one
30
34
  artifact with a recorded run id, artifact id, version, and source commit. It
31
35
  does not impose byte counts, hashes, digests, provenance receipts, or unrelated
32
36
  test suites as ordinary development gates. Broader integration, regression,
33
- platform, browser, presentation, and documentation work remains separately
34
- user selected.
37
+ and platform work remains separately user selected. Documentation and website
38
+ updates are required post-publication follow-through as described below.
35
39
 
36
40
  `publish-dev.yml` can run only when manually dispatched from `main` in
37
41
  `TheWizardNexus/arcane-os-sdk`. Dispatch supplies the exact successful Check
@@ -59,14 +63,15 @@ The unscoped package installs both `arcane` and `arcane-os`. The short command
59
63
  is the documented default; `arcane-os` is the collision-safe fallback. npm
60
64
  package names are unique, but executable names are not globally reserved.
61
65
 
62
- When `npm view arcane-os@dev version` reports the package unavailable, run
63
- `npm run pack:local` in this SDK checkout for local development, scaffold with
64
- `node ./bin/arcane.mjs new ...`, and install the resulting `.tgz` into the app
66
+ External applications consume published npm releases. An unavailable release
67
+ does not authorize them to use an unpublished SDK checkout or tarball. Only
68
+ tasks explicitly collaborating on an SDK update inside this project may use
69
+ the local `npm run pack:local` workflow and its resulting `.tgz`. That local
70
+ workflow can scaffold with `node ./bin/arcane.mjs new ...` and install the tarball
65
71
  with `npm install --save-dev --save-exact <path>`. Keep the tarball at the path
66
- recorded by `package-lock.json`; subsequent `npm ci` uses that declared package.
67
- Arcane reads the installed package's name and version against the root
68
- dependency declaration. A local directory `file:` install is intentionally unsupported because
69
- it may be linked.
72
+ recorded by `package-lock.json` for subsequent `npm ci`. Arcane reads the installed
73
+ package's name and version against the root dependency declaration. A local
74
+ directory `file:` install is unsupported because it may be linked.
70
75
 
71
76
  The npm package already has its trusted-publishing relationship. Each later
72
77
  release therefore follows the same direct selected-artifact path: push the
@@ -77,9 +82,11 @@ dist-tag after publication. Never rebuild or repack the artifact under
77
82
  publication authority, and never substitute a different source revision.
78
83
 
79
84
  Generated app CI uses `npm ci --ignore-scripts`, so its lock must exist and its
80
- dependency source must be reachable by the runner. A sibling local tarball is a
81
- workstation workflow, not a portable GitHub dependency source; switch to the
82
- exact registry release (or deliberately vendor the tarball) before remote CI.
85
+ dependency source must be reachable by the runner. External application CI uses
86
+ the published registry package recorded in the committed lockfile. A locked
87
+ install reproduces that selected version; it does not establish that the SDK is
88
+ currently npm's `latest`. Complete the development refresh before preparing and
89
+ committing managed application files and the updated lockfile for CI.
83
90
 
84
91
  ## Reusable application release workflow
85
92
 
@@ -111,8 +118,23 @@ GitHub release, and no release creates a Git branch for an npm dist-tag.
111
118
 
112
119
  ## Documentation publication
113
120
 
114
- Documentation publication occurs only when the user explicitly selects it. The
115
- Pages job checks out that selected `main` revision without persistent
121
+ After every successful npm release and its applicable requester notices, the
122
+ SDK owner updates documentation and the existing SDK website for all added,
123
+ removed, or modified features, APIs, members, methods, and affected public
124
+ contracts. Include the relevant signatures, inputs, outputs, defaults,
125
+ lifecycle, errors, platform availability, and usage examples. This standing
126
+ duty also applies when a release has no requesting applications. Record the
127
+ actual documentation and website delivery outcomes in the release handoff and
128
+ report any unfinished work. See [Consumer updates](consumer-updates.md) for
129
+ request intake and notice requirements.
130
+
131
+ Documentation and website content outside the selected npm payload does not
132
+ block npm publication. Prepare and deliver those updates through the existing
133
+ documentation/site workflow after publication; do not wait for consumer
134
+ adoption or application tests. This duty does not authorize ChatGPT Sites or
135
+ production-server administration.
136
+
137
+ The Pages job checks out the selected `main` revision without persistent
116
138
  credentials and uploads only the static `site/` tree. It does not automatically
117
139
  run the SDK test suite, checks, generators, or repository build code.
118
140
 
@@ -128,5 +150,6 @@ One producer creates the tarball. Publication names that producer's exact Check
128
150
  run, artifact, and version and does not rebuild from a later checkout. The
129
151
  release workflow checks only the publication contract and required legal
130
152
  inventory for that selected output.
131
- Platform matrices, full product regressions, Pages, and broader presentation
132
- work remain separate user-selected operations.
153
+ Platform matrices and full product regressions remain separate user-selected
154
+ operations. Documentation and the existing website are required post-release
155
+ follow-through, with their outcomes recorded separately from npm publication.
@@ -17,9 +17,14 @@ high-level page links to the relevant deep section instead of repeating it.
17
17
  Install the SDK in your application:
18
18
 
19
19
  ```sh
20
- npm install --save-exact arcane-os@latest
20
+ npm pkg set dependencies.arcane-os=latest
21
+ npm install
21
22
  ```
22
23
 
24
+ The development declaration tracks the current `latest` channel. The lockfile
25
+ and installed package record the exact version actually selected for the
26
+ workspace.
27
+
23
28
  For your first AI call, follow the [TWiN Cloud quick start](ai/twin-cloud.md).
24
29
  For local models, native browser voices or cloud speech, follow the [browser speech guide](ai/browser-speech.md)
25
30
  and the complete [browser AI demo](https://github.com/TheWizardNexus/arcane-os-sdk/tree/main/examples/wasm-ai-demo).
@@ -38,14 +43,19 @@ alone does not make bare module names resolve in a browser.
38
43
  | Use the `arcane` command | [CLI reference](cli.md) |
39
44
  | Install an app and cache selected resources offline | [Progressive web applications](pwa.md) |
40
45
  | Generate named browser imports or inspect the selected physical runtime | [`arcane import-map`](cli.md#arcane-import-map) and [browser runtime delivery](protocols.md#browser-runtime-delivery) |
46
+ | Connect an application through the shared WebSocket transport | [Shared WebSocket clients](websocket-client.md) |
41
47
  | Choose browser, native, cloud, or cross-host behavior | [Availability and normalization](availability-and-normalization.md) |
42
48
  | Import a shipped renderer module | [Runtime module catalog](runtime-modules.md) |
43
49
  | Use a shared entity | [Runtime entity modules](runtime-entities.md) and [exact export contracts](core/arcane-entities.md) |
44
50
  | Load a reusable HTML component | [Runtime component catalog](runtime-components.md) |
45
51
  | Submit complete speech parts immediately and play them in order | [`SpeechPlayback`](runtime-modules.md#speechplaybackjs) and the [basic browser example](ai/browser-speech.md#play-a-complete-array-with-speechplayback) |
46
52
  | Call `globalThis.Arcane` | [Arcane Core API](core/arcane-api.md) |
53
+ | Connect a browser to one Core transport | [Core browser client](core-client.md) |
54
+ | Compose native Core dispatch, service lifecycle, and stdio hosting | [Native Core runtime](core-runtime.md) |
55
+ | Build an installed-SDK portable Core payload | [Portable Core packaging](core-native-packaging.md) |
47
56
  | Subscribe to native events | [Arcane event reference](core/arcane-events.md) |
48
57
  | Use provider-neutral AI lifecycle, chat, speech, persistence, or document context | [Normalized AI](#normalized-ai) |
58
+ | Choose a normalized typed browser decision surface | [Browser typed decisions](ai/browser-decisions.md) |
49
59
  | Run a caller-selected local LLM in the browser | [Browser-WASM local AI](ai/browser-wasm.md) |
50
60
  | Run caller-selected Whisper/Kokoro, native browser recognition/voices or cloud TTS | [Browser speech providers](ai/browser-speech.md) |
51
61
  | Send TWiN chat, System One state/questions or image requests | [TWiN Cloud guide](ai/twin-cloud.md) |
@@ -58,9 +58,12 @@ false when a probe throws; SDK branching continues through its owning error path
58
58
  policy, package-owned model policy, platform support, and dependency readiness
59
59
  are independent checks.
60
60
 
61
- The current native host/target matrix covers Microsoft NT, Linux, and Android
62
- where listed. It exposes no macOS target or Core host contract in this SDK
63
- version; WebKitGTK availability must not be generalized to macOS.
61
+ The current executable host/target matrix covers Microsoft NT, Linux, and
62
+ Android where listed. This SDK exposes no macOS executable target, native
63
+ bridge, artifact, or run contract; WebKitGTK availability must not be
64
+ generalized to macOS. The reusable Node Core runtime, framed stdio transport,
65
+ host composition, and portable payload can be used by a separately composed
66
+ macOS host, but they do not supply that native platform host or bridge.
64
67
 
65
68
  ## Capability-first matrix
66
69
 
@@ -68,7 +71,9 @@ version; WebKitGTK availability must not be generalized to macOS.
68
71
  | --- | --- | --- | --- |
69
72
  | Scaffold, inspect, test, package, bundle, build, verify, or run an app | `arcane` CLI or `arcane-os` package functions | **Node**; native targets invoke one explicit provider | CLI events and SDK errors/results are normalized by versioned SDK contracts. Tests and checks run only when explicitly selected; verification is separate and selected-output-specific. |
70
73
  | Publish application events or review a complete event history | `arcane-os/event-manager` | **Node** and **Browser**; optional DOM capture needs a browser DOM or compatible host | Live listeners receive original arguments. Ordinary `secure:false` recording preserves complete URLs, public details, and captured stack text in deeply frozen `arcane-event-stack/1` snapshots while credential-named fields remain redacted. The stack format is local diagnostic data, not a host transport. |
74
+ | Share one native WebSocket by exact URI and ordered protocols | `arcane-os/websocket-client` | **Node** and **Browser**, using the host's native `WebSocket` | The selected upstream `ws-share` instance owns the connection pool and global observation. Native socket messages and events remain unchanged; there is no reconnect, heartbeat, application envelope, storage, or fallback. |
71
75
  | Build browser UI and app-local behavior | `/arcane/modules/*.js`, shared entities, and components | **Browser**; many modules also run inside every native renderer | Pure modules own their result contracts. Modules that call `Arcane` inherit the bridge boundary described below. |
76
+ | Score caller-supplied typed options with Laya or Julia | `arcane-os/ai/browser-decisions` | **Browser** only; caller-selected upstream runtime/model and backend | Typed decisions retain complete rows and return typed values, scores, probabilities, and diagnostics in caller order. This surface is separate from chat and speech and does not silently change model, precision, backend, or family. |
72
77
  | Select and observe independent LLM/STT/TTS roles | `/arcane/modules/AIProviderRuntime.js` and `AIRuntimeState.js` | **Cross-host** controller/state; registered providers retain their own host requirements | Required/projected provider members, route/configuration records, and status fields; per-role lifecycle, cancellation, stream cleanup, sticky state, and startup barriers are normalized. `localOnly` creates no fallback. |
73
78
  | Run a caller-selected local LLM entirely in a browser renderer | `arcane-os/ai/browser-wasm` through `createArcaneAI()` | **Browser** only; secure context, WebAssembly, and OPFS/DBOPFS; full WebGPU offload by default or explicit CPU with `gpuLayers:0` | The public AI API module normalizes multi-model lifecycle, status, complete all-choice streaming, cancellation, exact ordered structural tool-call visibility, and session persistence. Model sources are canonical ordered file descriptors; licenses and model choice remain application policy. |
74
79
  | Run caller-selected Whisper or Kokoro in a browser renderer | `arcane-os/ai/browser-speech` registered with `AIProviderRuntime` | **Browser** only; DBOPFS, Web Locks, Workers, Fetch/object URLs, and a caller-supplied self-contained runtime/model closure are required | Independent provider/2 lifecycle and status, with `auto`, `webnn-npu`, `webgpu`, or `wasm` execution. Whisper owns one Worker/session; Kokoro defaults to four and accepts one through four. Complete model/runtime selection, offline behavior, cancellation, Worker teardown, and request/result shapes are normalized. No runtime/model content or cloud fallback is supplied. |
@@ -81,6 +86,9 @@ version; WebKitGTK availability must not be generalized to macOS.
81
86
  | Print rendered editor content or file previews | `/arcane/modules/PrintView.js`, editor `print()`, and file/history `printPreview()` | **Browser / native WebView** with DOM and browser printing | Complete current rendered content, fonts, and resolved media are captured for print. Explicit requests await view preparation; native Print captures synchronously. Shared `print.css` supplies a light paper palette and one-inch margins; snapshots stay owned through `afterprint`. |
82
87
  | Search an app-owned document corpus for explicit chat context | `/arcane/modules/DBOPFSDocumentLibrary.js` | **Browser** or compatible injected DBOPFS adapter | Generation/manifest completion, complete lexical search, partial read failures, and untrusted context labels are normalized. Construction does not search; an explicitly wired context builder performs retrieval for each prepared chat send. |
83
88
  | Read host identity, capabilities, storage, preferences, appearance, or platform state | `globalThis.Arcane` | **Cross-host** where the method is implemented and admitted | Promise behavior and `Arcane.Error` are normalized. Result fields are normalized unless the method explicitly documents a platform-dependent snapshot. |
89
+ | Connect browser code to an existing Core transport | `arcane-os/core/client` and `arcane-os/core/contracts` | **Browser / native WebView** with an admitted transport; explicit development HTTP remains opt-in | The reusable client owns `arcane/1` correlation, readiness, cancellation, events, facades, and complete native error fidelity. It supplies no service, policy, or host process. |
90
+ | Compose native Core services and framed stdio hosting | `arcane-os/core/runtime`, `arcane-os/core/stdio`, and `arcane-os/core/host` | **Node** on Windows, Linux, or macOS; the composing native host supplies its process and bridge | The SDK owns reusable dispatch, per-service lifecycle, request/service work ownership, cooperative cancellation, drain, and stdio framing. Product identity, policy, privileges, models, storage, and services remain host-owned. |
91
+ | Assemble a portable Core payload from the installed SDK | `arcane-os/native/portable-provider` | **Node**; portable platform selection is currently Windows or Linux, while separately composed hosts may use the payload | The provider copies the selected app, dependencies, installed SDK runtime, Core client/runtime, and explicit service composition into a non-executable directory. It uses no `arcaneRoot` by default and does not prove host behavior or supply a platform bridge. |
84
92
  | Use local AI without coupling app code to Ollama HTTP | `Arcane.localAI`, `Arcane.ai`, or `/arcane/modules/Ollama.js` | Primarily **Native**; Android exposes a narrower admitted inference projection | Admission, errors, and managed-operation events are normalized. Direct Ollama response envelopes remain **Provider-native**. |
85
93
  | Use TWiN Cloud from the renderer profile | `/arcane/modules/AI.js` | **Cloud** from an allowed browser/native renderer | High-level chat behavior is normalized by the module. The TWiN access key authenticates remote LLM chat; raw provider diagnostics remain provider-specific. No automatic cloud fallback is inferred from local failure. |
86
94
  | Send one TWiN Cloud request with an explicit key and model | `fetchRequest` from `arcane-os/ai/twin-cloud` | **Node** and **Browser**, using standard Fetch and a remote HTTPS provider | Keeps complete messages and returns the full parsed provider JSON. Rejected Fetch and HTTP 529 share three retries after 3000 ms; HTTP 429 overload retains unlimited three-second retries. Optional `onRetry` reports waiting/requesting phases without blocking recovery. Shared structured-output mapping and cancellation match browser TWiN transport. No browser profile, AI/user singleton, DBOPFS, or retained request history is created. |
@@ -1,7 +1,8 @@
1
1
  # Core browser client
2
2
 
3
3
  `arcane-os/core/client` is the browser-safe `arcane/1` RPC owner. It exports
4
- `createCoreClient`, `createCoreFacade` and `installCoreClient`.
4
+ `createCoreClient`, `createCoreFacade`, `installCoreClient` and
5
+ `getInstalledCoreClient`.
5
6
  `arcane-os/core/contracts` exports `CORE_PROTOCOL`, `CORE_READY_EVENTS`,
6
7
  `CORE_FRAME_CONTRACTS`, `CORE_METHOD_CONTRACTS`, `CoreError` and
7
8
  `serializeCoreError`. Contracts describe transport and existing method shapes;
@@ -25,6 +26,23 @@ remain available through this facade; their implementations and availability
25
26
  belong to the selected host. No repository, app list, model selection or service
26
27
  implementation is installed by importing this client.
27
28
 
29
+ An application using a host-installed client can read it without installing or
30
+ replacing anything:
31
+
32
+ ```js
33
+ import {getInstalledCoreClient} from 'arcane-os/core/client';
34
+
35
+ const client=getInstalledCoreClient();
36
+ // null means this global has no live SDK-installed client.
37
+ ```
38
+
39
+ `getInstalledCoreClient(global=globalThis)` returns the exact live SDK-installed
40
+ client, including one installed by classic document-created injection. It does
41
+ not connect, install a client or replace an existing `Arcane` object. Applications
42
+ need not inspect private installation fields. It returns `null` after that
43
+ installation closes or fails. A
44
+ foreign application's `Arcane` object is not evidence of an SDK installation.
45
+
28
46
  The native adapters select WebView2, WebKitGTK or Android WebView. Ordinary
29
47
  browsers remain `standalone`; they do not probe a local server. Development
30
48
  HTTP is selected only by the existing explicit `__ARCANE_DEV_HTTP__` flag.
@@ -41,6 +59,14 @@ without callable `send(frame)` remains disconnected and reports
41
59
  `ARCANE_TRANSPORT_INVALID`; it does not announce `transport.ready`.
42
60
  `autoConnect:false` defers connection until `connect()` or the first invocation.
43
61
 
62
+ `replayRuntimeState:true` sends one `runtime.replay` control after the transport's
63
+ receive listener and `transport.ready` are established. The default is `false`,
64
+ preserving existing hosts. The send is observed without delaying page startup;
65
+ delivery errors reach `onError`. A host supporting this control supplies its
66
+ current dispatcher and service state for a newly connected document, without
67
+ restarting services. Repeated `connect()` calls on the same connection do not
68
+ send another replay. See [runtime replay](core-runtime.md#state-and-frames).
69
+
44
70
  `client.invoke(method, parameters, {signal, timeoutMs})` returns the actual
45
71
  response result or rejects with `CoreError`. Parameters, result fields and
46
72
  event data retain their complete supplied content. JSON encoding belongs only
@@ -75,6 +101,20 @@ native callbacks, timers and pending renderer requests; it does not kill Core.
75
101
  Call `stop()` to remove an individual subscription and `client.close()` when
76
102
  the client owner ends its lifetime.
77
103
 
104
+ For an actual terminal child-process or transport failure, the host calls
105
+ `client.failTransport(error)`. Installed clients also own the native callback
106
+ `globalThis.__arcaneTransportFailed(error)`. This boundary rejects pending RPCs
107
+ and WebKit acknowledgements with the complete actual `CoreError`, reports that
108
+ failure once, releases callbacks/subscriptions/timers and rejects future
109
+ invocations with the same failure. It sends no cancellation frames to a failed
110
+ transport and does not invent response identifiers or results. A normal close
111
+ still uses cancellation rather than reporting a transport failure.
112
+
113
+ Closing or failing an installation restores its previous `Arcane` object and
114
+ native callbacks only when those globals still belong to that installation.
115
+ Another owner's later replacements remain unchanged. The read-only accessor
116
+ returns `null` before the installed failure is reported to `onError`.
117
+
78
118
  Errors retain complete native fields, including diagnostics and stack/cause
79
119
  information. WebView2 and Android bridge failures retain native error codes,
80
120
  messages and diagnostic fields; the request method and transport are added
@@ -90,7 +130,8 @@ transport/event-owner failures outside individual request promises.
90
130
  import {createCoreClassicSource} from 'arcane-os/core/classic-source';
91
131
 
92
132
  const source=await createCoreClassicSource({
93
- eventOwnerModuleURL:'/arcane/sdk/event-manager.mjs'
133
+ eventOwnerModuleURL:'/arcane/sdk/event-manager.mjs',
134
+ replayRuntimeState:true
94
135
  });
95
136
  // Supply source to the native host's existing classic-script injection owner.
96
137
  ```
@@ -100,6 +141,8 @@ installed SDK. It emits the same implementation as a classic script, without
100
141
  copying another client or adding a bundler. The caller supplies the actual
101
142
  served SDK event-manager URL; the example URL must match its selected layout.
102
143
  That module and its existing managed import map must be included by packaging.
144
+ Select `replayRuntimeState:true` only when composing a host with the current
145
+ runtime replay control; omitting it preserves the existing no-replay default.
103
146
 
104
147
  The generated facade, native receive callback and current-state methods install
105
148
  synchronously. When the shared event owner already exists, it is reused
@@ -107,6 +107,16 @@ Events have `{protocol, type:'event', event, data, time}`:
107
107
  - `core.service.state`: a service's name, state and serialized error when present.
108
108
  - `core.error`: a lifecycle or transport diagnostic.
109
109
 
110
+ A newly connected document may send
111
+ `{protocol:'arcane/1', type:'control', control:'runtime.replay'}`. The runtime
112
+ publishes one current snapshot: `core.ready` only when the dispatcher is ready,
113
+ then `core.state`, followed by `core.service.state` for every registered service.
114
+ Complete service errors and active request state are retained. Replay does not
115
+ start or restart services, dispatch a request, poll readiness or create a second
116
+ lifecycle owner. The runtime completes that snapshot before publishing lifecycle
117
+ changes triggered reentrantly by its listeners. A draining runtime reports its
118
+ draining state rather than announcing readiness.
119
+
110
120
  Service states are `registered`, `starting`, `ready`, `failed`, `draining` and
111
121
  `closed`. Dispatcher states are `created`, `ready`, `draining` and `closed`.
112
122
  Complete diagnostics belong in the host's developer-facing error handling, not