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.
- package/CHANGELOG.md +41 -0
- package/README.md +1 -1
- package/browser-runtime/ai/model-observation.mjs +315 -0
- package/browser-runtime/core/app-control.mjs +27 -1
- package/docs/reference/README.md +1 -0
- package/docs/reference/cli.md +5 -2
- package/docs/reference/core-native-packaging.md +14 -0
- package/docs/reference/core-runtime.md +20 -11
- package/docs/reference/core-shared-host.md +9 -3
- package/docs/reference/execution-devices.md +6 -0
- package/docs/reference/inventory/package-api.json +49 -7
- package/docs/reference/model-observation.md +220 -0
- package/docs/reference/native-app-control.md +132 -16
- package/docs/reference/sdk-api.md +106 -8
- package/package.json +3 -1
- package/src/cli/app-control.mjs +14 -3
- package/src/core/app-control.mjs +2 -0
- package/src/core/hosts/windows/ArcaneAppControl.cs +2 -1
- package/src/core/hosts/windows/ArcaneWindowControl.cs +216 -31
- package/src/core/runtime-connection.mjs +4 -1
- package/src/core/runtime.mjs +15 -7
- package/src/core/services/model-observation.mjs +231 -0
- package/src/local-ai/execution-devices-windows.ps1 +3 -1
- package/src/native/portable-layout.mjs +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.86.0
|
|
4
|
+
|
|
5
|
+
- Add fixed `Tab`, `Shift+Tab`, `Enter` and `Space` key operations to
|
|
6
|
+
`connectAppControl` and the public `arcane app-control key` command. Each
|
|
7
|
+
request sends an ordered press/release pair to the selected WebView's current
|
|
8
|
+
focus without desktop activation or global input. Preserve complete phase
|
|
9
|
+
outcomes, original errors and observation of a dispatched release after
|
|
10
|
+
cancellation or navigation; never replay an ambiguous action.
|
|
11
|
+
- Add `resize` to the same client and CLI for a Normal window's native client
|
|
12
|
+
dimensions. Return actual previous and resulting dimensions plus the observed
|
|
13
|
+
CSS viewport; restoration is an explicit second request. Preserve window
|
|
14
|
+
state, position, profile, origin and zoom, and report minimized or maximized
|
|
15
|
+
state without changing it.
|
|
16
|
+
- Include viewport, document focus and accessible open-shadow focus-path
|
|
17
|
+
observations in inspection. These are DOM/native observations, not proof of
|
|
18
|
+
an application action or completed asynchronous handler. The matching Windows
|
|
19
|
+
host asset is required for the new operations; existing consumer artifacts
|
|
20
|
+
must adopt the public package and rebuild through their owning workflow.
|
|
21
|
+
|
|
22
|
+
## 0.85.0
|
|
23
|
+
|
|
24
|
+
- Add `createModelObservationService({runtime, localAI, image, decisions})`
|
|
25
|
+
through `arcane-os/core/model-observation`, and renderer
|
|
26
|
+
`attachModelObservation` / observer `subscribeModelObservation` through
|
|
27
|
+
`arcane-os/ai/model-observation`. Observe the actual existing Core and
|
|
28
|
+
renderer model owners without creating, loading or replacing a runtime,
|
|
29
|
+
provider or model.
|
|
30
|
+
- Replay complete current owner state and retain its reported errors, with
|
|
31
|
+
explicit opt-in live diagnostics and connection-owned cancellation. Keep
|
|
32
|
+
original client request identity separate from actual internal Core request
|
|
33
|
+
identity. Native `coreListener` composition reuses existing selected services;
|
|
34
|
+
applications explicitly attach their renderer owners.
|
|
35
|
+
- Preserve the complete Windows PnP property batch while narrowing only the
|
|
36
|
+
property cmdlet's internal concurrency. Keep asynchronous shared discovery,
|
|
37
|
+
cancellation, explicit refresh and model/device selection unchanged. This
|
|
38
|
+
addresses the observed missing/duplicate property rows without adding retries
|
|
39
|
+
or another hardware query.
|
|
40
|
+
- These capabilities expose current state and correct the property query;
|
|
41
|
+
they do not claim to repair or verify native model inference. No dependency
|
|
42
|
+
changes or native host rebuild are required for this revision.
|
|
43
|
+
|
|
3
44
|
## 0.84.0
|
|
4
45
|
|
|
5
46
|
- Add `startCoreListener({runtime, endpoint, onError})` through
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
|
|
|
25
25
|
share the theme, packaging, event, cancellation,
|
|
26
26
|
and browser run contracts while retaining their selected application layout.
|
|
27
27
|
|
|
28
|
-
This checkout defines the `0.
|
|
28
|
+
This checkout defines the `0.86.0` SDK contract. Development applications declare
|
|
29
29
|
`arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
|
|
30
30
|
designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
|
|
31
31
|
to refresh and compare the installed SDK and lockfile with the published channel;
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import Is from '../dependencies/strong-type/index.js';
|
|
2
|
+
import {CoreError, serializeCoreError} from '../core/contracts.mjs';
|
|
3
|
+
import {getInstalledCoreClient, subscribeCoreClient} from '../core/client.mjs';
|
|
4
|
+
|
|
5
|
+
const is = new Is(false);
|
|
6
|
+
|
|
7
|
+
/** Attach the caller's existing renderer owners to its existing Core client. */
|
|
8
|
+
export function attachModelObservation({client, aiRuntimeState = null, imageRuntime = null,
|
|
9
|
+
decisionModel = null, modelController = null, signal, onError} = {}) {
|
|
10
|
+
requireOwner(aiRuntimeState, ['getAIRuntimeState', 'subscribeAIRuntimeState'], 'aiRuntimeState');
|
|
11
|
+
requireOwner(imageRuntime, ['current', 'subscribe'], 'imageRuntime');
|
|
12
|
+
requireOwner(decisionModel, ['status', 'subscribe'], 'decisionModel');
|
|
13
|
+
requireOwner(modelController, ['status', 'on'], 'modelController');
|
|
14
|
+
const lifetime = observationLifetime({client, signal, onError});
|
|
15
|
+
let attachmentRequestId;
|
|
16
|
+
let coreRequestId;
|
|
17
|
+
let snapshot = null;
|
|
18
|
+
let attached = false;
|
|
19
|
+
|
|
20
|
+
function readOwners() {
|
|
21
|
+
return {
|
|
22
|
+
aiRuntimeState: aiRuntimeState?.getAIRuntimeState() ?? null,
|
|
23
|
+
imageRuntime: imageRuntime?.current() ?? null,
|
|
24
|
+
decisionModel: decisionModel?.status() ?? null,
|
|
25
|
+
modelController: modelController?.status() ?? null
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function publishOwners() {
|
|
30
|
+
if (!attached || lifetime.stopped) return;
|
|
31
|
+
try {
|
|
32
|
+
snapshot = readOwners();
|
|
33
|
+
lifetime.invoke('model.observation.renderer.publish', {
|
|
34
|
+
attachmentRequestId, coreRequestId, snapshot: transportSnapshot(snapshot)
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
lifetime.fail(error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function observeOwners() {
|
|
42
|
+
if (aiRuntimeState) lifetime.cleanup(aiRuntimeState.subscribeAIRuntimeState(
|
|
43
|
+
publishOwners, {emitCurrent: false, signal: lifetime.signal}
|
|
44
|
+
));
|
|
45
|
+
if (imageRuntime) lifetime.cleanup(imageRuntime.subscribe(
|
|
46
|
+
publishOwners, {replay: false, signal: lifetime.signal}
|
|
47
|
+
));
|
|
48
|
+
if (decisionModel) lifetime.cleanup(decisionModel.subscribe(
|
|
49
|
+
publishOwners, {emitCurrent: false, signal: lifetime.signal}
|
|
50
|
+
));
|
|
51
|
+
if (modelController) {
|
|
52
|
+
lifetime.cleanup(modelController.on('statechange', publishOwners));
|
|
53
|
+
lifetime.cleanup(modelController.on('progress', publishOwners));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function acknowledgeAttachment(value) {
|
|
58
|
+
if (lifetime.stopped || attached || value?.attachmentRequestId !== attachmentRequestId) return;
|
|
59
|
+
try {
|
|
60
|
+
if (!is.string(value.coreRequestId)) {
|
|
61
|
+
throw observationError('MODEL_OBSERVATION_CONTEXT_UNAVAILABLE',
|
|
62
|
+
'The renderer acknowledgement omitted the actual Core request identity.');
|
|
63
|
+
}
|
|
64
|
+
coreRequestId = value.coreRequestId;
|
|
65
|
+
attached = true;
|
|
66
|
+
observeOwners();
|
|
67
|
+
// Read again after all subscriptions exist so changes during the
|
|
68
|
+
// attachment handshake are represented by current owner state.
|
|
69
|
+
publishOwners();
|
|
70
|
+
lifetime.acknowledge(value);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
lifetime.fail(error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!lifetime.stopped) {
|
|
77
|
+
try {
|
|
78
|
+
snapshot = readOwners();
|
|
79
|
+
lifetime.cleanup(client.events.on('model.observation.renderer.attached', acknowledgeAttachment));
|
|
80
|
+
lifetime.invoke('model.observation.renderer.attach', {snapshot: transportSnapshot(snapshot)}, {
|
|
81
|
+
long: true,
|
|
82
|
+
onRequest: function identifyAttachment(value) { attachmentRequestId = value.requestId; }
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
lifetime.fail(error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
ready: lifetime.ready,
|
|
91
|
+
closed: lifetime.closed,
|
|
92
|
+
current: function currentRendererObservation() { return snapshot; },
|
|
93
|
+
close: lifetime.close
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Subscribe through an existing Core client; model diagnostics are explicit. */
|
|
98
|
+
export function subscribeModelObservation({client, signal, onState, onDiagnostic, onError} = {}) {
|
|
99
|
+
if (!is.function(onState)) throw new TypeError('Model observation requires an onState listener.');
|
|
100
|
+
if (onDiagnostic !== undefined && !is.function(onDiagnostic)) {
|
|
101
|
+
throw new TypeError('onDiagnostic must be a function.');
|
|
102
|
+
}
|
|
103
|
+
const lifetime = observationLifetime({client, signal, onError});
|
|
104
|
+
let watchRequestId;
|
|
105
|
+
let snapshot = null;
|
|
106
|
+
|
|
107
|
+
function receiveState(value) {
|
|
108
|
+
if (lifetime.stopped || value?.watchRequestId !== watchRequestId) return;
|
|
109
|
+
if (!value.snapshot || !is.object(value.snapshot)) {
|
|
110
|
+
lifetime.fail(observationError('MODEL_OBSERVATION_STATE_INVALID', 'Core omitted the observation snapshot.'));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
snapshot = value.snapshot;
|
|
114
|
+
lifetime.acknowledge(snapshot);
|
|
115
|
+
lifetime.notify(onState, snapshot);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function receiveDiagnostic(value) {
|
|
119
|
+
if (lifetime.stopped || value?.watchRequestId !== watchRequestId) return;
|
|
120
|
+
lifetime.notify(onDiagnostic, value);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!lifetime.stopped) {
|
|
124
|
+
try {
|
|
125
|
+
lifetime.cleanup(client.events.on('model.observation.state', receiveState));
|
|
126
|
+
if (onDiagnostic) lifetime.cleanup(client.events.on('model.observation.error', receiveDiagnostic));
|
|
127
|
+
lifetime.invoke('model.observation.watch', {diagnostics: Boolean(onDiagnostic)}, {
|
|
128
|
+
long: true,
|
|
129
|
+
onRequest: function identifyWatch(value) { watchRequestId = value.requestId; }
|
|
130
|
+
});
|
|
131
|
+
} catch (error) {
|
|
132
|
+
lifetime.fail(error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
ready: lifetime.ready,
|
|
138
|
+
closed: lifetime.closed,
|
|
139
|
+
current: function currentModelObservation() { return snapshot; },
|
|
140
|
+
close: lifetime.close
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function requireOwner(owner, methods, label) {
|
|
145
|
+
if (owner === null) return;
|
|
146
|
+
for (const method of methods) {
|
|
147
|
+
if (!is.function(owner?.[method])) throw new TypeError(`${label} must be an existing owner exposing ${method}().`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function observationError(code, message) {
|
|
152
|
+
return new CoreError({code, message});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function cancelledObservation() {
|
|
156
|
+
return new CoreError({name: 'AbortError', code: 'ARCANE_REQUEST_ABORTED', message: 'Model observation was cancelled.'});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function isCancellation(error) {
|
|
160
|
+
return error?.name === 'AbortError' || error?.code === 'ARCANE_REQUEST_ABORTED' || error?.code === 'REQUEST_ABORTED';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Error encoding belongs only to the existing JSON transport boundary. */
|
|
164
|
+
function transportSnapshot(snapshot) {
|
|
165
|
+
return Object.fromEntries(Object.entries(snapshot).map(function transportOwner([name, value]) {
|
|
166
|
+
return [name, value && is.error(value.error) ? {...value, error: serializeCoreError(value.error)} : value];
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function reportObservationFailure(error) {
|
|
171
|
+
globalThis.console?.error('Model observation failed.', error);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Own only observation RPCs and subscriptions, never the supplied owners. */
|
|
175
|
+
function observationLifetime({client, signal, onError = reportObservationFailure}) {
|
|
176
|
+
if (!is.function(client?.invoke) || !is.function(client?.events?.on)) {
|
|
177
|
+
throw new TypeError('Model observation requires an existing Core client.');
|
|
178
|
+
}
|
|
179
|
+
if (!is.function(onError)) throw new TypeError('onError must be a function.');
|
|
180
|
+
const controller = new AbortController();
|
|
181
|
+
const cleanups = [];
|
|
182
|
+
const pending = new Set();
|
|
183
|
+
const failures = [];
|
|
184
|
+
let stopped = false;
|
|
185
|
+
let acknowledged = false;
|
|
186
|
+
let completed = false;
|
|
187
|
+
let resolveReady;
|
|
188
|
+
let rejectReady;
|
|
189
|
+
let resolveClosed;
|
|
190
|
+
let rejectClosed;
|
|
191
|
+
const ready = new Promise(function ownObservationReadiness(resolve, reject) {
|
|
192
|
+
resolveReady = resolve;
|
|
193
|
+
rejectReady = reject;
|
|
194
|
+
});
|
|
195
|
+
const closed = new Promise(function ownObservationClosure(resolve, reject) {
|
|
196
|
+
resolveClosed = resolve;
|
|
197
|
+
rejectClosed = reject;
|
|
198
|
+
});
|
|
199
|
+
// The same failures are reported through onError and retained on these
|
|
200
|
+
// public promises; unused companion promises must not create extra alerts.
|
|
201
|
+
ready.catch(function readinessFailureObservedByLifetime() {});
|
|
202
|
+
closed.catch(function closureFailureObservedByLifetime() {});
|
|
203
|
+
|
|
204
|
+
function recordFailure(error) {
|
|
205
|
+
if (failures.includes(error)) return;
|
|
206
|
+
failures.push(error);
|
|
207
|
+
try {
|
|
208
|
+
Promise.resolve(onError(error)).catch(function reportErrorCallbackFailed(callbackError) {
|
|
209
|
+
reportObservationFailure(callbackError);
|
|
210
|
+
});
|
|
211
|
+
} catch (callbackError) {
|
|
212
|
+
reportObservationFailure(callbackError);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function finish() {
|
|
217
|
+
if (!stopped || pending.size || completed) return;
|
|
218
|
+
completed = true;
|
|
219
|
+
if (failures.length === 1) rejectClosed(failures[0]);
|
|
220
|
+
else if (failures.length) rejectClosed(new AggregateError(failures, 'Model observation closed with errors.'));
|
|
221
|
+
else resolveClosed();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function close() {
|
|
225
|
+
if (!stopped) {
|
|
226
|
+
stopped = true;
|
|
227
|
+
for (const cleanup of cleanups.splice(0)) {
|
|
228
|
+
try { cleanup(); } catch (error) { recordFailure(error); }
|
|
229
|
+
}
|
|
230
|
+
controller.abort();
|
|
231
|
+
if (!acknowledged) rejectReady(failures[0] ?? cancelledObservation());
|
|
232
|
+
}
|
|
233
|
+
finish();
|
|
234
|
+
return closed;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function fail(error) {
|
|
238
|
+
recordFailure(error);
|
|
239
|
+
close();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function cleanup(release) {
|
|
243
|
+
if (!is.function(release)) throw new TypeError('An observation subscription must return an unsubscribe function.');
|
|
244
|
+
if (stopped) release();
|
|
245
|
+
else cleanups.push(release);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function invoke(method, parameters, {long = false, onRequest} = {}) {
|
|
249
|
+
if (stopped) return;
|
|
250
|
+
// Reserve ownership before invoke: an in-process transport can deliver
|
|
251
|
+
// acknowledgement synchronously while invoke is still on the stack.
|
|
252
|
+
const operation = {};
|
|
253
|
+
pending.add(operation);
|
|
254
|
+
let task;
|
|
255
|
+
try {
|
|
256
|
+
task = client.invoke(method, parameters, {signal: controller.signal, timeoutMs: 0, onRequest});
|
|
257
|
+
} catch (error) {
|
|
258
|
+
task = Promise.reject(error);
|
|
259
|
+
}
|
|
260
|
+
Promise.resolve(task).then(function observationCompleted() {
|
|
261
|
+
pending.delete(operation);
|
|
262
|
+
if (long && !stopped) {
|
|
263
|
+
if (!acknowledged) fail(observationError('MODEL_OBSERVATION_ENDED', 'The observation ended before acknowledgement.'));
|
|
264
|
+
else close();
|
|
265
|
+
}
|
|
266
|
+
finish();
|
|
267
|
+
}, function observationFailed(error) {
|
|
268
|
+
pending.delete(operation);
|
|
269
|
+
if (isCancellation(error)) close();
|
|
270
|
+
else fail(error);
|
|
271
|
+
finish();
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function acknowledge(value) {
|
|
276
|
+
if (stopped || acknowledged) return;
|
|
277
|
+
acknowledged = true;
|
|
278
|
+
resolveReady(value);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function notify(listener, value) {
|
|
282
|
+
if (stopped || !listener) return;
|
|
283
|
+
try {
|
|
284
|
+
// Callback lifetimes belong to callers, who may await close().
|
|
285
|
+
// Observe rejection without making that callback a close barrier.
|
|
286
|
+
Promise.resolve(listener(value)).catch(fail);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
fail(error);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function retireObservation() { close(); }
|
|
293
|
+
if (signal) {
|
|
294
|
+
signal.addEventListener('abort', retireObservation, {once: true});
|
|
295
|
+
cleanup(function releaseCallerSignal() { signal.removeEventListener('abort', retireObservation); });
|
|
296
|
+
}
|
|
297
|
+
if (is.function(globalThis.addEventListener)) {
|
|
298
|
+
globalThis.addEventListener('pagehide', retireObservation, {once: true});
|
|
299
|
+
cleanup(function releasePageLifetime() { globalThis.removeEventListener('pagehide', retireObservation); });
|
|
300
|
+
}
|
|
301
|
+
if (getInstalledCoreClient() === client) {
|
|
302
|
+
cleanup(subscribeCoreClient(function installedOwnerChanged(value) {
|
|
303
|
+
if (value.client !== client) {
|
|
304
|
+
if (value.error) fail(value.error);
|
|
305
|
+
else close();
|
|
306
|
+
}
|
|
307
|
+
}, {emitCurrent: false}));
|
|
308
|
+
}
|
|
309
|
+
if (signal?.aborted) close();
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
ready, closed, signal: controller.signal, cleanup, invoke, acknowledge, notify, fail, close,
|
|
313
|
+
get stopped() { return stopped; }
|
|
314
|
+
};
|
|
315
|
+
}
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* returned completely. Frame documents and unselected shadow-root contents stay
|
|
10
10
|
* outside this scope. Closed shadow roots cannot be discovered or entered.
|
|
11
11
|
* domRole/domName are DOM-derived hints, not an accessibility-tree computation.
|
|
12
|
+
* Document-level viewport/focus observations also describe the active open-shadow
|
|
13
|
+
* branch, independently of the selected inspection root. The native-only view
|
|
14
|
+
* operation reads that observation without collecting HTML/control roots.
|
|
12
15
|
*
|
|
13
16
|
* act({action,selector,shadowPath?,...}) requires one current CSS match in the
|
|
14
17
|
* selected root. fill uses an exact
|
|
@@ -36,7 +39,10 @@ export function runAppControl(operation, params = {}) {
|
|
|
36
39
|
if (operation === 'act') {
|
|
37
40
|
return {ok: true, result: actOnElement()};
|
|
38
41
|
}
|
|
39
|
-
|
|
42
|
+
if (operation === 'view') {
|
|
43
|
+
return {ok: true, result: readView()};
|
|
44
|
+
}
|
|
45
|
+
fail('INVALID_ARGUMENT', 'Expected the inspect, act, or view operation.');
|
|
40
46
|
} catch (error) {
|
|
41
47
|
return {
|
|
42
48
|
ok: false,
|
|
@@ -66,6 +72,25 @@ export function runAppControl(operation, params = {}) {
|
|
|
66
72
|
return Array.from(root.querySelectorAll(selector));
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
// Native keys and resize need only this fixed observation, not a DOM scan.
|
|
76
|
+
function readView() {
|
|
77
|
+
const focusPath = [];
|
|
78
|
+
let element = document.activeElement;
|
|
79
|
+
while (element) {
|
|
80
|
+
focusPath.push(describeElement(element, null));
|
|
81
|
+
element = element.shadowRoot?.activeElement ?? null;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
viewport: {
|
|
85
|
+
width: globalThis.innerWidth,
|
|
86
|
+
height: globalThis.innerHeight,
|
|
87
|
+
devicePixelRatio: globalThis.devicePixelRatio
|
|
88
|
+
},
|
|
89
|
+
documentHasFocus: document.hasFocus(),
|
|
90
|
+
focusPath
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
69
94
|
function selectedRoot() {
|
|
70
95
|
const shadowPath = params.shadowPath === undefined ? [] : params.shadowPath;
|
|
71
96
|
if (!Array.isArray(shadowPath)) {
|
|
@@ -156,6 +181,7 @@ export function runAppControl(operation, params = {}) {
|
|
|
156
181
|
...(isDocument ? {} : {shadowPath}),
|
|
157
182
|
title: document.title,
|
|
158
183
|
readyState: document.readyState,
|
|
184
|
+
...readView(),
|
|
159
185
|
doctype: document.doctype
|
|
160
186
|
? new XMLSerializer().serializeToString(document.doctype)
|
|
161
187
|
: null,
|
package/docs/reference/README.md
CHANGED
|
@@ -70,6 +70,7 @@ alone does not make bare module names resolve in a browser.
|
|
|
70
70
|
| Own an explicitly selected native Codex App Server session | [Codex App Server](codex-app-server.md) |
|
|
71
71
|
| Expose application-owned tools and static resources through Node STDIO | [MCP STDIO server](mcp-stdio.md) |
|
|
72
72
|
| Run application-selected llama.cpp, Ollama or ONNX through Core | [Local AI through Core](local-ai.md) |
|
|
73
|
+
| Observe existing renderer and Core model state, progress and failures | [Existing model observation](model-observation.md) |
|
|
73
74
|
| Generate complete local PNG images with a retained native model | [Local image generation](local-image-generation.md) |
|
|
74
75
|
| Select FLUX.2 Klein 4B and its three complementary model resources | [FLUX.2 Klein 4B through Core](local-image-flux.md) |
|
|
75
76
|
| Select the complete SDXL Base 1.0 checkpoint for generation and whole-image editing | [SDXL Base 1.0 through Core](local-image-sdxl.md) |
|
package/docs/reference/cli.md
CHANGED
|
@@ -17,7 +17,10 @@ JSON to stderr and a nonzero exit. Capture writes its image to the explicit
|
|
|
17
17
|
the general output-format parser below applies to the other commands.
|
|
18
18
|
Inspection accepts either `--selector` or `--request <json-file>`; the latter
|
|
19
19
|
reads the complete inspection object, including an explicit `shadowPath` through
|
|
20
|
-
open shadow roots. Actions
|
|
20
|
+
open shadow roots. Actions, fixed keys and Normal-window resizing use `--request`
|
|
21
|
+
for their complete parameter object. Keys require the observed document
|
|
22
|
+
generation; resize returns actual previous native client dimensions for an
|
|
23
|
+
explicit restoration request.
|
|
21
24
|
|
|
22
25
|
## Command inventory
|
|
23
26
|
|
|
@@ -41,7 +44,7 @@ open shadow roots. Actions continue to use `--request` for their complete object
|
|
|
41
44
|
| `arcane run` | Serves an existing browser release, or packages, plans, builds, and launches one paired native artifact. |
|
|
42
45
|
| `arcane update-check` | Performs one explicit, read-only npm dist-tag query for the installed SDK version. |
|
|
43
46
|
| `arcane targets` | Lists target ids, declared status, formats, architectures, signing profiles, methods, and pairing reason. |
|
|
44
|
-
| `arcane app-control status\|inspect\|capture\|act` | Connects to an explicit running native window endpoint for document inspection, viewport capture
|
|
47
|
+
| `arcane app-control status\|inspect\|capture\|act\|key\|resize` | Connects to an explicit running native window endpoint for document inspection, viewport capture, targeted DOM actions, fixed keys or Normal-window client resizing. |
|
|
45
48
|
| `arcane repo status\|pull\|push` | Runs one selected repository operation for the current app workspace. |
|
|
46
49
|
| `arcane mail key set\|status\|delete` | Manages one server-only Resend API-key profile in `.arcane.env.json`. |
|
|
47
50
|
| `arcane mail send` | Performs one explicit, idempotency-keyed Resend attempt from a complete JSON report on redirected stdin. |
|
|
@@ -245,6 +245,20 @@ listener endpoint has no default and its parent directory must already exist
|
|
|
245
245
|
where required by the operating system. `coreListener` and `sharedHost` are
|
|
246
246
|
alternative selections; supplying both reports `TypeError`.
|
|
247
247
|
|
|
248
|
+
That same `coreListener` opt-in registers one
|
|
249
|
+
[`createModelObservationService()`](model-observation.md) after `startCoreHost()`
|
|
250
|
+
and before binding the listener. It receives `host.runtime` and the exact
|
|
251
|
+
already-created service objects exposing `localai.status`, `image.status` and
|
|
252
|
+
`decisions.status`; a configurable decision-service name remains supported.
|
|
253
|
+
Absent owners are `null`. Composition creates no model or second runtime,
|
|
254
|
+
performs no service lookup and adds no model-readiness wait. Ordinary stdio
|
|
255
|
+
without this opt-in and the existing `sharedHost` branches retain their behavior.
|
|
256
|
+
The application separately calls `attachModelObservation()` with its existing
|
|
257
|
+
renderer owners and Core client; native composition cannot infer those objects.
|
|
258
|
+
Observers select `onDiagnostic` to receive complete live model failure frames
|
|
259
|
+
in their developer diagnostic surface. Observation does not start inference or
|
|
260
|
+
establish that an existing model failure has been corrected.
|
|
261
|
+
|
|
248
262
|
Clients use `connectSharedCoreHost({endpoint})` with `start` omitted and close
|
|
249
263
|
only their own connection. This listener provides no host-shutdown RPC. Closing
|
|
250
264
|
the window still drains its Core, including accepted service-owned operations,
|
|
@@ -67,7 +67,7 @@ complete diagnostic serialization; the startup promise retains its original
|
|
|
67
67
|
rejection value.
|
|
68
68
|
|
|
69
69
|
Each lifecycle hook receives `{application, service, emit, getService}`. Each request handler
|
|
70
|
-
receives `(parameters, {application, service, emit, getService, requestId, coreRequestId, signal})`, with
|
|
70
|
+
receives `(parameters, {application, service, emit, getService, requestId, clientRequestId, coreRequestId, signal})`, with
|
|
71
71
|
`this` bound to its service definition. A hook or handler may publish a complete
|
|
72
72
|
service-owned event through `emit(event, data)`.
|
|
73
73
|
|
|
@@ -133,16 +133,25 @@ availability errors remain observable; unavailable services are not replaced
|
|
|
133
133
|
with browser behavior.
|
|
134
134
|
|
|
135
135
|
A transport that assigns internal dispatch IDs may call
|
|
136
|
-
`runtime.handle(frame, {contextRequestId})`.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
`frame.id`,
|
|
144
|
-
|
|
145
|
-
|
|
136
|
+
`runtime.handle(frame, {contextRequestId, contextClientRequestId})`. Each supplied
|
|
137
|
+
metadata value must be a nonempty string. Handler identities are:
|
|
138
|
+
|
|
139
|
+
| Field | Value |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `requestId` | `contextRequestId ?? frame.id`, preserving the transport's established handler contract |
|
|
142
|
+
| `clientRequestId` | `contextClientRequestId ?? contextRequestId ?? frame.id`, exposing the actual incoming client identity |
|
|
143
|
+
| `coreRequestId` | `frame.id`, the actual internal dispatch identity |
|
|
144
|
+
|
|
145
|
+
Ordinary `handle(frame)` exposes all three equal to `frame.id`. Existing callers
|
|
146
|
+
that supply only `contextRequestId` also receive that value as `clientRequestId`.
|
|
147
|
+
Stdio and both IPC listener forms always supply the actual client ID separately.
|
|
148
|
+
Stdio and the borrowed listener preserve it as `requestId` too; the owning
|
|
149
|
+
shared-host adapter retains its established internal `requestId` contract.
|
|
150
|
+
|
|
151
|
+
The supplied `frame.id` still owns active-request state, cancellation lookup,
|
|
152
|
+
response correlation and the request event's top-level envelope. It distinguishes
|
|
153
|
+
simultaneous requests whose clients selected equal IDs. Parameters, results and
|
|
154
|
+
event data remain unchanged; these identity fields are separate context metadata.
|
|
146
155
|
|
|
147
156
|
A request with no registered handler retains code `METHOD_NOT_ALLOWED` and
|
|
148
157
|
adds the exact `method`, its first dotted `namespace`, and a `reason`.
|
|
@@ -58,7 +58,8 @@ the supplied runtime alive. A draining/closed runtime rejects attachment with
|
|
|
58
58
|
|
|
59
59
|
Stdio and each IPC connection have independent request correlation and
|
|
60
60
|
cancellation. Handlers reached through stdio or this listener receive the
|
|
61
|
-
original client ID in `context.requestId
|
|
61
|
+
original client ID in both `context.requestId` and `context.clientRequestId`;
|
|
62
|
+
`context.coreRequestId` exposes the
|
|
62
63
|
fresh internal ID used by runtime dispatch and protocol routing. Responses and
|
|
63
64
|
top-level event correlation return to their originating connection. The new listener leaves all event data,
|
|
64
65
|
including any authored `data.requestId`, and all parameters/results unchanged.
|
|
@@ -186,8 +187,13 @@ substitutes an internal Core request ID and restores the originating ID on respo
|
|
|
186
187
|
the optional top-level event `requestId`. Existing `data.requestId` values that
|
|
187
188
|
carry that same Core correlation ID are restored too. `parameters`, returned results, schemas,
|
|
188
189
|
documents, and content remain complete and unchanged. A handler's
|
|
189
|
-
`context.requestId`
|
|
190
|
-
separate `requestId` field, not
|
|
190
|
+
`context.requestId` remains the Core-side correlation ID, equal to
|
|
191
|
+
`context.coreRequestId`; put it in the event's separate `requestId` field, not
|
|
192
|
+
inside application content. The additive `context.clientRequestId` exposes the
|
|
193
|
+
actual incoming client ID independently of that compatibility behavior. Services
|
|
194
|
+
such as [model observation](model-observation.md) can therefore publish explicit
|
|
195
|
+
client-facing acknowledgement fields without changing the existing adapter or
|
|
196
|
+
rewriting application payloads.
|
|
191
197
|
|
|
192
198
|
Responses and request-context events go to the originating connection in
|
|
193
199
|
order. Core supplies the top-level request correlation outside `data`, so two
|
|
@@ -41,6 +41,12 @@ An adapter whose stable identity cannot be established remains in the inventory
|
|
|
41
41
|
with `deviceId: null` and a corresponding issue. The SDK does not invent a key
|
|
42
42
|
from a name, enumeration order, hardware model number or adapter LUID.
|
|
43
43
|
|
|
44
|
+
Windows discovery reads the present PCI devices' bus and address properties in
|
|
45
|
+
one batched `Get-PnpDeviceProperty` call with `-ThrottleLimit 1`. This limits
|
|
46
|
+
concurrency inside that query because concurrent CIM operations have been
|
|
47
|
+
observed to duplicate some device results and omit others. Discovery remains
|
|
48
|
+
asynchronous at the host, with the same shared catalog and explicit refresh.
|
|
49
|
+
|
|
44
50
|
The CPU choice uses `deviceId: 'cpu'` and `identitySource: 'host-cpu'`. It selects
|
|
45
51
|
the whole-host CPU execution target, without claiming socket or core affinity.
|
|
46
52
|
Its `processors` entries contain `{deviceId, name, socketDesignation, cores,
|