arcane-os 0.80.0 → 0.82.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 +44 -0
- package/README.md +1 -1
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +97 -19
- package/browser-runtime/ai/dbopfs-model-parts.mjs +12 -5
- package/browser-runtime/ai/dbopfs-model-resources.mjs +2 -5
- package/browser-runtime/core/app-control.mjs +439 -0
- package/docs/reference/ai/browser-speech.md +11 -0
- package/docs/reference/ai/browser-wasm.md +21 -10
- package/docs/reference/cli.md +9 -1
- package/docs/reference/core-native-packaging.md +14 -0
- package/docs/reference/inventory/package-api.json +23 -9
- package/docs/reference/inventory/runtime-components.json +2 -1
- package/docs/reference/native-app-control.md +147 -0
- package/docs/reference/native-decisions.md +74 -16
- package/docs/reference/runtime-components.md +20 -0
- package/docs/reference/sdk-api.md +49 -6
- package/package.json +2 -1
- package/runtime/arcane/components/chat.html +140 -18
- package/runtime/arcane/modules/AI.js +10 -5
- package/src/cli/app-control.mjs +106 -0
- package/src/cli/main.mjs +5 -0
- package/src/core/app-control.mjs +23 -0
- package/src/core/hosts/windows/ArcaneAppControl.cs +525 -0
- package/src/core/hosts/windows/ArcaneCoreProcess.cs +2 -63
- package/src/core/hosts/windows/ArcaneFrameTransport.cs +80 -0
- package/src/core/hosts/windows/ArcaneHost.cs +45 -7
- package/src/core/hosts/windows/ArcaneLauncher.cs +5 -0
- package/src/core/hosts/windows/ArcaneWindowControl.cs +248 -0
- package/src/core/services/decisions.mjs +16 -2
- package/src/core/shared-host.mjs +7 -91
- package/src/core/socket-connection.mjs +109 -0
- package/src/local-ai/decision-tokenizer-worker.mjs +1 -1
- package/src/local-ai/decision-tokenizer.mjs +2 -2
- package/src/local-ai/decisions.mjs +33 -10
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs inside the existing app document. The native host serializes this named
|
|
3
|
+
* function with toString(), so all helpers must remain inside its lexical scope.
|
|
4
|
+
*
|
|
5
|
+
* inspect({selector?}) reads the top-level light DOM. Omission selects the whole
|
|
6
|
+
* document; a CSS selector may select any number of roots. HTML, textContent,
|
|
7
|
+
* attributes and live control state are returned completely. Frame documents
|
|
8
|
+
* and shadow-root contents are outside this CSS scope; frame elements and open
|
|
9
|
+
* shadow hosts are identified only. Closed shadow roots cannot be discovered.
|
|
10
|
+
* domRole/domName are DOM-derived hints, not an accessibility-tree computation.
|
|
11
|
+
*
|
|
12
|
+
* act({action,selector,...}) requires one current CSS match. fill uses an exact
|
|
13
|
+
* string value; select uses either value:string or values:string[]. scroll uses
|
|
14
|
+
* absolute left/top CSS-pixel offsets, preserving any omitted axis. Actions use
|
|
15
|
+
* DOM APIs and untrusted events without focus or desktop input. Results describe
|
|
16
|
+
* immediate DOM state, including any browser value sanitization or scroll
|
|
17
|
+
* clamping, not completion of asynchronous application work.
|
|
18
|
+
*
|
|
19
|
+
* The native host owns document-generation checks, scheduling and cancellation.
|
|
20
|
+
* This function retains no state, retries no action and returns a JSON envelope
|
|
21
|
+
* because ExecuteScriptAsync otherwise loses uncaught JavaScript exceptions.
|
|
22
|
+
*/
|
|
23
|
+
export function runAppControl(operation, params = {}) {
|
|
24
|
+
let actionAttempted = false;
|
|
25
|
+
const eventsDispatched = [];
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (params === null || typeof params !== 'object' || Array.isArray(params)) {
|
|
29
|
+
fail('INVALID_ARGUMENT', 'App-control parameters must be an object.');
|
|
30
|
+
}
|
|
31
|
+
if (operation === 'inspect') {
|
|
32
|
+
return {ok: true, result: inspectDocument()};
|
|
33
|
+
}
|
|
34
|
+
if (operation === 'act') {
|
|
35
|
+
return {ok: true, result: actOnElement()};
|
|
36
|
+
}
|
|
37
|
+
fail('INVALID_ARGUMENT', 'Expected the inspect or act operation.');
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
error: {
|
|
42
|
+
name: error?.name ?? 'Error',
|
|
43
|
+
message: error?.message ?? String(error),
|
|
44
|
+
stack: error?.stack ?? null,
|
|
45
|
+
code: error?.code ?? 'ARCANE_APP_CONTROL_FAILED',
|
|
46
|
+
details: error?.details ?? null,
|
|
47
|
+
actionAttempted,
|
|
48
|
+
eventsDispatched
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function fail(code, message, details = null) {
|
|
54
|
+
const error = new Error(message);
|
|
55
|
+
error.code = code;
|
|
56
|
+
error.details = details;
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function queryElements(selector) {
|
|
61
|
+
if (typeof selector !== 'string') {
|
|
62
|
+
fail('INVALID_ARGUMENT', 'selector must be a CSS selector string.');
|
|
63
|
+
}
|
|
64
|
+
return Array.from(document.querySelectorAll(selector));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function inspectDocument() {
|
|
68
|
+
const roots = params.selector === undefined
|
|
69
|
+
? (document.documentElement ? [document.documentElement] : [])
|
|
70
|
+
: queryElements(params.selector);
|
|
71
|
+
const rootSet = new Set(roots);
|
|
72
|
+
const elements = Array.from(document.querySelectorAll('*'));
|
|
73
|
+
const idCounts = new Map();
|
|
74
|
+
const childCounts = new Map();
|
|
75
|
+
const childIndexes = new Map();
|
|
76
|
+
const scope = new Set();
|
|
77
|
+
const selectors = new Map();
|
|
78
|
+
const controls = [];
|
|
79
|
+
const frames = [];
|
|
80
|
+
const shadowHosts = [];
|
|
81
|
+
|
|
82
|
+
// One traversal establishes selector paths and scope for all records.
|
|
83
|
+
for (const element of elements) {
|
|
84
|
+
if (element.id) {
|
|
85
|
+
idCounts.set(element.id, (idCounts.get(element.id) ?? 0) + 1);
|
|
86
|
+
}
|
|
87
|
+
const parent = element.parentElement;
|
|
88
|
+
const index = (childCounts.get(parent) ?? 0) + 1;
|
|
89
|
+
childCounts.set(parent, index);
|
|
90
|
+
childIndexes.set(element, index);
|
|
91
|
+
if (rootSet.has(element) || scope.has(parent)) {
|
|
92
|
+
scope.add(element);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const element of elements) {
|
|
97
|
+
if (!scope.has(element)) continue;
|
|
98
|
+
if (element.matches('input,textarea,select,button,option,optgroup,fieldset,output,progress,meter,details,summary,a[href],area[href],audio,video,[contenteditable],[role],[tabindex]')) {
|
|
99
|
+
controls.push(describeElement(element, uniqueSelector(element)));
|
|
100
|
+
}
|
|
101
|
+
if (element.localName === 'iframe' || element.localName === 'frame') {
|
|
102
|
+
frames.push(describeElement(element, uniqueSelector(element)));
|
|
103
|
+
}
|
|
104
|
+
if (element.shadowRoot) {
|
|
105
|
+
shadowHosts.push(
|
|
106
|
+
{
|
|
107
|
+
selector: uniqueSelector(element),
|
|
108
|
+
mode: element.shadowRoot.mode
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
scope: 'top-level-light-dom',
|
|
116
|
+
title: document.title,
|
|
117
|
+
readyState: document.readyState,
|
|
118
|
+
doctype: document.doctype
|
|
119
|
+
? new XMLSerializer().serializeToString(document.doctype)
|
|
120
|
+
: null,
|
|
121
|
+
roots: roots.map(describeRoot),
|
|
122
|
+
controls,
|
|
123
|
+
frames,
|
|
124
|
+
shadowHosts,
|
|
125
|
+
activeElement: document.activeElement
|
|
126
|
+
? describeElement(document.activeElement, uniqueSelector(document.activeElement))
|
|
127
|
+
: null,
|
|
128
|
+
scrollingElement: document.scrollingElement
|
|
129
|
+
? uniqueSelector(document.scrollingElement)
|
|
130
|
+
: null,
|
|
131
|
+
selection: {text: document.getSelection()?.toString() ?? ''}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
function describeRoot(element) {
|
|
135
|
+
return {
|
|
136
|
+
selector: uniqueSelector(element),
|
|
137
|
+
html: element.outerHTML,
|
|
138
|
+
text: element.textContent,
|
|
139
|
+
state: readState(element)
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function uniqueSelector(element) {
|
|
144
|
+
if (!element.isConnected || element.getRootNode() !== document) return null;
|
|
145
|
+
const path = [];
|
|
146
|
+
let current = element;
|
|
147
|
+
let prefix = '';
|
|
148
|
+
while (current) {
|
|
149
|
+
if (selectors.has(current)) {
|
|
150
|
+
prefix = selectors.get(current);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
if (current.id && !current.id.includes('\0') && idCounts.get(current.id) === 1) {
|
|
154
|
+
prefix = `#${CSS.escape(current.id)}`;
|
|
155
|
+
selectors.set(current, prefix);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
path.push(current);
|
|
159
|
+
current = current.parentElement;
|
|
160
|
+
}
|
|
161
|
+
for (let index = path.length - 1; index >= 0; index -= 1) {
|
|
162
|
+
const part = path[index];
|
|
163
|
+
const segment = `*:nth-child(${childIndexes.get(part)})`;
|
|
164
|
+
prefix = prefix ? `${prefix} > ${segment}` : ':root';
|
|
165
|
+
selectors.set(part, prefix);
|
|
166
|
+
}
|
|
167
|
+
return prefix;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function describeElement(element, selector) {
|
|
172
|
+
const attributeEntries = [];
|
|
173
|
+
for (const attribute of element.attributes) {
|
|
174
|
+
attributeEntries.push(
|
|
175
|
+
[attribute.name, attribute.value]
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
selector,
|
|
180
|
+
tagName: element.localName,
|
|
181
|
+
attributes: Object.fromEntries(attributeEntries),
|
|
182
|
+
domRole: element.getAttribute('role'),
|
|
183
|
+
domName: readDOMName(element),
|
|
184
|
+
state: readState(element)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function readDOMName(element) {
|
|
189
|
+
const labelledBy = element.getAttribute('aria-labelledby');
|
|
190
|
+
if (labelledBy) {
|
|
191
|
+
const labels = [];
|
|
192
|
+
for (const id of labelledBy.split(/\s+/u)) {
|
|
193
|
+
const label = document.getElementById(id);
|
|
194
|
+
if (label) labels.push(label.textContent);
|
|
195
|
+
}
|
|
196
|
+
if (labels.length) return {source: 'aria-labelledby', value: labels.join(' ')};
|
|
197
|
+
}
|
|
198
|
+
if (element.hasAttribute('aria-label')) {
|
|
199
|
+
return {source: 'aria-label', value: element.getAttribute('aria-label')};
|
|
200
|
+
}
|
|
201
|
+
if (element.labels?.length) {
|
|
202
|
+
const labels = [];
|
|
203
|
+
for (const label of element.labels) labels.push(label.textContent);
|
|
204
|
+
return {source: 'labels', value: labels.join(' ')};
|
|
205
|
+
}
|
|
206
|
+
if (element.hasAttribute('alt')) {
|
|
207
|
+
return {source: 'alt', value: element.getAttribute('alt')};
|
|
208
|
+
}
|
|
209
|
+
if (element.localName === 'input' && ['button', 'submit', 'reset'].includes(element.type)) {
|
|
210
|
+
return {source: 'value', value: element.value};
|
|
211
|
+
}
|
|
212
|
+
return {source: 'textContent', value: element.textContent};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function readState(element) {
|
|
216
|
+
const state = {
|
|
217
|
+
connected: element.isConnected,
|
|
218
|
+
contentEditable: element.isContentEditable === true,
|
|
219
|
+
effectivelyDisabled: element.matches(':disabled'),
|
|
220
|
+
scrollLeft: element.scrollLeft,
|
|
221
|
+
scrollTop: element.scrollTop,
|
|
222
|
+
scrollWidth: element.scrollWidth,
|
|
223
|
+
scrollHeight: element.scrollHeight,
|
|
224
|
+
clientWidth: element.clientWidth,
|
|
225
|
+
clientHeight: element.clientHeight
|
|
226
|
+
};
|
|
227
|
+
for (const field of [
|
|
228
|
+
'value', 'type', 'checked', 'indeterminate', 'disabled', 'readOnly',
|
|
229
|
+
'required', 'multiple', 'selected', 'selectedIndex', 'hidden', 'open',
|
|
230
|
+
'tabIndex', 'selectionStart', 'selectionEnd', 'selectionDirection',
|
|
231
|
+
'willValidate', 'validationMessage', 'paused', 'ended', 'currentTime',
|
|
232
|
+
'muted', 'volume', 'playbackRate'
|
|
233
|
+
]) {
|
|
234
|
+
const value = element[field];
|
|
235
|
+
if (value === null || ['string', 'boolean', 'number'].includes(typeof value)) {
|
|
236
|
+
state[field] = value;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (element.isContentEditable) state.editableText = element.textContent;
|
|
240
|
+
if (element.validity) {
|
|
241
|
+
state.validity = {};
|
|
242
|
+
for (const field of [
|
|
243
|
+
'badInput', 'customError', 'patternMismatch', 'rangeOverflow',
|
|
244
|
+
'rangeUnderflow', 'stepMismatch', 'tooLong', 'tooShort',
|
|
245
|
+
'typeMismatch', 'valid', 'valueMissing'
|
|
246
|
+
]) {
|
|
247
|
+
state.validity[field] = element.validity[field];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (element.localName === 'select') {
|
|
251
|
+
state.options = [];
|
|
252
|
+
for (const option of element.options) {
|
|
253
|
+
state.options.push(
|
|
254
|
+
{
|
|
255
|
+
value: option.value,
|
|
256
|
+
text: option.text,
|
|
257
|
+
label: option.label,
|
|
258
|
+
selected: option.selected,
|
|
259
|
+
disabled: option.disabled,
|
|
260
|
+
effectivelyDisabled: option.matches(':disabled')
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (element.localName === 'input' && element.type === 'file') {
|
|
266
|
+
state.files = [];
|
|
267
|
+
for (const file of element.files) {
|
|
268
|
+
state.files.push(
|
|
269
|
+
{name: file.name, type: file.type, lastModified: file.lastModified}
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return state;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function actOnElement() {
|
|
277
|
+
const targets = queryElements(params.selector);
|
|
278
|
+
if (targets.length !== 1) {
|
|
279
|
+
fail(
|
|
280
|
+
'ARCANE_APP_CONTROL_TARGET_COUNT',
|
|
281
|
+
'An action requires exactly one matching element.',
|
|
282
|
+
{matches: targets.length}
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
const target = targets[0];
|
|
286
|
+
const action = params.action;
|
|
287
|
+
if (!['click', 'fill', 'select', 'scroll'].includes(action)) {
|
|
288
|
+
fail('INVALID_ARGUMENT', 'Expected a click, fill, select or scroll action.');
|
|
289
|
+
}
|
|
290
|
+
if (action !== 'scroll' && target.matches(':disabled')) {
|
|
291
|
+
fail('ARCANE_APP_CONTROL_DISABLED', 'The selected control is disabled.');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let method;
|
|
295
|
+
if (action === 'click') {
|
|
296
|
+
if (typeof target.click !== 'function') {
|
|
297
|
+
fail('ARCANE_APP_CONTROL_UNSUPPORTED', 'The selected element does not provide a DOM click method.');
|
|
298
|
+
}
|
|
299
|
+
actionAttempted = true;
|
|
300
|
+
target.click();
|
|
301
|
+
method = 'click';
|
|
302
|
+
} else if (action === 'fill') {
|
|
303
|
+
fillElement(target);
|
|
304
|
+
method = 'fill';
|
|
305
|
+
} else if (action === 'select') {
|
|
306
|
+
selectOptions(target);
|
|
307
|
+
method = 'select';
|
|
308
|
+
} else {
|
|
309
|
+
if (params.left === undefined && params.top === undefined) {
|
|
310
|
+
fail('INVALID_ARGUMENT', 'scroll requires left, top or both absolute offsets.');
|
|
311
|
+
}
|
|
312
|
+
for (const field of ['left', 'top']) {
|
|
313
|
+
if (params[field] !== undefined && (typeof params[field] !== 'number' || !Number.isFinite(params[field]))) {
|
|
314
|
+
fail('INVALID_ARGUMENT', `${field} must be a finite number.`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
actionAttempted = true;
|
|
318
|
+
target.scrollTo(
|
|
319
|
+
{
|
|
320
|
+
left: params.left === undefined ? target.scrollLeft : params.left,
|
|
321
|
+
top: params.top === undefined ? target.scrollTop : params.top,
|
|
322
|
+
behavior: 'instant'
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
method = 'scrollTo';
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
action,
|
|
329
|
+
method,
|
|
330
|
+
invoked: true,
|
|
331
|
+
eventsDispatched,
|
|
332
|
+
target: describeElement(target, params.selector)
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function fillElement(target) {
|
|
337
|
+
if (typeof params.value !== 'string') {
|
|
338
|
+
fail('INVALID_ARGUMENT', 'fill requires an exact string value.');
|
|
339
|
+
}
|
|
340
|
+
if (target.readOnly) fail('ARCANE_APP_CONTROL_READ_ONLY', 'The selected control is read-only.');
|
|
341
|
+
let prototype;
|
|
342
|
+
if (target.localName === 'textarea') {
|
|
343
|
+
prototype = HTMLTextAreaElement.prototype;
|
|
344
|
+
} else if (target.localName === 'input') {
|
|
345
|
+
if (['file', 'checkbox', 'radio', 'button', 'submit', 'reset', 'image'].includes(target.type)) {
|
|
346
|
+
fail('ARCANE_APP_CONTROL_UNSUPPORTED', 'The selected input type does not support filling text.');
|
|
347
|
+
}
|
|
348
|
+
prototype = HTMLInputElement.prototype;
|
|
349
|
+
} else if (!target.isContentEditable) {
|
|
350
|
+
fail('ARCANE_APP_CONTROL_UNSUPPORTED', 'fill requires an input, textarea or contenteditable element.');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
actionAttempted = true;
|
|
354
|
+
if (prototype) {
|
|
355
|
+
Object.getOwnPropertyDescriptor(prototype, 'value').set.call(target, params.value);
|
|
356
|
+
} else {
|
|
357
|
+
Object.getOwnPropertyDescriptor(Node.prototype, 'textContent').set.call(target, params.value);
|
|
358
|
+
}
|
|
359
|
+
dispatchInputAndChange(target, params.value, true);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function selectOptions(target) {
|
|
363
|
+
if (target.localName !== 'select') {
|
|
364
|
+
fail('ARCANE_APP_CONTROL_UNSUPPORTED', 'select requires a native select element.');
|
|
365
|
+
}
|
|
366
|
+
if ((params.value === undefined) === (params.values === undefined)) {
|
|
367
|
+
fail('INVALID_ARGUMENT', 'select requires either value or values.');
|
|
368
|
+
}
|
|
369
|
+
const values = params.values === undefined ? [params.value] : params.values;
|
|
370
|
+
if (!Array.isArray(values)) fail('INVALID_ARGUMENT', 'values must be an array of exact strings.');
|
|
371
|
+
for (const value of values) {
|
|
372
|
+
if (typeof value !== 'string') fail('INVALID_ARGUMENT', 'Every selected value must be an exact string.');
|
|
373
|
+
}
|
|
374
|
+
if (!target.multiple && values.length > 1) {
|
|
375
|
+
fail('INVALID_ARGUMENT', 'A single-select control accepts at most one selected value.');
|
|
376
|
+
}
|
|
377
|
+
const optionsByValue = new Map();
|
|
378
|
+
for (const option of target.options) {
|
|
379
|
+
if (!optionsByValue.has(option.value)) {
|
|
380
|
+
optionsByValue.set(
|
|
381
|
+
option.value,
|
|
382
|
+
[]
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
optionsByValue.get(option.value).push(option);
|
|
386
|
+
}
|
|
387
|
+
const selectedOptions = new Set();
|
|
388
|
+
for (const value of values) {
|
|
389
|
+
const options = optionsByValue.get(value);
|
|
390
|
+
if (!options) {
|
|
391
|
+
fail(
|
|
392
|
+
'ARCANE_APP_CONTROL_OPTION_MISSING',
|
|
393
|
+
'No option has the requested value.',
|
|
394
|
+
{value}
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
for (const option of options) {
|
|
398
|
+
if (option.matches(':disabled')) {
|
|
399
|
+
fail(
|
|
400
|
+
'ARCANE_APP_CONTROL_DISABLED',
|
|
401
|
+
'A requested option is disabled.',
|
|
402
|
+
{value}
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
selectedOptions.add(option);
|
|
406
|
+
if (!target.multiple) break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
actionAttempted = true;
|
|
410
|
+
const setter = Object.getOwnPropertyDescriptor(HTMLOptionElement.prototype, 'selected').set;
|
|
411
|
+
for (const option of target.options) {
|
|
412
|
+
setter.call(option, selectedOptions.has(option));
|
|
413
|
+
}
|
|
414
|
+
if (selectedOptions.size === 0) {
|
|
415
|
+
Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'selectedIndex').set.call(target, -1);
|
|
416
|
+
}
|
|
417
|
+
dispatchInputAndChange(target, null, false);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function dispatchInputAndChange(target, data, textInput) {
|
|
421
|
+
const input = textInput
|
|
422
|
+
? new InputEvent(
|
|
423
|
+
'input',
|
|
424
|
+
{bubbles: true, composed: true, inputType: 'insertReplacementText', data}
|
|
425
|
+
)
|
|
426
|
+
: new Event(
|
|
427
|
+
'input',
|
|
428
|
+
{bubbles: true, composed: true}
|
|
429
|
+
);
|
|
430
|
+
target.dispatchEvent(input);
|
|
431
|
+
eventsDispatched.push('input');
|
|
432
|
+
const change = new Event(
|
|
433
|
+
'change',
|
|
434
|
+
{bubbles: true}
|
|
435
|
+
);
|
|
436
|
+
target.dispatchEvent(change);
|
|
437
|
+
eventsDispatched.push('change');
|
|
438
|
+
}
|
|
439
|
+
}
|
|
@@ -1116,6 +1116,17 @@ prepared playback without invoking `finishTTS()` or `streamTTS()`. Unmuted
|
|
|
1116
1116
|
completion keeps its ordinary speech flush. Explicit calls to either public
|
|
1117
1117
|
speech method retain their existing behavior.
|
|
1118
1118
|
|
|
1119
|
+
For a text-only request, pass `speech:false` to the object-form
|
|
1120
|
+
`ai.streamRequest({messages, speech:false, onChunk, signal})`. Its completion,
|
|
1121
|
+
failure, and cancellation leave buffered speech, prepared playback, and native
|
|
1122
|
+
playback alone. The option controls only the request's automatic speech
|
|
1123
|
+
finalization and stopping; it is not sent to the language provider and does not
|
|
1124
|
+
mute or unload TTS. Explicit speech calls made by your callbacks still run with
|
|
1125
|
+
their ordinary behavior. Omitted `speech` or `speech:true` preserves the
|
|
1126
|
+
automatic lifecycle above, and positional `streamMessage(...)` is unchanged.
|
|
1127
|
+
Explicit `streamTTS()`, `finishTTS()`, and global `stopAudio()` keep their existing
|
|
1128
|
+
scope.
|
|
1129
|
+
|
|
1119
1130
|
## Automatic speech-input formatting cleanup
|
|
1120
1131
|
|
|
1121
1132
|
The shared streaming, audio-file, and `SpeechPlayback` paths remove repeated
|
|
@@ -115,13 +115,17 @@ The controller emits `statechange` and `progress` through
|
|
|
115
115
|
current mutable status snapshot. Provider states are `unloaded`, `loading`, `ready`,
|
|
116
116
|
`unloading`, and `error`.
|
|
117
117
|
|
|
118
|
-
Browser-WASM model loads report `cache-check`, `download`, and `initialize`
|
|
119
|
-
phases.
|
|
118
|
+
Browser-WASM model loads report `cache-check`, `load`, `download`, and `initialize`
|
|
119
|
+
phases. Cache-read and download records preserve the ordered model-file fields `completed`,
|
|
120
120
|
`total`, and `unit:'files'`. Their ordered `members` records contain each file's
|
|
121
121
|
`name`, committed-part `completed`, `total`, and `unit:'shards'`. A streaming
|
|
122
122
|
response has `total:null` until its end is observed; completion then supplies
|
|
123
|
-
the actual committed-part count.
|
|
124
|
-
`
|
|
123
|
+
the actual committed-part count. Reading saved parts reports `phase:'load'`,
|
|
124
|
+
`cached:true`, and the known saved-part total before the first read and after
|
|
125
|
+
each part is opened. An existing HTTP Range plan likewise supplies its actual
|
|
126
|
+
shard total. Previously complete members identify `state:'cached'`; their
|
|
127
|
+
per-member records remain cache reads even if another member is downloading.
|
|
128
|
+
A fully cached activation emits no download phase. `activeTransfers` is the current transfer-worker count,
|
|
125
129
|
`transferLimit` is the selected concurrency, and `transferMode` identifies
|
|
126
130
|
`probing`, `files`, `ranges`, or `single`. No byte progress, rate, or estimated
|
|
127
131
|
completion time is produced. Closed-part and lifecycle changes publish promptly;
|
|
@@ -136,7 +140,7 @@ These messages do not change the runtime's log level or replace its complete
|
|
|
136
140
|
diagnostic output.
|
|
137
141
|
|
|
138
142
|
Initialization has `total:null` and omits `completed` and `unit`, so its progress
|
|
139
|
-
bar is indeterminate.
|
|
143
|
+
bar is indeterminate. Cached or downloaded file completion is not activation completion.
|
|
140
144
|
The metadata tensor count and GPU layer assignment are descriptive only:
|
|
141
145
|
upstream reports assigned layers before model-weight loading has finished.
|
|
142
146
|
The pinned runtime does not expose a measured overall initialization fraction
|
|
@@ -658,10 +662,13 @@ contains `kind`, `tableName`, `downloadConcurrency`, the original `adapter`,
|
|
|
658
662
|
and `ready`, `install`, `ensure`, `remove`, and `fetchResource`. `ensure()` preserves the complete
|
|
659
663
|
ordered model set and reports whether it was cached or installed.
|
|
660
664
|
`install(source,{signal,onProgress})` and
|
|
661
|
-
`ensure(source,{signal,onProgress,offline})` publish `cache-check
|
|
662
|
-
`download` records using ordered file counts and per-member closed-part progress
|
|
665
|
+
`ensure(source,{signal,onProgress,offline})` publish `cache-check`, cache-reading
|
|
666
|
+
`load`, and actual network `download` records using ordered file counts and per-member closed-part progress
|
|
663
667
|
when `onProgress` is supplied. Each member reports `completed`, `total`, and
|
|
664
|
-
`unit:'shards'`; streaming totals remain `null` until completion.
|
|
668
|
+
`unit:'shards'`; streaming totals remain `null` until completion. Cache reads
|
|
669
|
+
publish the known stored-part plan from zero through each opened part, with
|
|
670
|
+
`cached:true`; the existing Range plan supplies known totals during resume.
|
|
671
|
+
No model length is converted into product progress. Records retain
|
|
665
672
|
`activeTransfers`, `transferLimit`, and `transferMode`, with prompt part/lifecycle
|
|
666
673
|
updates and an existing 250 ms heartbeat. They contain no transfer rate or ETA.
|
|
667
674
|
Multi-file sources use up to that many concurrent member workers
|
|
@@ -693,8 +700,12 @@ loaders share this owner for actual configuration, tokenizer, graph,
|
|
|
693
700
|
external-data and support-file requests. Complete successful GET resources may
|
|
694
701
|
be reused. Cancellation joins reader/writer cleanup and preserves closed parts
|
|
695
702
|
for supported HTTP resume; a full response restarts that resource without
|
|
696
|
-
duplicating its saved prefix. Resource progress uses `unit:'shards'
|
|
697
|
-
`total:null` until completion
|
|
703
|
+
duplicating its saved prefix. Resource progress uses `unit:'shards'`. A network
|
|
704
|
+
response without a known shard plan retains `total:null` until completion;
|
|
705
|
+
opening a saved complete or partial resource reports `phase:'load'`,
|
|
706
|
+
`cached:true`, and the known number of stored parts before and during the read.
|
|
707
|
+
The resource URL accompanies every update. These counts describe resource
|
|
708
|
+
preparation, never engine activation. Independent resource requests remain concurrent.
|
|
698
709
|
|
|
699
710
|
### Availability and normalization
|
|
700
711
|
|
package/docs/reference/cli.md
CHANGED
|
@@ -9,6 +9,13 @@ work, emits progress or heartbeat records, observes cancellation where safe,
|
|
|
9
9
|
and exits nonzero on failure. Machine output is defined by
|
|
10
10
|
`arcane-cli-events/1`.
|
|
11
11
|
|
|
12
|
+
`app-control` is the dedicated native-window client described in
|
|
13
|
+
[native application control](native-app-control.md). It writes a running JSON
|
|
14
|
+
acknowledgement to stderr, then complete result JSON to stdout, or complete error
|
|
15
|
+
JSON to stderr and a nonzero exit. Capture writes its image to the explicit
|
|
16
|
+
`--output` filename and returns metadata. Its options are scoped to that command;
|
|
17
|
+
the general output-format parser below applies to the other commands.
|
|
18
|
+
|
|
12
19
|
## Command inventory
|
|
13
20
|
|
|
14
21
|
| Command | Scope and result |
|
|
@@ -31,6 +38,7 @@ and exits nonzero on failure. Machine output is defined by
|
|
|
31
38
|
| `arcane run` | Serves an existing browser release, or packages, plans, builds, and launches one paired native artifact. |
|
|
32
39
|
| `arcane update-check` | Performs one explicit, read-only npm dist-tag query for the installed SDK version. |
|
|
33
40
|
| `arcane targets` | Lists target ids, declared status, formats, architectures, signing profiles, methods, and pairing reason. |
|
|
41
|
+
| `arcane app-control status\|inspect\|capture\|act` | Connects to an explicit running native window endpoint for document inspection, viewport capture or targeted DOM actions. |
|
|
34
42
|
| `arcane repo status\|pull\|push` | Runs one selected repository operation for the current app workspace. |
|
|
35
43
|
| `arcane mail key set\|status\|delete` | Manages one server-only Resend API-key profile in `.arcane.env.json`. |
|
|
36
44
|
| `arcane mail send` | Performs one explicit, idempotency-keyed Resend attempt from a complete JSON report on redirected stdin. |
|
|
@@ -69,7 +77,7 @@ meaning and cardinality rules:
|
|
|
69
77
|
| `--allow-to` | optional comma-separated addresses | `mail serve` |
|
|
70
78
|
| `--report-key` | nonempty string | `mail send`; caller-owned stable Resend idempotency key, forwarded unchanged |
|
|
71
79
|
| `--request-timeout` | optional integer from 1 through 2147483647 milliseconds | `mail send`, `mail serve` |
|
|
72
|
-
| `--output` | `human`, `json`, `ndjson` |
|
|
80
|
+
| `--output` | `human`, `json`, `ndjson` | General toolchain invocations; the final occurrence wins. `app-control capture` instead uses its command-specific output filename. |
|
|
73
81
|
| `--git` | flag | `new` |
|
|
74
82
|
| `--skip-tests` | flag | `check --scope app` |
|
|
75
83
|
| `--dry-run` | flag | `package`; parser-supported on `build` with the boundary below |
|
|
@@ -592,7 +592,21 @@ before the window closes; complete engineering errors go to the supplied
|
|
|
592
592
|
diagnostic/error callbacks. Callbacks must not synchronously wait for the
|
|
593
593
|
window's lifetime promises.
|
|
594
594
|
|
|
595
|
+
A WebView2 `BrowserProcessExited` failure retains its complete diagnostic,
|
|
596
|
+
fails a pending `Ready`, and begins the existing Core drain and window close.
|
|
597
|
+
It does not wait for a user to close a blank window or invoke document callbacks
|
|
598
|
+
and script cleanup in the closed browser. The launcher reports the failure
|
|
599
|
+
after that drain. Main-renderer exits and Core-only transport failures retain
|
|
600
|
+
their existing handling. This terminal-flow correction does not identify or
|
|
601
|
+
prevent the underlying browser-process exit, restart the application, or change
|
|
602
|
+
its profile and saved data.
|
|
603
|
+
|
|
595
604
|
`ArcaneLauncher.cs` supplies the executable entry point for that composition.
|
|
605
|
+
An explicit `native.launchContext.appControl.endpoint` enables the current
|
|
606
|
+
Windows window's background document-control listener. The public client and
|
|
607
|
+
CLI inspect, capture and act on that existing WebView without selecting a desktop
|
|
608
|
+
window or changing its Core/profile. See [native application control](native-app-control.md)
|
|
609
|
+
for launch configuration, document generations, cancellation and platform scope.
|
|
596
610
|
`tools/build-core-windows-host.mjs` builds the selected SDK host output from the
|
|
597
611
|
approved WebView2 package, existing .NET Framework compiler and selected Node
|
|
598
612
|
SEA output. It retains complete compiler diagnostics and produces a fresh
|
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"sdkVersion": "0.
|
|
3
|
+
"sdkVersion": "0.81.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":
|
|
9
|
+
"memberCount": 302,
|
|
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-app-control:connectAppControl",
|
|
17
|
+
"name": "connectAppControl",
|
|
18
|
+
"displayName": "connectAppControl()",
|
|
19
|
+
"kind": "function",
|
|
20
|
+
"signature": "connectAppControl({endpoint,signal,onError}={})",
|
|
21
|
+
"entrypoints": ["arcane-os/core/app-control"],
|
|
22
|
+
"primaryImport": "arcane-os/core/app-control",
|
|
23
|
+
"group": "Native application control",
|
|
24
|
+
"summary": "Connects to one explicit running native window for background document inspection, viewport capture and targeted DOM actions.",
|
|
25
|
+
"availability": "Node local IPC on Windows, Linux and macOS; the current window adapter is Windows WebView2, with other native adapters unavailable",
|
|
26
|
+
"protocol": "arcane/1 app.control.status, app.control.inspect, app.control.capture and app.control.act over the existing framed local connection. The window host selects native.launchContext.appControl.endpoint and preserves its existing Core child, origin, profile and lifetime.",
|
|
27
|
+
"normalization": "Returns endpoint, closed, status(options), inspect(parameters={},options), capture(parameters={},options), act(parameters,options), and close(). Parameters pass unchanged; per-operation options preserve Core signal/timeout behavior. Status reports actual app/window/document readiness. Inspect returns complete selected top-level light-DOM HTML/text and live control state with DOM-derived role/name hints; frames and open shadow hosts are descriptors only. Capture returns mimeType image/png, complete base64 data, documentGeneration and URL for the rendered viewport. Act requires documentGeneration and one CSS selector with click, exact-string fill, exact-value select or absolute-offset scroll. The native document marker prevents queued actions crossing replacement; cancellation before dispatch prevents execution while dispatched work is observed through completion without retry or reversal. close() cancels only this connection, never the application or Core. No launch, automatic reconnect, desktop activation, global input or public arbitrary evaluator. See native-app-control.md for complete result fields, CLI and platform scope."
|
|
28
|
+
},
|
|
15
29
|
{
|
|
16
30
|
"id": "local-ai-execution-devices:createExecutionDeviceCatalog",
|
|
17
31
|
"name": "createExecutionDeviceCatalog",
|
|
@@ -45,14 +59,14 @@
|
|
|
45
59
|
"name": "createNativeDecisionModel",
|
|
46
60
|
"displayName": "createNativeDecisionModel()",
|
|
47
61
|
"kind": "function",
|
|
48
|
-
"signature": "createNativeDecisionModel({onnx,modelAssets,workingDirectory,paths,assetProjectionId,resourcePaths,model='onnx-community/laya-typed-decisions-ONNX',revision='main',dtype='fp32',sessionOptions,executionPreference='gpu',executionTarget,signal}={})",
|
|
62
|
+
"signature": "createNativeDecisionModel({onnx,modelAssets,workingDirectory,paths,assetProjectionId,resourcePaths,family='laya',model='onnx-community/laya-typed-decisions-ONNX',revision='main',dtype='fp32',sessionOptions,executionPreference='gpu',executionTarget,signal}={})",
|
|
49
63
|
"entrypoints": ["arcane-os/local-ai/decisions"],
|
|
50
64
|
"primaryImport": "arcane-os/local-ai/decisions",
|
|
51
65
|
"group": "Native typed decisions",
|
|
52
|
-
"summary": "Explicit native Laya FP32 option scoring through an existing ONNX owner and a tokenizer Worker.",
|
|
66
|
+
"summary": "Explicit native Laya FP32/FP16 or Julia FP32 option scoring through an existing ONNX owner and a tokenizer Worker.",
|
|
53
67
|
"availability": "Node on Windows/Linux/macOS with selected ONNX Runtime and model files; Android needs host adaptation",
|
|
54
|
-
"protocol": "Shared browser/native decision encoding with native named tensor results",
|
|
55
|
-
"normalization": "Returns load/evaluate/classify/current/status/subscribe/unload/dispose; preserves complete separate state/question/options/type and original rows, raw scores and every graph output. load({assetProjectionId,resourcePaths,executionTarget,signal}) retains omitted or undefined source, mapping and target fields from the latest selection, initially the factory configuration. A supplied ready projection takes precedence over configured paths and upstream settings; the existing modelAssets owner retains it directly without upstream preparation or fetching. resourcePaths maps model, tokenizer and tokenizerConfig to exact projection member.path values; complete companion files remain at their original relative paths. Missing projections or mapped members report their actual failure. assetProjectionId:null returns to configured paths or existing upstream preparation when no paths were configured. Separately, executionTarget:null requests automatic resolution, while {deviceId} forwards the saved physical selection to the existing ONNX owner. With no target selection, GPU preference still attempts advertised providers then CPU and explicit session providers retain precedence. Equal source, complete mapping and target selections share activation; a changed selection cancels the earlier request and joins its actual ONNX/tokenizer cleanup before replacement. Incoming projection use is retained before retiring the previous activation; caller preparation ownership is independent, and native files remain retained through actual ONNX and tokenizer Worker exit. Unload ends native use; loading again after all ownership is released requires a fresh projection. pendingActivation reports the requested replacement and projected source/mapping separately; current().execution remains the complete ONNX load record, with requested/resolved/configured/observed target diagnostics in current().execution.execution. Configuration alone does not prove physical execution. Cancellation retires the shared activation. No preference writes, chat history or duplicate inference engine."
|
|
68
|
+
"protocol": "Shared browser/native decision encoding with native named tensor results. Factory and each load accept family/model/revision/dtype; omission or undefined retains the selected value. Laya FP32 defaults remain. Laya FP16 selects onnx/model_fp16.onnx and onnx/model_fp16.onnx_data; Julia FP32 selects model.onnx and model.onnx.data, with its own tokenizer files. Complete new-family selection and matching files are caller-owned. All model fields participate in activation equality; top-level metadata stays attributable to the retiring activation while pendingActivation records its requested successor. Decoder preserves raw probabilities, every original row and every actual output without inventing Julia action logits.",
|
|
69
|
+
"normalization": "Returns load/evaluate/classify/current/status/subscribe/unload/dispose; preserves complete separate state/question/options/type and original rows, raw scores and every graph output. load({family,model,revision,dtype,assetProjectionId,resourcePaths,executionTarget,signal}) retains omitted or undefined family, model, revision, dtype, source, mapping and target fields from the latest selection, initially the factory configuration. A supplied ready projection takes precedence over configured paths and upstream settings; the existing modelAssets owner retains it directly without upstream preparation or fetching. resourcePaths maps model, tokenizer and tokenizerConfig to exact projection member.path values; complete companion files remain at their original relative paths. Missing projections or mapped members report their actual failure. assetProjectionId:null returns to configured paths or existing upstream preparation when no paths were configured. Separately, executionTarget:null requests automatic resolution, while {deviceId} forwards the saved physical selection to the existing ONNX owner. With no target selection, GPU preference still attempts advertised providers then CPU and explicit session providers retain precedence. Equal family, model, revision, dtype, source, complete mapping and target selections share activation; a changed selection cancels the earlier request and joins its actual ONNX/tokenizer cleanup before replacement. Incoming projection use is retained before retiring the previous activation; caller preparation ownership is independent, and native files remain retained through actual ONNX and tokenizer Worker exit. Unload ends native use; loading again after all ownership is released requires a fresh projection. pendingActivation reports the requested replacement and projected source/mapping separately; current().execution remains the complete ONNX load record, with requested/resolved/configured/observed target diagnostics in current().execution.execution. Configuration alone does not prove physical execution. Cancellation retires the shared activation. No preference writes, chat history or duplicate inference engine."
|
|
56
70
|
},
|
|
57
71
|
{
|
|
58
72
|
"id": "core-decisions:createNativeDecisionService",
|
|
@@ -63,10 +77,10 @@
|
|
|
63
77
|
"entrypoints": ["arcane-os/core/decisions"],
|
|
64
78
|
"primaryImport": "arcane-os/core/decisions",
|
|
65
79
|
"group": "Native typed decisions",
|
|
66
|
-
"summary": "Renderer-independent native Laya service reusing the registered local-AI and model-assets owners.",
|
|
80
|
+
"summary": "Renderer-independent native Laya/Julia service reusing the registered local-AI and model-assets owners.",
|
|
67
81
|
"availability": "Node Core with selected native ONNX and model files",
|
|
68
|
-
"protocol": "arcane/1 decisions.status/load/evaluate/unload and decisions.state",
|
|
69
|
-
"normalization": "Registration starts no model or tokenizer. Explicit load obtains dependency-ready same-process owners and reacquires getONNXRuntime after recovery. Factory configuration accepts assetProjectionId, resourcePaths and executionTarget; direct load({assetProjectionId,resourcePaths,executionTarget,signal}) and Core decisions.load({assetProjectionId,resourcePaths,executionTarget}) preserve omitted/undefined selections, with the Core request signal forwarded separately. resourcePaths maps model, tokenizer and tokenizerConfig to exact ready projection member.path values; complete companion members retain their relative paths. Supplied projections take precedence over configured paths/upstream settings, are retained through the existing model-assets owner, and never invoke upstream preparation or fetching; unavailable projections or mapped members fail directly. assetProjectionId:null returns to configured paths or existing upstream preparation, independently of executionTarget:null requesting automatic device selection. Equal source, complete mapping and target selections coalesce; changed selections cancel pending work and join the prior model's disposal before replacement. Incoming retained ownership protects projection files while the previous activation retires; caller preparation ownership remains independent, and native use retains files through actual ONNX/tokenizer exit. pendingActivation includes a projected source/mapping separately from the full activation execution record and its nested execution diagnostics. Native load/evaluate/classify/current/subscribe/unload/dispose preserve complete results; RPC uses the existing tensor codec. Unload has accepted service lifetime; host shutdown cancels request-lifetime load/evaluation."
|
|
82
|
+
"protocol": "arcane/1 decisions.status/load/evaluate/unload and decisions.state. Factory, direct load and decisions.load accept family/model/revision/dtype with the direct model's exact variant, omission and replacement semantics. The service persists across explicit model changes; top-level activation metadata and pendingActivation distinguish the retiring and requested selections.",
|
|
83
|
+
"normalization": "Registration starts no model or tokenizer. Explicit load obtains dependency-ready same-process owners and reacquires getONNXRuntime after recovery. Factory configuration accepts family, model, revision, dtype, assetProjectionId, resourcePaths and executionTarget; direct load({family,model,revision,dtype,assetProjectionId,resourcePaths,executionTarget,signal}) and Core decisions.load({family,model,revision,dtype,assetProjectionId,resourcePaths,executionTarget}) preserve omitted/undefined selections, with the Core request signal forwarded separately. resourcePaths maps model, tokenizer and tokenizerConfig to exact ready projection member.path values; complete companion members retain their relative paths. Supplied projections take precedence over configured paths/upstream settings, are retained through the existing model-assets owner, and never invoke upstream preparation or fetching; unavailable projections or mapped members fail directly. assetProjectionId:null returns to configured paths or existing upstream preparation, independently of executionTarget:null requesting automatic device selection. Equal family, model, revision, dtype, source, complete mapping and target selections coalesce; changed selections cancel pending work and join the prior model's disposal before replacement. Incoming retained ownership protects projection files while the previous activation retires; caller preparation ownership remains independent, and native use retains files through actual ONNX/tokenizer exit. pendingActivation includes a projected source/mapping separately from the full activation execution record and its nested execution diagnostics. Native load/evaluate/classify/current/subscribe/unload/dispose preserve complete results; RPC uses the existing tensor codec. Unload has accepted service lifetime; host shutdown cancels request-lifetime load/evaluation."
|
|
70
84
|
},
|
|
71
85
|
{
|
|
72
86
|
"id": "core-decisions:default",
|