arcane-os 0.54.1 → 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.
Files changed (45) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +35 -28
  3. package/browser-runtime/core/client.mjs +552 -0
  4. package/browser-runtime/core/contracts.mjs +85 -0
  5. package/docs/architecture.md +31 -23
  6. package/docs/compatibility.md +10 -1
  7. package/docs/consumer-updates.md +82 -0
  8. package/docs/platform-targets.md +20 -11
  9. package/docs/publishing.md +41 -18
  10. package/docs/reference/README.md +19 -7
  11. package/docs/reference/availability-and-normalization.md +15 -5
  12. package/docs/reference/behavioral-testing.md +7 -6
  13. package/docs/reference/cli.md +12 -4
  14. package/docs/reference/core-client.md +163 -0
  15. package/docs/reference/core-native-packaging.md +149 -0
  16. package/docs/reference/core-runtime.md +185 -0
  17. package/docs/reference/inventory/package-api.json +271 -5
  18. package/docs/reference/protocols.md +38 -14
  19. package/docs/reference/pwa.md +17 -0
  20. package/docs/reference/sdk-api.md +772 -15
  21. package/package.json +10 -3
  22. package/schemas/arcane-app.schema.json +10 -0
  23. package/schemas/arcane-package.schema.json +10 -0
  24. package/schemas/native-build-plan.schema.json +8 -0
  25. package/src/app-descriptor.mjs +18 -1
  26. package/src/app-layout.mjs +6 -2
  27. package/src/cli/main.mjs +8 -6
  28. package/src/core/host.mjs +23 -0
  29. package/src/core/hosts/arcane-core-loader.cjs +2 -0
  30. package/src/core/hosts/linux/arcane_core_process.c +568 -0
  31. package/src/core/hosts/linux/arcane_core_process.h +127 -0
  32. package/src/core/hosts/sea-launcher.cjs +17 -0
  33. package/src/core/hosts/windows/ArcaneCoreProcess.cs +416 -0
  34. package/src/core/runtime.mjs +345 -0
  35. package/src/core/stdio.mjs +209 -0
  36. package/src/core-classic-source.mjs +43 -0
  37. package/src/import-map.mjs +62 -0
  38. package/src/native/portable-layout.mjs +184 -0
  39. package/src/native/portable-provider.mjs +211 -0
  40. package/src/native-plan.mjs +10 -4
  41. package/src/native-provider-loader.mjs +15 -4
  42. package/src/packager/core.mjs +131 -32
  43. package/src/process.mjs +8 -4
  44. package/src/targets/index.mjs +3 -1
  45. package/src/toolchain.mjs +23 -6
