arcane-os 0.84.0 → 0.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,59 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "sdkVersion": "0.83.0",
3
+ "sdkVersion": "0.85.0",
4
4
  "environment": {
5
5
  "runtime": "Node.js for Node entrypoints; browser for browser-only entrypoints",
6
6
  "minimumVersion": "22.23.2 for Node entrypoints",
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",
@@ -0,0 +1,220 @@
1
+ # Existing model observation
2
+
3
+ Model observation reads the existing renderer and Core owners selected by the
4
+ application. It exposes their current lifecycle, progress, retained errors and
5
+ actual operation identities to an explicitly selected developer observer.
6
+ Model loading, inference, selection, cancellation and saved application state
7
+ remain with those owners. Observation creates no model, accessor or Core runtime.
8
+
9
+ ## Compose the existing Core
10
+
11
+ ```js
12
+ import {createModelObservationService} from 'arcane-os/core/model-observation';
13
+
14
+ const observation = createModelObservationService({
15
+ runtime: host.runtime,
16
+ localAI: existingLocalAIService,
17
+ image: existingImageService,
18
+ decisions: existingDecisionService
19
+ });
20
+ host.runtime.registerService(observation);
21
+ ```
22
+
23
+ `host` and the three services in this example are the application's existing
24
+ instances. `localAI`, `image` and `decisions` are optional and default to `null`.
25
+ Supply their actual service definitions, including `current()` and `methods`.
26
+ The constructor neither looks up nor starts a service. Registration can follow
27
+ `startCoreHost()` on its returned runtime, before opening an additional listener;
28
+ it requires no model readiness barrier.
29
+
30
+ The service is named `model-observation` and exposes `current()`, `dispose()` and
31
+ the fixed Core methods below. `dispose()` retires only its observation requests
32
+ and subscriptions. The host continues to own the Core and model services.
33
+
34
+ An external local caller can connect through the explicitly selected
35
+ [`startCoreListener`](core-shared-host.md) endpoint on that same runtime.
36
+ For a packaged window, `native.launchContext.coreListener: {endpoint}` selects
37
+ that same-runtime listener. Before binding it, the generated Core entry registers
38
+ `model-observation` with the actual existing `services` definitions that expose
39
+ `localai.status`, `image.status` and `decisions.status`; absent owners are `null`.
40
+ The observer reuses those model-service owners and creates no accessor or model.
41
+ The application supplies its renderer attachment separately.
42
+ The observation module does not discover endpoints, launch a host, reconnect,
43
+ change a profile, or create a second runtime. Native DOM inspection and actions
44
+ retain their separate [app-control contract](native-app-control.md).
45
+
46
+ ## Attach existing renderer owners
47
+
48
+ ```js
49
+ import * as aiRuntimeState from 'arcane-os/ai-runtime-state';
50
+ import {attachModelObservation} from 'arcane-os/ai/model-observation';
51
+
52
+ const attachment = attachModelObservation({
53
+ client: existingCoreClient,
54
+ aiRuntimeState,
55
+ imageRuntime: existingImageAccessor,
56
+ decisionModel: existingBrowserDecisionModel,
57
+ modelController: existingModelController,
58
+ signal: pageLifetime.signal,
59
+ onError: function reportObservationFailure(error) {
60
+ developerDiagnostics.report(error);
61
+ }
62
+ });
63
+ await attachment.ready;
64
+ ```
65
+
66
+ Each optional owner defaults to `null`. Pass the instances already used by the
67
+ application. Creating another image accessor would describe that new accessor's
68
+ requests rather than the existing work. The supported owner methods are:
69
+
70
+ | Input | Read | Subscribe |
71
+ |---|---|---|
72
+ | `aiRuntimeState` | `getAIRuntimeState()` | `subscribeAIRuntimeState()` |
73
+ | `imageRuntime` | `current()` | `subscribe()` |
74
+ | `decisionModel` | `status()` | `subscribe()` |
75
+ | `modelController` | `status()` | `on('statechange')`, `on('progress')` |
76
+
77
+ The attachment returns `{ready, closed, current, close}`. `ready` resolves with
78
+ `{attachmentRequestId, coreRequestId}` after Core acknowledges the attachment.
79
+ `current()` returns the latest complete renderer snapshot read by this
80
+ attachment, or `null` when it was cancelled before reading. Subscriptions begin
81
+ after acknowledgement; the attachment then reads and publishes current state
82
+ again to cover changes during that handshake.
83
+
84
+ Caller abort, `pagehide`, retirement of the exact installed Core client,
85
+ transport failure, or `close()` removes the observation subscriptions and
86
+ cancels only this attachment's observation RPCs. A supplied client remains
87
+ caller-owned. The attachment never follows a replacement client or automatically
88
+ reattaches. Native document retirement also cancels its long-lived Core request.
89
+ Core then removes the renderer record; late publication to that retired
90
+ attachment fails honestly. Detachment does not unload or cancel a model.
91
+
92
+ `close()` returns `closed`, which settles after the attachment's observation
93
+ RPC promises settle locally. It does not claim that a remote model or host has
94
+ closed. Transport and observation errors remain available through `onError`
95
+ and the rejected public promises. With `onError` omitted they reach the developer
96
+ console. Normal cancellation, including cancellation initiated by the existing
97
+ client or native document owner, closes observation without a model error. When
98
+ cancellation precedes acknowledgement, `ready` rejects with the cancellation
99
+ while `closed` resolves after the observation RPCs settle.
100
+
101
+ ## Read or subscribe from an existing client
102
+
103
+ ```js
104
+ import {subscribeModelObservation} from 'arcane-os/ai/model-observation';
105
+
106
+ const current = await client.invoke('model.observation.current', {}, {signal});
107
+ const observation = subscribeModelObservation({
108
+ client,
109
+ signal,
110
+ onState: function showRetainedOwners(snapshot) {
111
+ developerDiagnostics.show(snapshot);
112
+ },
113
+ onDiagnostic: function showModelFailure(record) {
114
+ developerDiagnostics.report(record);
115
+ },
116
+ onError: function showObservationFailure(error) {
117
+ developerDiagnostics.report(error);
118
+ }
119
+ });
120
+ await observation.ready;
121
+ ```
122
+
123
+ `onState` is required. The returned `{ready, closed, current, close}` has the same
124
+ observation-only lifecycle as the renderer attachment. `ready` resolves with
125
+ the first current-state replay; `current()` holds the latest complete snapshot,
126
+ initially `null`. `onDiagnostic` explicitly selects live correlated Core model
127
+ failure frames. Omitting it requests state observation only. Callback failures
128
+ close that observation and reach `onError`; callback promises remain
129
+ caller-owned and may await `close()` without becoming its completion barrier.
130
+
131
+ Every snapshot is:
132
+
133
+ ```js
134
+ {
135
+ core: existingCoreRuntime.current(),
136
+ native: {localAI, image, decisions},
137
+ renderers: [{attachmentRequestId, coreRequestId, owners: {
138
+ aiRuntimeState, imageRuntime, decisionModel, modelController
139
+ }}]
140
+ }
141
+ ```
142
+
143
+ The values represent complete supplied-owner snapshots. Absent optional owners
144
+ are `null`; absent renderer attachments produce an empty array. Core state also
145
+ contains its real active observation requests, identified by their actual
146
+ methods, alongside other active requests. Observation does not relabel them as
147
+ model work. Snapshots are current observations of independent owners, rather
148
+ than an atomic transaction across all owners.
149
+
150
+ ## Fixed transport and correlation
151
+
152
+ | Method/event | Contract |
153
+ |---|---|
154
+ | `model.observation.current` | Empty parameters; returns the current snapshot without model refresh or readiness waits. |
155
+ | `model.observation.watch` | `{diagnostics:false}` by default; remains pending until cancellation. |
156
+ | `model.observation.state` | Targeted watch event `{watchRequestId,snapshot}`; the first event acknowledges the watch and replays current state. |
157
+ | `model.observation.error` | Targeted diagnostic `{watchRequestId,source:'core',method,frame}` preserving the complete original failed response frame. |
158
+ | `model.observation.renderer.attach` | `{snapshot}`; remains pending to own the renderer publication lifetime. |
159
+ | `model.observation.renderer.attached` | Targeted acknowledgement `{attachmentRequestId,coreRequestId}`. |
160
+ | `model.observation.renderer.publish` | `{attachmentRequestId,coreRequestId,snapshot}`; replaces that attachment's current snapshot and returns both attachment identities. |
161
+
162
+ Both helpers register listeners before invoking. Existing
163
+ [`client.invoke`](core-client.md) supplies `onRequest({requestId})` before send;
164
+ long-lived observation requests use `timeoutMs:0`. Their acknowledgement is a
165
+ targeted request event, while the invocation promise remains pending.
166
+
167
+ `attachmentRequestId` and `watchRequestId` are the actual client-visible Core
168
+ request identities supplied by service context `clientRequestId`.
169
+ `coreRequestId` is the actual internal Core frame identity;
170
+ renderer attachments are keyed by that value so clients with identical local
171
+ request IDs cannot collide. The renderer echoes both values when publishing.
172
+ `snapshot.core.activeRequests[].id` and diagnostic `frame.id` use internal Core
173
+ identities. They can differ from client-visible IDs under connection routing.
174
+ None of these observation/transport IDs is a model-operation ID.
175
+
176
+ Core supplies both metadata fields for direct runtime calls, stdio connections,
177
+ `startCoreListener()` connections and the existing `startSharedCoreHost()` path.
178
+ Each connection preserves the original incoming request ID as `clientRequestId`
179
+ independently of its established `requestId` mapping. Direct calls use the
180
+ incoming frame ID for both identities. Observation requires these actual
181
+ context fields; it does not infer them from `requestId` or rewrite event
182
+ payloads to manufacture correlation.
183
+
184
+ The exact `methods` keys on the supplied native service definitions select the
185
+ Core operations whose live failures can be observed. The observer tracks their
186
+ actual active request IDs while a watch exists. Core publishes request removal
187
+ before emitting its response; the observer retains that correlation until the
188
+ response arrives, then removes it. Removing the final watcher releases frame
189
+ observation and remaining transient correlation. It retains no failure history.
190
+
191
+ ## Evidence and lifecycle limits
192
+
193
+ - AIRuntimeState exposes its existing llm/stt/tts records, including the reported
194
+ operation ID. Its retained errors contain only code/message. Concurrent role
195
+ work is not a complete request enumeration in this snapshot.
196
+ - The existing image accessor exposes its own active method, stream ID, status
197
+ and progress records. Its retained error is preserved; that error has no
198
+ reliable explicit stream association. Constructor-only diagnostic callbacks
199
+ cannot be retroactively attached through this API.
200
+ - Browser and native decision snapshots retain their existing errors and active
201
+ request counts. A count does not supply missing per-evaluation IDs.
202
+ - ModelController status exposes its existing state/progress and code/message
203
+ error. Its load/unload event IDs are not retained by `status()`, and are not
204
+ presented as inference IDs or reconstructed history here.
205
+ - Core supplies its real active request identities and complete live failed
206
+ response frames. A watch cannot recover responses that settled before it
207
+ observed them. Retained owner errors remain available in current snapshots.
208
+
209
+ Renderer `Error` values in owner error fields use the existing Core error
210
+ serializer at the JSON boundary; other owner fields remain unchanged. A value
211
+ the selected transport cannot encode fails through the observation error path
212
+ rather than being replaced with shortened or invented content. Observation
213
+ does not alter model inputs, outputs, prompts or their persistence policy.
214
+
215
+ The shared JavaScript contract is portable wherever the existing Core client
216
+ and supplied owners are available. Endpoint and native-host availability belong
217
+ to their documented adapters. Developer observation remains separate from
218
+ ordinary user status, app-owned stage/avatar association and actual model
219
+ execution verification. This capability does not establish that an inference
220
+ failure has been corrected.
@@ -2,9 +2,9 @@
2
2
 
3
3
  `connectAppControl` from `arcane-os/core/app-control` connects to one explicitly
4
4
  selected running application window. The Windows WebView2 host provides document
5
- inspection, a PNG of its rendered viewport, and targeted DOM actions through its
6
- own local named pipe. The application keeps its existing Core child, profile,
7
- origin and window lifecycle. These operations do not activate the desktop window
5
+ inspection, a PNG of its rendered viewport, targeted DOM actions, fixed keys and
6
+ Normal-window client resizing through its own local named pipe. The application
7
+ keeps its existing Core child, profile, origin and window lifecycle. These operations do not activate the desktop window
8
8
  or send global keyboard or pointer input.
9
9
 
10
10
  The Node client uses the SDK's existing `arcane/1` framing and local pipe/socket
@@ -77,29 +77,45 @@ connection or other business result.
77
77
  | `inspect(parameters={}, options?)` | Optional `shadowPath` selects an open shadow root. Optional CSS `selector` selects zero or more elements within the selected root; omission selects the whole root. Optional `documentGeneration` selects the expected document. Returns complete HTML/text, live control state and DOM-derived role/name hints. |
78
78
  | `capture(parameters={}, options?)` | Returns `{mimeType:'image/png', data, documentGeneration, url}`. `data` contains the complete base64 PNG. Optional `documentGeneration` selects the expected document. |
79
79
  | `act(parameters, options?)` | Requires `documentGeneration`, one unambiguous CSS `selector` within the root selected by optional `shadowPath`, and the action fields below. |
80
+ | `key(parameters, options?)` | Requires `documentGeneration` and `key:'Tab'`, `'Enter'`, or `'Space'`. Optional `shiftKey:true` is supported only for Tab. Sends one ordered press/release pair to the selected WebView's current focus. |
81
+ | `resize(parameters, options?)` | Requires positive integral native client `width` and `height` supported by WinForms. Optional `documentGeneration` selects the expected document. Resizes only a Normal window and returns immediate previous/actual client dimensions plus the subsequently observed CSS viewport. |
80
82
  | `close()` | Closes this caller's connection and cancels its pending requests. It never closes the app or Core. Returns the connection's completion promise. |
81
83
  | `closed` | Connection lifetime promise; actual transport failures remain observable. |
82
84
  | `endpoint` | The caller-selected local endpoint. |
83
85
 
84
86
  Each operation's separate `options` uses the existing Core client request
85
87
  contract, including `signal` and `timeoutMs`. Connection cancellation closes only
86
- that connection. Cancellation before UI dispatch prevents execution. Once a
87
- script or capture has been dispatched, cancellation cannot reverse its effects;
88
- the host still observes its completion. A disconnected caller's completed result
89
- is retained in the host diagnostics instead of being represented as delivered.
88
+ that connection. Cancellation before UI dispatch prevents execution; keys also
89
+ check cancellation after waiting for the keyboard owner and immediately before
90
+ the press. Once a script, capture, key press or resize has been dispatched,
91
+ cancellation cannot reverse its effects. The host observes its actual completion,
92
+ including the owned key release. The caller receives its ordinary abort result;
93
+ the complete later response is retained in host diagnostics for a cancelled
94
+ request or closed connection instead of being represented as delivered.
90
95
 
91
96
  ## Document operations
92
97
 
93
98
  Inspection defaults to the top-level light DOM. Its result contains `scope`, `title`,
94
99
  `readyState`, `doctype`, `roots`, `controls`, `frames`, `shadowHosts`,
95
- `activeElement`, `scrollingElement`, `selection`, `documentGeneration`, and
96
- `url`. Each root retains its complete `html` and `text`; controls retain their
100
+ `activeElement`, `scrollingElement`, `selection`, `viewport`, `documentHasFocus`,
101
+ `focusPath`, `documentGeneration`, and `url`. Each root retains its complete
102
+ `html` and `text`; controls retain their
97
103
  attributes and current form, selection, disabled, scrolling and related state.
98
104
  Generated CSS selectors are useful for the inspected document; a later DOM edit
99
105
  can change their meaning. Application-authored stable selectors are preferable
100
106
  when available. Component replacement or mutation can make a selector stale
101
107
  without changing the native document generation.
102
108
 
109
+ `viewport` contains the top-level document's `innerWidth` and `innerHeight` as
110
+ CSS-pixel `width`/`height`, plus its current `devicePixelRatio`.
111
+ `documentHasFocus` records `document.hasFocus()`. `focusPath` describes the
112
+ top-level active element followed by each active element in accessible open
113
+ shadow roots along that focus branch. These records retain complete attributes,
114
+ DOM-derived names and control state, with `selector:null`; they are observations,
115
+ not selector targets. Reading a focused body or host's name can traverse its
116
+ complete text subtree. The path stops at a frame element or closed shadow host;
117
+ it does not enter frame documents or closed roots.
118
+
103
119
  ### Open shadow roots
104
120
 
105
121
  `inspect` and `act` accept `shadowPath`, an ordered array of CSS host selectors.
@@ -134,7 +150,10 @@ inspection; pair selectors from its result with that same path for actions.
134
150
  `activeElement` comes from the selected root. `scrollingElement` is `null` for a
135
151
  shadow-root scope because the root has no document scrolling element; scroll
136
152
  actions still target elements. `selection.text`, `title`, `readyState`, `doctype`
137
- and native URL/generation remain document-level information. Role/name label
153
+ and native URL/generation remain document-level information. The added
154
+ `viewport`, `documentHasFocus` and `focusPath` also always describe the top-level
155
+ document: the focus path can enter open roots outside the selected inspection
156
+ root. The selected root's `activeElement` remains separate. Role/name label
138
157
  references resolve within the element's own tree. Inspection follows the DOM
139
158
  tree, not the composed rendering tree: slot-assigned light-DOM nodes remain in
140
159
  their own containing DOM tree and are not duplicated inside the selected shadow
@@ -155,9 +174,10 @@ actual target state after dispatch, including disconnection by the action.
155
174
 
156
175
  Role/name hints are derived from DOM attributes and labels, not a native
157
176
  accessibility-tree computation. Frame documents and unselected shadow-root
158
- contents are outside the selected scope. Frame elements and open-shadow hosts
159
- are described; closed shadow roots cannot be discovered. Capture is the current rendered
160
- viewport, not an offscreen full-document rendering. Covered and minimized-window
177
+ contents are outside the selected HTML/control scope; the document-level focus
178
+ path separately describes its active open-shadow branch. Frame elements and
179
+ open-shadow hosts are described; closed shadow roots cannot be discovered.
180
+ Capture is the current rendered viewport, not an offscreen full-document rendering. Covered and minimized-window
161
181
  capture behavior requires execution evidence in the selected environment.
162
182
 
163
183
  | Action | Parameters and immediate behavior |
@@ -174,11 +194,101 @@ rewritten by the SDK.
174
194
 
175
195
  The host publishes a new generation when an actual replacement document becomes
176
196
  ready. A creation-time document marker is also checked inside the executing
177
- script so a queued action cannot run against a successor page. During navigation,
197
+ script so a queued DOM action cannot run against a successor page. During navigation,
178
198
  operations report that the document is not ready. A change during completion
179
199
  reports the actual result with that failure; actions are never retried. These
180
200
  markers are transient document lifecycle state and are not written to saved data.
181
201
 
202
+ ## Fixed keys
203
+
204
+ ```js
205
+ const page = await app.inspect();
206
+ const moved = await app.key({documentGeneration: page.documentGeneration, key: 'Tab'});
207
+ console.log(moved.previous.focusPath, moved.actual.focusPath);
208
+ await app.key({documentGeneration: moved.documentGeneration, key: 'Tab', shiftKey: true});
209
+ await app.key({documentGeneration: moved.documentGeneration, key: 'Enter'});
210
+ ```
211
+
212
+ Keys use the selected WebView's current focus. No selector, shadow path, frame
213
+ selection, focus assignment or desktop activation is performed. Tab and
214
+ Shift+Tab use a fixed Tab `rawKeyDown`/`keyUp` pair, with the Shift modifier on
215
+ both events for Shift+Tab. Enter and Space use `keyDown` with their corresponding
216
+ text, followed by `keyUp`, preserving browser default actions and application
217
+ `preventDefault` handling. Shift+Tab does not send a separate Shift key pair.
218
+ The API exposes these fixed keys, not an arbitrary protocol method or script.
219
+
220
+ The result contains `key`, `shiftKey`, `previous` and `actual` document views,
221
+ `press`, `release`, `documentGeneration` and `url`. Each view contains the same
222
+ `viewport`, `documentHasFocus` and `focusPath` described above. Each key phase
223
+ records its fixed `parameters`, `attempted`, `completed`, and the complete
224
+ protocol `response` when returned. Dispatch completion describes the native
225
+ command, not application handler completion or a successful business action.
226
+ Inspect the resulting page to establish the application outcome.
227
+
228
+ Accepted key pairs share one keyboard owner so another key request cannot
229
+ interleave between press and release. Other independent control operations keep
230
+ their existing scheduling. The host awaits each native phase in order. Once
231
+ the press is attempted, the host observes that task and attempts the matching
232
+ release on the same WebView even after cancellation, navigation or press
233
+ failure. Release can itself activate Space. A failed or ambiguous press is
234
+ never replayed.
235
+
236
+ Generation checks occur before dispatch and after completion; the native input
237
+ command has no atomic document binding. Navigation or focus changes can occur
238
+ between the checks and phases, and Tab intentionally changes focus before its
239
+ release. A detected document change is reported with the actual attempted and
240
+ completed phases. Dispatch, release or later observation failures retain the
241
+ complete partial result at `error.data.find(entry => entry.key === 'result').value`
242
+ under `ARCANE_APP_CONTROL_FAILED`. Original exceptions remain in `cause`; if
243
+ both native phases fail, both errors are also retained in the aggregate and
244
+ their respective phase records. An unavailable `actual` view is omitted rather
245
+ than inferred.
246
+
247
+ ## Resize and restore a Normal window
248
+
249
+ ```js
250
+ const resized = await app.resize({width: 1200, height: 800});
251
+ try {
252
+ console.log(resized.previous, resized.actual, resized.viewport);
253
+ console.log(await app.inspect({selector: '#pm-content'}));
254
+ } finally {
255
+ await app.resize({width: resized.previous.width, height: resized.previous.height});
256
+ }
257
+ ```
258
+
259
+ `width` and `height` select the native WinForms client dimensions, in the same
260
+ units returned by `status().window`. They are not CSS viewport dimensions.
261
+ The host changes only `ClientSize`; it does not assign window state, position,
262
+ restore bounds, activation or WebView zoom, and it preserves the profile and
263
+ origin. Read `viewport` for the resulting CSS dimensions instead of assuming a
264
+ particular DPI or zoom conversion.
265
+
266
+ Immediately before the UI-thread mutation, the host checks the actual
267
+ `WindowState` and records `previous:{title,state,width,height}`. It records
268
+ `actual` in the same shape immediately after the native setter, then observes
269
+ the CSS `viewport` asynchronously. The result also contains `requested`,
270
+ `resizeAttempted`, `resizeCompleted`, `documentGeneration` and `url`.
271
+ Platform constraints may make actual dimensions differ from requested ones.
272
+ Other input or concurrent resize requests can change the window between the
273
+ native snapshot and the document observation; these records are not an atomic
274
+ native/CSS snapshot. Completion does not establish that asynchronous application
275
+ resize handlers have settled.
276
+
277
+ Maximized or minimized windows return `ARCANE_APP_CONTROL_FAILED` before any
278
+ size mutation, with `reason:'unsupported-window-state'`,
279
+ `supportedWindowState:'Normal'` and the complete current `window` in the error's
280
+ `data` entries. The host never automatically restores, maximizes or minimizes a
281
+ window to satisfy this request. Invalid dimensions return `INVALID_ARGUMENT`.
282
+ The same state rule applies to explicit restoration.
283
+
284
+ Restore using the actual `previous.width` and `previous.height` from the
285
+ operation being reversed, never a historical status snapshot or CSS viewport
286
+ dimensions. Restoration is an explicit second request; cancellation does not
287
+ resize the window back. If a dispatched resize or its later observation fails,
288
+ the complete partial `result` remains in the error's `data`, including the
289
+ recorded previous dimensions, actual dimensions and attempted/completed flags.
290
+ Use that actual record when deciding whether and how to restore.
291
+
182
292
  ## CLI
183
293
 
184
294
  ```powershell
@@ -187,14 +297,20 @@ arcane app-control inspect --endpoint '\\.\pipe\arcane-pm-control' --selector '#
187
297
  arcane app-control inspect --endpoint '\\.\pipe\arcane-pm-control' --request '.arcane\inspection.json'
188
298
  arcane app-control capture --endpoint '\\.\pipe\arcane-pm-control' --output '.arcane\view.png'
189
299
  arcane app-control act --endpoint '\\.\pipe\arcane-pm-control' --request '.arcane\action.json'
300
+ arcane app-control key --endpoint '\\.\pipe\arcane-pm-control' --request '.arcane\key.json'
301
+ arcane app-control resize --endpoint '\\.\pipe\arcane-pm-control' --request '.arcane\resize.json'
190
302
  ```
191
303
 
192
- The action file is the complete action parameter object, including the actual
304
+ The action or key file is the complete parameter object, including the actual
193
305
  `documentGeneration` returned by the preceding status or inspection. Inspection
194
306
  also accepts `--request` for a complete parameter object, such as
195
307
  `{"shadowPath":["#appearance-panel","theme-switcher"]}` or the same object with
196
308
  `selector` and `documentGeneration`. Inspection's `--request` and `--selector`
197
- options are mutually exclusive. The CLI reads the selected file completely.
309
+ options are mutually exclusive. A key file can contain
310
+ `{"documentGeneration":3,"key":"Tab","shiftKey":true}`, using the observed
311
+ generation. A resize file contains native dimensions such as
312
+ `{"width":1200,"height":800}`. Each requires `--request`; the CLI reads the
313
+ selected file completely and forwards the parsed parameter object unchanged.
198
314
  Paths are relative to the current directory. Capture
199
315
  writes the PNG to the explicitly selected file and prints its metadata. The other
200
316
  operations print complete JSON. `app-control --help` describes this command's