@@ -0,0 +1,163 @@
1
+ # Core browser client
2
+
3
+ `arcane-os/core/client` is the browser-safe `arcane/1` RPC owner. It exports
4
+ `createCoreClient`, `createCoreFacade`, `installCoreClient` and
5
+ `getInstalledCoreClient`.
6
+ `arcane-os/core/contracts` exports `CORE_PROTOCOL`, `CORE_READY_EVENTS`,
7
+ `CORE_FRAME_CONTRACTS`, `CORE_METHOD_CONTRACTS`, `CoreError` and
8
+ `serializeCoreError`. Contracts describe transport and existing method shapes;
9
+ they do not select application policy or decide which services are installed.
10
+
11
+ ## Native connection and explicit adapters
12
+
13
+ ```js
14
+ import {installCoreClient} from 'arcane-os/core/client';
15
+
16
+ const client=installCoreClient();
17
+ const stop=client.events.when('core.ready',state=>{
18
+ console.log('The moon-base dispatcher is connected.',state);
19
+ });
20
+ ```
21
+
22
+ Installation immediately supplies `globalThis.Arcane`, `__arcaneReceive`,
23
+ synchronous `Arcane.runtime.current()` and `Arcane.events.completed(name)`.
24
+ Repeated installation returns the same client. Existing namespace method names
25
+ remain available through this facade; their implementations and availability
26
+ belong to the selected host. No repository, app list, model selection or service
27
+ implementation is installed by importing this client.
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
+
46
+ The native adapters select WebView2, WebKitGTK or Android WebView. Ordinary
47
+ browsers remain `standalone`; they do not probe a local server. Development
48
+ HTTP is selected only by the existing explicit `__ARCANE_DEV_HTTP__` flag.
49
+ Native process startup and shutdown belong to the native host/runtime, not
50
+ this browser module.
51
+
52
+ For a separately owned transport, use `createCoreClient({transport})` with
53
+ `transport.name`, `send(frame)`, and an optional `subscribe(receive)` returning
54
+ an unsubscribe function. Alternatively deliver responses/events through
55
+ `client.receive(frame)`. An adapter may return a promise from `send`; rejection
56
+ rejects the associated pending request. If cancellation, timeout or a response
57
+ has already settled it, `onError` observes the later send failure. A transport
58
+ without callable `send(frame)` remains disconnected and reports
59
+ `ARCANE_TRANSPORT_INVALID`; it does not announce `transport.ready`.
60
+ `autoConnect:false` defers connection until `connect()` or the first invocation.
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
+
70
+ `client.invoke(method, parameters, {signal, timeoutMs})` returns the actual
71
+ response result or rejects with `CoreError`. Parameters, result fields and
72
+ event data retain their complete supplied content. JSON encoding belongs only
73
+ to transports that require it; values crossing those transports must be
74
+ JSON-compatible. There is no client method allowlist or content limit.
75
+ The default request timeout is ten minutes; `timeoutMs:0` disables that timer.
76
+ The compatibility facade retains its operation-specific timeouts and streaming
77
+ callbacks. Streaming IDs and isolated-operation IDs are separate protocol
78
+ correlation fields alongside the supplied request fields.
79
+
80
+ ## Events and request lifetime
81
+
82
+ `events.on(name, listener)`, `once(name, listener)` and
83
+ `when(name, listener)` return unsubscribe functions. Named listeners receive
84
+ the original `data`; `on('*', listener)` receives `{event, data}`. The shared
85
+ SDK event owner provides delivery, with one source per client and no competing
86
+ event bus. `when` replays the first authoritative `transport.ready` or
87
+ `core.ready` state once, on a microtask. `completed` is synchronous.
88
+
89
+ Transport connection, Core dispatcher readiness, application-service state,
90
+ repository synchronization and model readiness are different states.
91
+ `core.ready` does not claim that a model is loaded or an app repository synced.
92
+ Other service events pass through normally; this client does not fabricate
93
+ service readiness.
94
+
95
+ Abort/timeout sends `request.cancel`; `cancelAll()` and page hide send
96
+ `requests.cancelAll` for pending renderer requests. Late responses cannot
97
+ resolve a cancelled request. These controls do not promise to roll back an
98
+ accepted mutation. Accepted saves, operation journals and graceful drain belong
99
+ to the service/runtime owner. `client.close()` releases its subscriptions,
100
+ native callbacks, timers and pending renderer requests; it does not kill Core.
101
+ Call `stop()` to remove an individual subscription and `client.close()` when
102
+ the client owner ends its lifetime.
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
+
118
+ Errors retain complete native fields, including diagnostics and stack/cause
119
+ information. WebView2 and Android bridge failures retain native error codes,
120
+ messages and diagnostic fields; the request method and transport are added
121
+ where the native error does not already supply them. Errors without a native
122
+ code receive the adapter's bridge-call failure code. Keep those details in
123
+ developer diagnostics, rather than ordinary
124
+ product status surfaces. The optional `onError(error)` callback observes
125
+ transport/event-owner failures outside individual request promises.
126
+
127
+ ## Classic native-host injection
128
+
129
+ ```js
130
+ import {createCoreClassicSource} from 'arcane-os/core/classic-source';
131
+
132
+ const source=await createCoreClassicSource({
133
+ eventOwnerModuleURL:'/arcane/sdk/event-manager.mjs',
134
+ replayRuntimeState:true
135
+ });
136
+ // Supply source to the native host's existing classic-script injection owner.
137
+ ```
138
+
139
+ This Node-only source generator reads the canonical client and contracts in the
140
+ installed SDK. It emits the same implementation as a classic script, without
141
+ copying another client or adding a bundler. The caller supplies the actual
142
+ served SDK event-manager URL; the example URL must match its selected layout.
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.
146
+
147
+ The generated facade, native receive callback and current-state methods install
148
+ synchronously. When the shared event owner already exists, it is reused
149
+ immediately. Document-created injection can precede the page's import map, so
150
+ only its event-owner module import waits until document parsing completes.
151
+ Page rendering and RPC setup continue independently. Complete early event
152
+ frames, subscription operations and response deliveries remain ordered until
153
+ the event owner connects. Work received reentrantly during that handoff joins
154
+ the end of the existing queue, including new subscriptions and response frames.
155
+ Final response completion follows earlier event
156
+ delivery, preserving streaming callbacks before their completion cleanup.
157
+ `client.eventsReady` observes that connection; failures are also reported
158
+ through `onError` and reject pending requests.
159
+
160
+ The SDK's [portable native provider](core-native-packaging.md) assembles the
161
+ Core runtime and selected app services without a native-provider checkout.
162
+ It does not distribute a Core executable or change OS callers. Executable
163
+ platform hosts and their cutover remain separately owned work.
@@ -0,0 +1,149 @@
1
+ # Portable Core packaging
2
+
3
+ `arcane-os/native/portable-provider` owns the portable native payload in the SDK
4
+ package. It does not load Arcane OS source, choose models, download a runtime,
5
+ or compile an executable. Windows, Linux and macOS hosts may compose this
6
+ Node-based payload; Android needs its separate native host adaptation. The
7
+ current CLI's portable platform selections remain Windows and Linux. A portable
8
+ artifact is not a Windows executable, Linux package or Android application.
9
+
10
+ `loadArcaneNativeProvider({target:'portable'})` selects the installed SDK provider
11
+ without an `arcaneRoot`. Supplying an explicit `arcaneRoot` retains the existing
12
+ checkout-provider route. Other native targets remain with their actual platform
13
+ providers; the loader does not substitute this payload for an unavailable host.
14
+
15
+ ## Provider and selected input
16
+
17
+ The default export and `arcaneNativeBuilderProvider` are the same provider.
18
+ `createPortableNativeProvider({services:[]})` creates one with application-owned
19
+ service configuration. All implement `arcane-native-builder/1`:
20
+
21
+ - `describe()` identifies the portable target and `executable:false`.
22
+ - `doctor({targetRequest, signal, onEvent})` reports missing SDK Core source files.
23
+ - `prepare({targetRequest, signal, onEvent})` returns the selected SDK version and
24
+ package root. Neither operation builds or imports application services.
25
+ - `build(request)` assembles one selected release and its explicit dependencies.
26
+ - `verify({artifact, targetRequest, signal, onEvent})` explicitly reads its semantic
27
+ manifest and observes that the listed output files exist. It does not execute
28
+ a host or establish native behavior. Build does not implicitly run verification.
29
+ - `run()` reports `ARCANE_NATIVE_RUN_UNSUPPORTED`; portable is not an executable.
30
+
31
+ Use the existing `createNativeBuildPlan()` / `executeNativeBuildPlan()` public
32
+ contract. Build receives `appDescriptor`, `appReleaseRoot`, `release`,
33
+ `readReleaseFile`, `dependencies`, `minimumCoreVersion`, `protectedRoots`,
34
+ `outputRoot`, `targetRequest`, `selectedSdk`, `signal` and `onEvent`. Dependencies provide their
35
+ `appId`, `releaseRoot`, `release` and `readReleaseFile`. The selected release's
36
+ `files` inventory is copied completely through its supplied reader. Direct
37
+ callers may supply the release root for ordinary file reads instead.
38
+
39
+ Both the current flat signing selection and the CLI's nested signing selection
40
+ are accepted by the planning boundary. Portable assembly adds no signing,
41
+ receipts, hashes, content limits or security policy. The package-relative source
42
+ root comes from the provider module's own location, not `toolchainRoot` or an
43
+ Arcane OS machine bundle.
44
+
45
+ ## Output and host contract
46
+
47
+ Each build creates a fresh `arcane-portable-*` directory beneath `outputRoot`.
48
+ The result is `{app, target, manifest}`, with its actual directory at
49
+ `target.rootDir`. Existing artifacts are preserved. Cancellation or a failed
50
+ assembly removes only that build's newly created directory. Source/release roots
51
+ cannot be overwritten with generated output.
52
+
53
+ The selected app is copied unchanged under `app/`. Dependencies are copied
54
+ unchanged under `dependencies/<index>/`; the manifest relates each directory to
55
+ its actual app identifier and selected release. No HTML, payload, line ending,
56
+ application import, or base URL is rewritten by this provider. Dependency
57
+ navigation and service imports remain explicit host/application composition;
58
+ the payload does not invent cross-app routes.
59
+
60
+ `node_modules/arcane-os/` contains the SDK consumer source, runtime, schemas,
61
+ commands, package metadata and legal files. Its installed published runtime
62
+ dependency tree is included with licenses and actual nested dependency
63
+ resolution. The generated Core entry uses the ordinary public
64
+ `arcane-os/core/host` import. Application service modules can use those public
65
+ SDK exports through Node's normal package resolution. Existing app-owned
66
+ `node_modules` content remains authoritative where the selected app supplies it;
67
+ its own dependency completeness remains part of that app's selected release.
68
+ For the direct installed browser layout, the ordinary native selection also
69
+ supplies `selectedSdk:{packageRoot,packageSource}` from the application's actual
70
+ installed SDK. Assembly completes its browser-only package directory with
71
+ missing files and dependencies from that selected package, preserving every
72
+ selected file. It never fills an older projection with the builder's newer
73
+ package metadata. An already-supplied `package.json` remains app-owned, without
74
+ replacement or automatic completion. `manifest.appSdk` identifies this app
75
+ package separately from the builder SDK. Low-level callers composing services
76
+ with a partial direct SDK projection must supply the actual selected package
77
+ source; a file inventory alone does not identify its version or Node exports.
78
+ No dependency installer or package script runs. No OS model, Shell, Provisioner,
79
+ catalog, prompt, or native compiler is added.
80
+
81
+ The semantic `arcane-native.json` names the web root, selected start URL, Node
82
+ Core entry, classic client, dependencies and complete file inventory. A native
83
+ host must:
84
+
85
+ 1. Mount `app/` at the root of its local application URL origin, preserving the
86
+ selected start path and all navigable documents.
87
+ 2. Inject `runtime/arcane-api.js` at document creation. It is produced by the
88
+ canonical `createCoreClassicSource()` generator, not a copied OS client.
89
+ 3. Start `runtime/arcane-core.mjs` with the host's supported Node runtime and
90
+ connect the existing framed stdin/stdout transport.
91
+ 4. Close input and await Core's drain before terminating the child process.
92
+
93
+ The selected release must contain one shared `sdk/event-manager.mjs` path,
94
+ possibly nested under its SDK payload directory, or the direct installed
95
+ `node_modules/<package-or-alias>/browser-runtime/event-manager.mjs` path.
96
+ The classic client imports that
97
+ same owner with an origin-root URL, including on nested pages. It does not create
98
+ a second event implementation. A plain file-URL launch without this host mapping
99
+ is not the native serving contract. No host is launched during packaging.
100
+
101
+ ## Explicit application services
102
+
103
+ Pass native service modules already included in the selected app release:
104
+
105
+ ```js
106
+ import {createPortableNativeProvider} from 'arcane-os/native/portable-provider';
107
+
108
+ const nativeBuilder = createPortableNativeProvider({
109
+ services: [{module:'native/moon-ledger.mjs', options:{ledgerName:'Cheese debts'}}]
110
+ });
111
+ ```
112
+
113
+ Each module default-exports a synchronous factory returning the existing Core
114
+ service definition. Its options must be JSON-serializable data. Module imports
115
+ and factories run only when the generated Core entry is launched, never during
116
+ packaging. Put asynchronous initialization in `start()`, not a top-level await
117
+ or asynchronous factory, so unrelated services and dispatcher readiness remain
118
+ independent. Factories own product configuration and any service dependencies;
119
+ the packager does not discover, install or execute them.
120
+
121
+ ```js
122
+ // native/moon-ledger.mjs, owned by the application
123
+ export default function createMoonLedger({ledgerName}) {
124
+ return {
125
+ name: 'moon-ledger',
126
+ methods: {
127
+ 'moon.describe': function describeLedger() {
128
+ return {name:ledgerName, message:'The cheese debts are astronomical.'};
129
+ }
130
+ }
131
+ };
132
+ }
133
+ ```
134
+
135
+ `startCoreHost({application, version, services, input, output, onError, signal})`
136
+ is also available directly from `arcane-os/core/host`. It returns the existing
137
+ stdio owner's `{runtime, closed, close}` without another lifecycle controller.
138
+ An optional abort signal invokes that same idempotent drain. Dispatcher readiness
139
+ does not wait for service/model readiness. Accepted `lifetime:'service'` work
140
+ survives renderer cancellation and is drained before shutdown. Errors remain
141
+ observable through `onError` and `closed`; no process is killed or forcibly exited.
142
+ See [the Core runtime contract](core-runtime.md) for request and service lifetime
143
+ details and [the client contract](core-client.md) for native bridge integration.
144
+
145
+ This source increment supplies portable assembly and service composition.
146
+ Desktop WebView executables, platform child-process ownership, Android dispatch
147
+ and executable build toolchains require their own completed adapters. Published
148
+ consumers must adopt the numeric package that includes this increment; a source
149
+ checkout or a successful source push is not that public package authority.
@@ -0,0 +1,185 @@
1
+ # Native Core runtime
2
+
3
+ The SDK owns the reusable native dispatcher and framed stdio transport.
4
+ The composing application or native host supplies its identity, version and
5
+ services. Product prompts, model selections, filesystem locations, host
6
+ privileges and application policy remain with that owner.
7
+
8
+ ```js
9
+ import {createCoreRuntime} from 'arcane-os/core/runtime';
10
+ import {startCoreStdio} from 'arcane-os/core/stdio';
11
+ import catalogStore from './catalog-store.mjs';
12
+
13
+ const runtime = createCoreRuntime({
14
+ application: {id: 'moon-cheese-catalog', name: 'Moon Cheese Catalog'},
15
+ version: '1.0.0',
16
+ services: [{
17
+ name: 'catalog',
18
+ methods: {
19
+ 'catalog.save': {
20
+ lifetime: 'service',
21
+ async handle(record) {
22
+ // catalogStore is supplied by this application's native owner.
23
+ return catalogStore.save(record);
24
+ }
25
+ }
26
+ },
27
+ async drain() {
28
+ await catalogStore.flush();
29
+ },
30
+ async dispose() {
31
+ await catalogStore.close();
32
+ }
33
+ }]
34
+ });
35
+
36
+ const transport = startCoreStdio({runtime});
37
+ await transport.closed;
38
+ ```
39
+
40
+ The example's store is an application dependency, not an SDK-created database.
41
+ The runtime passes each request's `parameters` to its registered handler
42
+ unchanged. It preserves complete results and diagnostic errors; it neither
43
+ selects product data nor supplies replacement results.
44
+
45
+ ## Registration and startup
46
+
47
+ `createCoreRuntime({application, version, services})` creates a dispatcher.
48
+ Supply the application's real descriptor and host release version. The built-in
49
+ `app.current` and `version.current` methods return those values; `system.ping`
50
+ returns `{ok:true}`. A ping establishes dispatcher responsiveness, not model,
51
+ service or application readiness.
52
+
53
+ `runtime.registerService({name, methods, start, drain, dispose})` registers one
54
+ native service. Method names are complete RPC names, such as `catalog.save`.
55
+ A method is either a handler function or `{handle, lifetime}`. A handler function
56
+ has request lifetime by default. Duplicate service or method names, including
57
+ the three built-in methods, are reported rather than replacing another owner.
58
+ Registration is a native composition API, not a browser command.
59
+
60
+ Registration returns `{ready, current}`. Reading `ready` starts that service and
61
+ returns its shared startup promise; `current()` exposes its state and actual
62
+ error. `runtime.start()` starts all registered services independently and returns
63
+ the current dispatcher state without waiting for them. A service registered
64
+ after startup starts independently as well. A method waits only for its own
65
+ service's startup before invoking its handler. State errors use the shared
66
+ complete diagnostic serialization; the startup promise retains its original
67
+ rejection value.
68
+
69
+ Each lifecycle hook receives `{application, service, emit}`. Each request handler
70
+ receives `(parameters, {application, service, emit, requestId, signal})`, with
71
+ `this` bound to its service definition. A hook or handler may publish a complete
72
+ service-owned event through `emit(event, data)`.
73
+
74
+ ## State and frames
75
+
76
+ `runtime.current()` returns dispatcher state, application, version, per-service
77
+ state/errors and active request identities, methods and lifetimes.
78
+ `runtime.subscribe(listener, {emitCurrent:true, signal})` immediately replays the
79
+ current state and then observes changes through the canonical SDK event owner.
80
+ It returns an unsubscribe function. `runtime.onFrame(listener, {signal})`
81
+ observes outgoing protocol frames and also returns an unsubscribe function.
82
+ Attach transport listeners before starting the runtime to observe initial frames.
83
+
84
+ The shared `arcane-os/core/contracts` module owns `CORE_PROTOCOL` (`arcane/1`),
85
+ `CoreError` and `serializeCoreError`. Requests are:
86
+
87
+ ```js
88
+ {
89
+ protocol: 'arcane/1',
90
+ type: 'request',
91
+ id: 'request-1',
92
+ method: 'catalog.save',
93
+ parameters: {name: 'Lunar cheddar', notes: 'Keep the complete supplied text.'}
94
+ }
95
+ ```
96
+
97
+ `await runtime.handle(frame)` returns and emits the correlated response:
98
+ `{protocol, type:'response', id, ok:true, result, time}` or
99
+ `{protocol, type:'response', id, ok:false, error, time}`. Framing and method
100
+ availability errors remain observable; unavailable services are not replaced
101
+ with browser behavior.
102
+
103
+ Events have `{protocol, type:'event', event, data, time}`:
104
+
105
+ - `core.ready`: the dispatcher is connected; service/model readiness is separate.
106
+ - `core.state`: the current complete runtime state.
107
+ - `core.service.state`: a service's name, state and serialized error when present.
108
+ - `core.error`: a lifecycle or transport diagnostic.
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
+
120
+ Service states are `registered`, `starting`, `ready`, `failed`, `draining` and
121
+ `closed`. Dispatcher states are `created`, `ready`, `draining` and `closed`.
122
+ Complete diagnostics belong in the host's developer-facing error handling, not
123
+ in ordinary chat history or a fabricated conversation turn.
124
+
125
+ ## Cancellation and shutdown
126
+
127
+ The `request.cancel` control names `requestId`; `requests.cancelAll` applies to
128
+ active request-lifetime operations. Cancellation is cooperative. The handler's
129
+ signal is aborted. An otherwise successful cancelled operation returns
130
+ `REQUEST_ABORTED` after its owned work settles; a handler's own rejection remains
131
+ the actual reported error. If startup is still pending, cancellation is checked before
132
+ the handler runs. A service that does not finish or respond to cancellation is
133
+ still unfinished; the SDK does not report a forced timeout as successful cleanup.
134
+
135
+ Use `lifetime:'service'` for accepted work, such as a save, that must survive a
136
+ renderer cancellation or disconnect. These operations are not aborted by the
137
+ request cancellation controls or by normal Core shutdown. The service still
138
+ owns its persistence, queueing and failure semantics.
139
+
140
+ `runtime.close()` is idempotent and retains the same completion promise. It stops
141
+ accepting new requests, cancels request-lifetime operations, waits for accepted
142
+ responses, then drains and disposes services. Independent services close
143
+ concurrently; each service's startup, drain and dispose remain ordered.
144
+ Shutdown errors remain complete and reject the close promise. A failed drain
145
+ does not prevent the service's dispose hook from running.
146
+
147
+ The final closed state is published before event subscriptions are released.
148
+ `current()` remains available afterward; the disposed runtime is not restarted.
149
+ The host must observe completion before terminating Core. The SDK does not call
150
+ `process.exit()`, kill a host or claim durability after an operating-system crash
151
+ or a host-forced termination.
152
+
153
+ ## Stdio transport
154
+
155
+ `startCoreStdio({runtime, input, output, onError})` attaches a runtime and starts
156
+ it. Streams default to `process.stdin` and `process.stdout`; diagnostics default
157
+ to `console.error`, leaving stdout for protocol frames. It returns
158
+ `{runtime, closed, close}`. Observe `closed` for final completion or failure.
159
+
160
+ Frames use the existing `Content-Length` header followed by UTF-8 JSON. Length
161
+ is transport-local framing only. `encodeCoreFrame(frame)` and
162
+ `createCoreFrameDecoder(onFrame)` are also exported for host adapters. The
163
+ decoder accepts fragmented or multiple frames through `push(chunk)`;
164
+ `finish()` reports an incomplete final frame rather than dropping it.
165
+
166
+ Requests dispatch concurrently. Outgoing frames preserve their queued order and
167
+ wait for each writable-stream callback. Input EOF starts runtime shutdown, waits
168
+ for accepted service work and queued output, and resolves `closed` afterward.
169
+ An input stream closed without its normal EOF event also initiates this drain;
170
+ an already ended or destroyed input is handled when attached. Transport failures
171
+ reach `onError` and reject `closed`. The transport detaches
172
+ its listeners and pauses its input; it does not end caller-owned output streams
173
+ or take ownership of the host process.
174
+
175
+ ## Platform and delivery boundary
176
+
177
+ These modules use portable Node and JavaScript facilities for Windows, Linux
178
+ and macOS native hosts. Android requires its native transport/host adaptation;
179
+ the module is not a Node runtime embedded inside a WebView. Browser callers use
180
+ the separate [Core client](core-client.md).
181
+
182
+ The dispatcher/transport sources are one part of Core extraction. They do not
183
+ by themselves replace existing native host bundles, platform adapters, provider
184
+ loading, packaging or OS composition. Those owners must adopt the selected
185
+ published SDK boundary and preserve their real shutdown and service lifecycles.