arcane-os 0.86.0 → 0.87.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 +27 -0
- package/README.md +1 -1
- package/browser-runtime/core/contracts.mjs +1 -0
- package/browser-runtime/document-acquisition.mjs +112 -0
- package/docs/reference/document-acquisition.md +159 -0
- package/docs/reference/inventory/package-api.json +33 -5
- package/docs/reference/model-observation.md +7 -0
- package/docs/reference/sdk-api.md +42 -7
- package/package.json +4 -2
- package/src/core/services/document-acquisition.mjs +221 -0
- package/src/import-map.mjs +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.87.0
|
|
4
|
+
|
|
5
|
+
- Add `createDocumentAcquisitionService` through
|
|
6
|
+
`arcane-os/core/document-acquisition` and `acquireCoreDocument` through
|
|
7
|
+
`arcane-os/document-acquisition`. Applications can acquire selected HTTP(S)
|
|
8
|
+
documents through their existing native Core without depending on browser
|
|
9
|
+
cross-origin access.
|
|
10
|
+
- Preserve complete response bodies, requested and final URLs, response
|
|
11
|
+
metadata and ordered redirect evidence. An optional application-owned
|
|
12
|
+
destination predicate runs before each native request; domain selection and
|
|
13
|
+
document interpretation remain application policy.
|
|
14
|
+
- Correlate semantic progress and cancellation with the existing Core request,
|
|
15
|
+
preserve complete error evidence, and return browser bodies as `Blob`
|
|
16
|
+
objects. Native service disposal cancels and joins its owned operations.
|
|
17
|
+
No dependency or compiled native host change is required.
|
|
18
|
+
|
|
19
|
+
## 0.86.1
|
|
20
|
+
|
|
21
|
+
- Include the existing public `arcane-os/ai/model-observation` export in
|
|
22
|
+
generated browser import maps. Both managed application maps and standalone
|
|
23
|
+
document maps resolve the same published browser module, including direct
|
|
24
|
+
installed-package URLs and nested documents.
|
|
25
|
+
- Consumers adopt the published package and run their normal import-map
|
|
26
|
+
generation before rebuilding or serving their application. No manual map
|
|
27
|
+
edits, alternate imports or new native host build are required. The matching
|
|
28
|
+
Windows and Whisper runtime assets are unchanged from 0.86.0.
|
|
29
|
+
|
|
3
30
|
## 0.86.0
|
|
4
31
|
|
|
5
32
|
- Add fixed `Tab`, `Shift+Tab`, `Enter` and `Space` key operations to
|
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.87.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;
|
|
@@ -19,6 +19,7 @@ export const CORE_METHOD_CONTRACTS={
|
|
|
19
19
|
'capabilities.list':{input:'empty-object',output:'{app,grants,methods}'},
|
|
20
20
|
'platform.status':{input:'empty-object-v1',output:'platform-status-v1'},
|
|
21
21
|
'network.status':{input:'empty-object-v1',output:'network-status-v1',meaning:'A non-loopback interface is present; this is not proof of Internet reachability.'},
|
|
22
|
+
'documents.acquire':{input:'{url}',output:'document-acquisition-result-v1',meaning:'Complete HTTP(S) GET response and redirects; native application owns any optional destination predicate. Request-scoped documents.progress events.'},
|
|
22
23
|
'apps.list':{input:'empty-object-v1',output:'application-catalog-v1'},
|
|
23
24
|
'apps.launch':{input:'application-launch-v1',output:'application-launch-result-v1',meaning:'Host acceptance, not application readiness.'},
|
|
24
25
|
'external.open':{input:'external-open-v1',output:'external-open-result-v1',meaning:'Host acceptance, not confirmation of a user action.'},
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import Is from './dependencies/strong-type/index.js';
|
|
2
|
+
import {getInstalledCoreClient} from './core/client.mjs';
|
|
3
|
+
import {CoreError, serializeCoreError} from './core/contracts.mjs';
|
|
4
|
+
|
|
5
|
+
const is = new Is(false);
|
|
6
|
+
|
|
7
|
+
/** Acquires one complete document through the application's installed native service. */
|
|
8
|
+
export async function acquireCoreDocument({url, client = getInstalledCoreClient(), signal, onProgress} = {}) {
|
|
9
|
+
if (!is.function(client?.invoke) || !is.function(client?.events?.on)) {
|
|
10
|
+
throw new CoreError({
|
|
11
|
+
code: 'DOCUMENT_ACQUISITION_CORE_UNAVAILABLE',
|
|
12
|
+
message: 'Document acquisition requires an available native Core connection.'
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
if (!is.string(url) || url === '') {
|
|
16
|
+
throw new TypeError('Document acquisition url must be a nonempty string.');
|
|
17
|
+
}
|
|
18
|
+
if (onProgress !== undefined && !is.function(onProgress)) {
|
|
19
|
+
throw new TypeError('Document acquisition onProgress must be a function.');
|
|
20
|
+
}
|
|
21
|
+
const controller = new AbortController();
|
|
22
|
+
const operationSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
23
|
+
operationSignal.throwIfAborted();
|
|
24
|
+
const observers = new Set();
|
|
25
|
+
let requestId;
|
|
26
|
+
let active = true;
|
|
27
|
+
const observerErrors = [];
|
|
28
|
+
|
|
29
|
+
function retainObserverFailure(error) {
|
|
30
|
+
observerErrors.push(error);
|
|
31
|
+
controller.abort(error);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const unsubscribe = client.events.on('documents.progress', function documentProgress(progress) {
|
|
35
|
+
if (!active || operationSignal.aborted || !requestId || progress.requestId !== requestId || !onProgress) return;
|
|
36
|
+
let observation;
|
|
37
|
+
try { observation = Promise.resolve(onProgress(progress)); }
|
|
38
|
+
catch (error) { retainObserverFailure(error); return; }
|
|
39
|
+
observers.add(observation);
|
|
40
|
+
function observed() { observers.delete(observation); }
|
|
41
|
+
observation.then(observed, function failed(error) {
|
|
42
|
+
observed();
|
|
43
|
+
retainObserverFailure(error);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
let result;
|
|
48
|
+
let failed = false;
|
|
49
|
+
let failure;
|
|
50
|
+
try {
|
|
51
|
+
result = await client.invoke('documents.acquire', {url}, {
|
|
52
|
+
signal: operationSignal, timeoutMs: 0,
|
|
53
|
+
onRequest(request) { requestId = request.requestId; }
|
|
54
|
+
});
|
|
55
|
+
} catch (error) {
|
|
56
|
+
failed = true;
|
|
57
|
+
failure = error;
|
|
58
|
+
} finally {
|
|
59
|
+
active = false;
|
|
60
|
+
try { unsubscribe(); } catch (error) { observerErrors.push(error); }
|
|
61
|
+
await Promise.allSettled([...observers]);
|
|
62
|
+
}
|
|
63
|
+
if (failed) failure = decodeFailure(failure);
|
|
64
|
+
let document;
|
|
65
|
+
if (!failed) {
|
|
66
|
+
const {redirects, ...response} = result;
|
|
67
|
+
document = {...decodeResponse(response), redirects: redirects.map(decodeResponse)};
|
|
68
|
+
}
|
|
69
|
+
if (observerErrors.length) {
|
|
70
|
+
const error = !failed && observerErrors.length === 1
|
|
71
|
+
? new CoreError({...serializeCoreError(observerErrors[0]), cause: observerErrors[0]})
|
|
72
|
+
: new AggregateError(
|
|
73
|
+
failed ? [failure, ...observerErrors] : observerErrors,
|
|
74
|
+
'Document acquisition progress observers failed.',
|
|
75
|
+
failed ? {cause: failure} : undefined
|
|
76
|
+
);
|
|
77
|
+
if (document) {
|
|
78
|
+
const {requestedUrl, finalUrl, redirects, ...response} = document;
|
|
79
|
+
error.documentAcquisition = {requestedUrl, requestUrl: finalUrl, redirects, response};
|
|
80
|
+
} else if (failure?.documentAcquisition) {
|
|
81
|
+
error.documentAcquisition = failure.documentAcquisition;
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
if (failed) throw failure;
|
|
86
|
+
operationSignal.throwIfAborted();
|
|
87
|
+
return document;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function decodeFailure(error) {
|
|
91
|
+
if (!error?.documentAcquisition) return error;
|
|
92
|
+
const {response, redirects, ...metadata} = error.documentAcquisition;
|
|
93
|
+
return new CoreError({
|
|
94
|
+
...serializeCoreError(error),
|
|
95
|
+
documentAcquisition: {
|
|
96
|
+
...metadata,
|
|
97
|
+
redirects: redirects.map(decodeResponse),
|
|
98
|
+
...(response ? {response: decodeResponse(response)} : {})
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function decodeResponse(response) {
|
|
104
|
+
const {body, ...metadata} = response;
|
|
105
|
+
if (body.encoding !== 'base64') {
|
|
106
|
+
throw new TypeError(`Unsupported document transport encoding ${body.encoding}.`);
|
|
107
|
+
}
|
|
108
|
+
const content = Uint8Array.from(globalThis.atob(body.data), function decodeCharacter(character) {
|
|
109
|
+
return character.charCodeAt(0);
|
|
110
|
+
});
|
|
111
|
+
return {...metadata, body: new Blob([content], {type: metadata.mediaType ?? ''})};
|
|
112
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Native document acquisition
|
|
2
|
+
|
|
3
|
+
`acquireCoreDocument` from `arcane-os/document-acquisition` retrieves one
|
|
4
|
+
HTTP(S) document through the application's selected native Core service.
|
|
5
|
+
`createDocumentAcquisitionService` from `arcane-os/core/document-acquisition`
|
|
6
|
+
owns the reusable native acquisition. It performs a GET, follows redirects
|
|
7
|
+
manually, and retains the complete response bodies without parsing or rewriting
|
|
8
|
+
the document. Applications own source selection, document interpretation,
|
|
9
|
+
enumeration, storage, and any destination predicate.
|
|
10
|
+
|
|
11
|
+
## Application composition
|
|
12
|
+
|
|
13
|
+
Select an application-owned native factory through the existing
|
|
14
|
+
[`native.services` composition](core-native-packaging.md#explicit-application-services).
|
|
15
|
+
Include its source through the application's `package.nativeResources` as
|
|
16
|
+
described there. The factory runs in the native Core process; its options are
|
|
17
|
+
JSON data, while its predicate is an ordinary locally imported function.
|
|
18
|
+
Functions are never serialized through Core RPC.
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
// native/documents.mjs, owned by the application
|
|
22
|
+
import {createDocumentAcquisitionService} from 'arcane-os/core/document-acquisition';
|
|
23
|
+
|
|
24
|
+
export default function createDocuments() {
|
|
25
|
+
return createDocumentAcquisitionService();
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"native": {
|
|
32
|
+
"services": [{"module": "native/documents.mjs"}]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The ordinary service works without a predicate. When an application selects a
|
|
38
|
+
destination restriction, its native factory supplies
|
|
39
|
+
`createDocumentAcquisitionService({destinationPredicate})`. The SDK awaits
|
|
40
|
+
`destinationPredicate(destinationUrl, {requestedUrl, previousUrl, signal})`
|
|
41
|
+
before **every** HTTP(S) request, including the first URL and each redirect.
|
|
42
|
+
Return `true` to request that destination. Any other return value declines it
|
|
43
|
+
with `DOCUMENT_DESTINATION_DECLINED`. A thrown or rejected error remains the
|
|
44
|
+
acquisition error's original `cause`. The predicate receives the resolved URL
|
|
45
|
+
string, the caller's original URL string, the prior response URL or `null`, and
|
|
46
|
+
the operation's signal. It stays local to the application's native process.
|
|
47
|
+
|
|
48
|
+
An asynchronous predicate must finish or honor its signal so native shutdown
|
|
49
|
+
can join it. There is no automatic retry or redirect-count limit. The caller
|
|
50
|
+
can cancel its operation, including a repeating redirect chain. Redirects
|
|
51
|
+
with statuses 301, 302, 303, 307, and 308 follow their `Location`, resolved
|
|
52
|
+
against the response URL. A response without a redirect destination is returned
|
|
53
|
+
as received. The service performs GET on each destination; it does not submit
|
|
54
|
+
forms or add application credentials.
|
|
55
|
+
|
|
56
|
+
## Browser API
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
import {acquireCoreDocument} from 'arcane-os/document-acquisition';
|
|
60
|
+
|
|
61
|
+
const cancellation = new AbortController();
|
|
62
|
+
const document = await acquireCoreDocument({
|
|
63
|
+
url: selectedDocumentUrl,
|
|
64
|
+
signal: cancellation.signal,
|
|
65
|
+
onProgress(progress) {
|
|
66
|
+
status.textContent = progress.phase;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// The original entity content is available even for an HTTP error response.
|
|
71
|
+
const completeContent = document.body; // Blob
|
|
72
|
+
const receivedSuccessfully = document.ok;
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The options are `{url, client?, signal?, onProgress?}`. `client` defaults to the
|
|
76
|
+
installed Core client. The helper invokes `documents.acquire` with `{url}`;
|
|
77
|
+
callbacks and the signal stay local. It uses existing request cancellation and
|
|
78
|
+
`documents.progress` subscriptions, with no extra event bus or transport.
|
|
79
|
+
There is no implicit request timeout. The caller owns any selected deadline
|
|
80
|
+
through its signal.
|
|
81
|
+
|
|
82
|
+
The native service must be selected by the application. An absent Core client
|
|
83
|
+
reports `DOCUMENT_ACQUISITION_CORE_UNAVAILABLE`; an installed Core without the
|
|
84
|
+
service returns its existing unavailable-method error. Browser-only preview
|
|
85
|
+
does not acquire native capability and does not silently switch to browser
|
|
86
|
+
Fetch. Portable Windows, Linux, and macOS applications use their existing Core
|
|
87
|
+
transport; Android requires its host to provide this same native service.
|
|
88
|
+
|
|
89
|
+
The result contains:
|
|
90
|
+
|
|
91
|
+
| Field | Meaning |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `requestedUrl` | Caller-supplied URL string, retained unchanged. |
|
|
94
|
+
| `finalUrl` / `url` | Final response URL reported by Fetch. |
|
|
95
|
+
| `status`, `statusText`, `ok` | Actual Fetch response status fields. `ok:false` is returned with its body, not replaced by a generic error. |
|
|
96
|
+
| `headers` | All entries exposed by the Fetch `Headers` object, represented as `[name, value]` pairs. |
|
|
97
|
+
| `mediaType` | Complete `Content-Type` field value exposed by Fetch, or `null`. |
|
|
98
|
+
| `body` | Complete entity content as a `Blob` in the browser and a `Uint8Array` in native calls. |
|
|
99
|
+
| `complete` | `true` after the response body reaches its end. |
|
|
100
|
+
| `redirects` | Ordered preceding response records, each with `url`, status fields, headers, media type, body, and completion state. |
|
|
101
|
+
|
|
102
|
+
The content is the response entity supplied by Fetch, including Fetch's normal
|
|
103
|
+
content-decoding behavior. It is not a capture of compressed network framing.
|
|
104
|
+
Likewise, headers reflect Fetch's casing and combination behavior; the SDK does
|
|
105
|
+
not invent original wire casing or unavailable header distinctions. Document
|
|
106
|
+
content is never converted to text, normalized, summarized, or wrapped in
|
|
107
|
+
instructions. Query and progress metadata remain outside the content.
|
|
108
|
+
|
|
109
|
+
## Progress, cancellation, and errors
|
|
110
|
+
|
|
111
|
+
Progress records contain `phase`, `completed`, `total:1`, `unit:'documents'`,
|
|
112
|
+
`requestedUrl`, and the current `url`. Browser records additionally contain
|
|
113
|
+
the originating `requestId`. Phases are `accepted`, `destination`, `request`,
|
|
114
|
+
`response`, `redirect`, and `complete`. The completed count becomes one only
|
|
115
|
+
after the final response has been fully acquired, including an HTTP error
|
|
116
|
+
response. Status fields, not acquisition progress, establish HTTP success.
|
|
117
|
+
Progress does not measure content and carries no document body.
|
|
118
|
+
|
|
119
|
+
The browser helper filters request-correlated events and removes its listener
|
|
120
|
+
on every settlement. Synchronous or asynchronous progress callbacks are
|
|
121
|
+
observed; all accepted callback promises settle before the helper settles.
|
|
122
|
+
A callback failure cancels the request and remains observable. Simultaneous
|
|
123
|
+
operation and callback failures are retained in an `AggregateError`.
|
|
124
|
+
If a callback or subscription cleanup fails after the complete response has
|
|
125
|
+
arrived, that response remains in the error's `documentAcquisition` evidence;
|
|
126
|
+
the original callback failure remains its `cause` or an aggregated error.
|
|
127
|
+
Callbacks must finish or honor the application's cancellation signal.
|
|
128
|
+
|
|
129
|
+
Native acquisition errors retain the original thrown value as `cause` and
|
|
130
|
+
include `documentAcquisition: {requestedUrl, requestUrl, redirects, response?}`.
|
|
131
|
+
Every acquired redirect body is retained. If reading a response fails, its
|
|
132
|
+
available original content is retained with `complete:false`; that record is
|
|
133
|
+
explicitly incomplete. Cleanup failures remain observable alongside the
|
|
134
|
+
original error. On errors delivered through Core, the browser helper restores
|
|
135
|
+
the response and redirect bodies as Blobs and retains the native error record.
|
|
136
|
+
|
|
137
|
+
An abort uses the existing Core client's immediate local rejection and sends
|
|
138
|
+
request cancellation to the native owner. That local cancellation does not
|
|
139
|
+
wait for a native error record or return its response evidence. The native
|
|
140
|
+
owner cancels and joins its active response reader. Independent acquisitions
|
|
141
|
+
remain concurrent, and cancelling one request leaves sibling requests active.
|
|
142
|
+
Service `dispose()` aborts and joins all of that service's active operations.
|
|
143
|
+
|
|
144
|
+
## Native calls and transport
|
|
145
|
+
|
|
146
|
+
The synchronous factory accepts `{destinationPredicate?, signal?}` and returns
|
|
147
|
+
the existing Core service definition plus
|
|
148
|
+
`acquire({url, signal?, onProgress?})` and idempotent asynchronous `dispose()`.
|
|
149
|
+
Direct native results and error evidence contain `Uint8Array` bodies (Node
|
|
150
|
+
Buffers implement that interface). The optional factory signal controls the
|
|
151
|
+
service lifetime; a per-call signal controls only that acquisition.
|
|
152
|
+
|
|
153
|
+
At the JSON RPC boundary only, each body becomes
|
|
154
|
+
`{encoding:'base64', data:'...'}`. One final response carries the complete result
|
|
155
|
+
and ordered redirect records. The browser decodes those transport fields into
|
|
156
|
+
Blobs. This path buffers complete response bodies; phase events are not a
|
|
157
|
+
body stream and do not supply streaming backpressure. Existing Core lifecycle,
|
|
158
|
+
request routing, and connection-close cancellation retain ownership of the
|
|
159
|
+
operation through native settlement.
|
|
@@ -1,17 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"sdkVersion": "0.
|
|
3
|
+
"sdkVersion": "0.86.1",
|
|
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": 308,
|
|
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-document-acquisition:createDocumentAcquisitionService",
|
|
17
|
+
"name": "createDocumentAcquisitionService",
|
|
18
|
+
"displayName": "createDocumentAcquisitionService()",
|
|
19
|
+
"kind": "function",
|
|
20
|
+
"signature": "createDocumentAcquisitionService({destinationPredicate,signal}={})",
|
|
21
|
+
"entrypoints": ["arcane-os/core/document-acquisition"],
|
|
22
|
+
"primaryImport": "arcane-os/core/document-acquisition",
|
|
23
|
+
"group": "Native document acquisition",
|
|
24
|
+
"summary": "Acquires complete HTTP(S) documents in the native Core process with application-owned destination selection before every request.",
|
|
25
|
+
"availability": "Node Core on Windows, Linux and macOS; Android requires the same service through its host adaptation",
|
|
26
|
+
"protocol": "arcane/1 documents.acquire and request-correlated documents.progress; selected through the existing application-native service factory contract",
|
|
27
|
+
"normalization": "Returns name:documents, methods, acquire({url,signal,onProgress}) and idempotent asynchronous dispose(). GET follows 301/302/303/307/308 manually, awaiting the optional native destinationPredicate(url,{requestedUrl,previousUrl,signal}) before every request; only true proceeds. Omission leaves ordinary acquisition functional. Results retain requestedUrl, finalUrl/url, actual Fetch status/statusText/ok, all exposed header entries, mediaType, complete Uint8Array body and ordered complete redirect records, including non-2xx bodies. Fetch entity decoding is distinct from compressed wire framing. Errors retain their cause and complete available response evidence, with interrupted bodies marked complete:false. Independent jobs remain concurrent; cancellation stops the selected job and dispose aborts and joins all owned work. Progress is phase/document-count information, never content measurement. Base64 is used only at JSON transport; no parsing, text transformation, storage, automatic retry or application domain policy. See document-acquisition.md for composition, fields and lifecycle."
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "document-acquisition:acquireCoreDocument",
|
|
31
|
+
"name": "acquireCoreDocument",
|
|
32
|
+
"displayName": "acquireCoreDocument()",
|
|
33
|
+
"kind": "function",
|
|
34
|
+
"signature": "async acquireCoreDocument({url,client=getInstalledCoreClient(),signal,onProgress}={})",
|
|
35
|
+
"entrypoints": ["arcane-os/document-acquisition"],
|
|
36
|
+
"primaryImport": "arcane-os/document-acquisition",
|
|
37
|
+
"group": "Native document acquisition",
|
|
38
|
+
"summary": "Retrieves a complete selected document as a Blob through the application's existing native Core connection.",
|
|
39
|
+
"availability": "JavaScript with an existing Core client and explicitly composed documents service; no browser-only Fetch fallback",
|
|
40
|
+
"protocol": "documents.acquire({url}) with existing cancellation and correlated documents.progress subscriptions",
|
|
41
|
+
"normalization": "Preserves complete native response metadata and ordered redirect evidence, decoding only transport base64 into Blobs. The installed Core client is the default; unavailable Core reports DOCUMENT_ACQUISITION_CORE_UNAVAILABLE and a missing service uses the existing Core method error. The native application owns the optional destination predicate; no function crosses RPC. onProgress receives complete phase/document-count records plus requestId; accepted synchronous/asynchronous callbacks are observed and joined on settlement, listener cleanup always runs, and simultaneous operation/callback failures remain observable. The caller signal owns cancellation and any deadline; timeoutMs is disabled for the acquisition. Existing Core cancellation rejects locally and sends native cancellation without waiting for a native error evidence response. Complete bodies are buffered for one final RPC response; progress events are not streaming body backpressure. See document-acquisition.md for public usage and errors."
|
|
42
|
+
},
|
|
15
43
|
{
|
|
16
44
|
"id": "core-model-observation:createModelObservationService",
|
|
17
45
|
"name": "createModelObservationService",
|
|
@@ -63,10 +91,10 @@
|
|
|
63
91
|
"entrypoints": ["arcane-os/core/app-control"],
|
|
64
92
|
"primaryImport": "arcane-os/core/app-control",
|
|
65
93
|
"group": "Native application control",
|
|
66
|
-
"summary": "Connects to one explicit running native window for background document inspection, viewport capture
|
|
94
|
+
"summary": "Connects to one explicit running native window for background document inspection, viewport capture, targeted DOM actions, fixed keys and Normal-window client resizing.",
|
|
67
95
|
"availability": "Node local IPC on Windows, Linux and macOS; the current window adapter is Windows WebView2, with other native adapters unavailable",
|
|
68
|
-
"protocol": "arcane/1 app.control.status, app.control.inspect, app.control.capture and app.control.
|
|
69
|
-
"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 DOM HTML/text and live control state with DOM-derived role/name hints. Optional shadowPath on inspect and act is an ordered array of unique CSS host selectors through open shadow roots; selector resolves in the final root, with omitted/empty paths retaining top-level light DOM. Whole-shadow-root inspection returns complete innerHTML/textContent with null selector/state; nested shadow-host descriptors provide their full path. Frames remain descriptors and closed roots are unavailable. 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. CLI inspection accepts either --selector or a complete --request JSON object. See native-app-control.md for complete result fields, CLI and platform scope."
|
|
96
|
+
"protocol": "arcane/1 app.control.status, app.control.inspect, app.control.capture, app.control.act, app.control.key and app.control.resize over the existing framed local connection. The window host selects native.launchContext.appControl.endpoint and preserves its existing Core child, origin, profile and lifetime; assembly requires a matching published Windows host asset.",
|
|
97
|
+
"normalization": "Returns endpoint, closed, status(options), inspect(parameters={},options), capture(parameters={},options), act(parameters,options), key(parameters,options), resize(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 DOM HTML/text and live control state with DOM-derived role/name hints, plus top-level viewport CSS dimensions/devicePixelRatio, documentHasFocus and focusPath through the active open-shadow branch; focus records are observations with null selectors, separate from the selected root's activeElement. Optional shadowPath on inspect and act is an ordered array of unique CSS host selectors through open shadow roots; selector resolves in the final root, with omitted/empty paths retaining top-level light DOM. Whole-shadow-root inspection returns complete innerHTML/textContent with null selector/state; nested shadow-host descriptors provide their full path. Frames remain descriptors and closed roots are unavailable. 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. Key requires documentGeneration and Tab, Enter or Space; shiftKey:true is supported only for Tab. One keyboard owner keeps each press/release pair ordered at the selected WebView's current focus. An attempted press owns a matching release even after cancellation, navigation or failure; no failed or ambiguous press is replayed. Generation checks bracket native input without atomic document binding. Resize accepts positive integral native client width/height supported by WinForms and optional documentGeneration, changes only a Normal window's ClientSize, and returns immediate previous/actual native dimensions plus the later observed CSS viewport. Native and CSS snapshots are not atomic; platform constraints may change actual dimensions. Restoration is an explicit resize using the operation's previous native dimensions; unsupported window states fail before mutation. The native document marker prevents queued DOM actions crossing replacement; cancellation before dispatch prevents execution while dispatched work is observed through completion without retry or reversal, including owned key release. Partial results remain in actual errors and complete later responses remain in host diagnostics after cancellation or disconnection. Dispatch completion does not establish application handler completion or a successful business action. close() cancels only this connection, never the application or Core. No launch, automatic reconnect, desktop activation, global input or public arbitrary evaluator. CLI inspection accepts either --selector or a complete --request JSON object; act, key and resize forward their complete --request parameter objects. See native-app-control.md for complete result fields, CLI and platform scope."
|
|
70
98
|
},
|
|
71
99
|
{
|
|
72
100
|
"id": "local-ai-execution-devices:createExecutionDeviceCatalog",
|
|
@@ -45,6 +45,13 @@ retain their separate [app-control contract](native-app-control.md).
|
|
|
45
45
|
|
|
46
46
|
## Attach existing renderer owners
|
|
47
47
|
|
|
48
|
+
SDK 0.86.1 adds the public `arcane-os/ai/model-observation` export to normal
|
|
49
|
+
generated browser import maps. Development applications keep `arcane-os` on
|
|
50
|
+
`latest`; after updating through their existing dependency workflow, run
|
|
51
|
+
`arcane import-map` before building or serving. The mapping uses the same
|
|
52
|
+
published browser module and changes no observation runtime behavior. Regenerate
|
|
53
|
+
through the public command rather than editing map entries by hand.
|
|
54
|
+
|
|
48
55
|
```js
|
|
49
56
|
import * as aiRuntimeState from 'arcane-os/ai-runtime-state';
|
|
50
57
|
import {attachModelObservation} from 'arcane-os/ai/model-observation';
|
|
@@ -9076,7 +9076,7 @@ connectAppControl({endpoint,signal,onError}={})
|
|
|
9076
9076
|
```
|
|
9077
9077
|
|
|
9078
9078
|
Connects once to the explicit running native window and resolves
|
|
9079
|
-
`{endpoint,closed,status,inspect,capture,act,close}`. The Windows host selects its
|
|
9079
|
+
`{endpoint,closed,status,inspect,capture,act,key,resize,close}`. The Windows host selects its
|
|
9080
9080
|
own listener through `native.launchContext.appControl.endpoint` and preserves the
|
|
9081
9081
|
existing Core child, origin, profile and application lifetime. The client uses
|
|
9082
9082
|
portable Node local IPC; other native host adapters do not yet implement these
|
|
@@ -9088,14 +9088,49 @@ and no automatic reconnect or action retry occurs.
|
|
|
9088
9088
|
and live control state. Inspection and actions accept optional `shadowPath`, an
|
|
9089
9089
|
ordered array of unique CSS host selectors through open shadow roots; their
|
|
9090
9090
|
`selector` resolves inside the final root. Omission retains top-level light DOM.
|
|
9091
|
-
Closed roots remain unavailable.
|
|
9091
|
+
Closed roots remain unavailable. Inspection also returns top-level `viewport`
|
|
9092
|
+
CSS-pixel width/height and `devicePixelRatio`, `documentHasFocus`, and `focusPath`
|
|
9093
|
+
through the active open-shadow branch. These focus records have null selectors
|
|
9094
|
+
and remain separate from the selected root's `activeElement`; frames and closed
|
|
9095
|
+
roots end the path. `capture(parameters={},options)` returns a full base64 PNG
|
|
9092
9096
|
of the rendered viewport. `act(parameters,options)` requires a returned
|
|
9093
9097
|
`documentGeneration`, one CSS selector, and `click`, `fill`, `select` or `scroll`
|
|
9094
|
-
fields.
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9098
|
+
fields.
|
|
9099
|
+
|
|
9100
|
+
`key(parameters,options)` requires the observed `documentGeneration` and
|
|
9101
|
+
`key:'Tab'`, `'Enter'` or `'Space'`; `shiftKey:true` is supported only for Tab.
|
|
9102
|
+
It sends a fixed ordered press/release pair to the selected WebView's current
|
|
9103
|
+
focus, without selecting an element or activating the desktop window. Accepted
|
|
9104
|
+
pairs share one keyboard owner. Once a press is attempted, its matching release
|
|
9105
|
+
is attempted on the same WebView even after cancellation, navigation or failure;
|
|
9106
|
+
a failed or ambiguous press is never replayed. Results retain previous/actual
|
|
9107
|
+
document views and complete attempted/completed phase records. Generation checks
|
|
9108
|
+
bracket native input rather than atomically binding it to a document, so focus
|
|
9109
|
+
or navigation can change between phases.
|
|
9110
|
+
|
|
9111
|
+
`resize(parameters,options)` accepts positive integral native client `width`
|
|
9112
|
+
and `height` supported by WinForms, plus optional `documentGeneration`. It changes
|
|
9113
|
+
only `ClientSize` while the actual window is `Normal`; maximized or minimized
|
|
9114
|
+
windows fail before size mutation. Results retain immediate `previous`/`actual`
|
|
9115
|
+
native dimensions, requested dimensions, attempted/completed flags and a later
|
|
9116
|
+
observed CSS `viewport`. Platform constraints can change actual dimensions;
|
|
9117
|
+
native and CSS observations are not an atomic snapshot. Restore explicitly with
|
|
9118
|
+
the same operation's `previous.width` and `previous.height`, not CSS dimensions
|
|
9119
|
+
or a historical status snapshot. Cancellation does not restore the window.
|
|
9120
|
+
|
|
9121
|
+
Options preserve the Core client signal/timeout contract. Cancellation before
|
|
9122
|
+
dispatch prevents execution; dispatched work and owned key release remain
|
|
9123
|
+
observed through completion. Actual errors retain complete partial results;
|
|
9124
|
+
responses completed after cancellation or disconnection remain in host
|
|
9125
|
+
diagnostics. `close()` cancels this connection's requests and returns `closed`,
|
|
9126
|
+
without closing the app. Document changes and actual errors remain observable.
|
|
9127
|
+
Dispatch completion does not establish application handler completion or a
|
|
9128
|
+
successful business action; inspect the resulting built application to establish
|
|
9129
|
+
that outcome. Assembly requires a matching published Windows host asset.
|
|
9130
|
+
|
|
9131
|
+
The CLI provides `app-control key` and `app-control resize`, each forwarding the
|
|
9132
|
+
complete parsed `--request` object. Full parameter/result, error, cancellation,
|
|
9133
|
+
CLI and platform contracts are in [native application control](native-app-control.md).
|
|
9099
9134
|
|
|
9100
9135
|
## runSharedCoreHost()
|
|
9101
9136
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcane-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.87.0",
|
|
4
4
|
"description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"./entities/*": "./runtime/arcane/entities/*",
|
|
20
20
|
"./logging": "./browser-runtime/logging.mjs",
|
|
21
21
|
"./browser-device": "./browser-runtime/browser-device.mjs",
|
|
22
|
+
"./document-acquisition": "./browser-runtime/document-acquisition.mjs",
|
|
22
23
|
"./pwa": "./browser-runtime/pwa.mjs",
|
|
23
24
|
"./preference-store": "./runtime/arcane/modules/PreferenceStore.js",
|
|
24
25
|
"./ai": "./runtime/arcane/modules/AI.js",
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
"./core/repositories": "./src/core/repositories.mjs",
|
|
70
71
|
"./core/packaged-web": "./src/core/services/packaged-web.mjs",
|
|
71
72
|
"./core/preferences": "./src/core/services/preferences.mjs",
|
|
73
|
+
"./core/document-acquisition": "./src/core/services/document-acquisition.mjs",
|
|
72
74
|
"./core/diarization": "./src/diarization/service.mjs",
|
|
73
75
|
"./core/local-ai": "./src/core/services/local-ai.mjs",
|
|
74
76
|
"./core/execution-devices": "./src/core/services/execution-devices.mjs",
|
|
@@ -128,7 +130,7 @@
|
|
|
128
130
|
"test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
|
|
129
131
|
"test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
|
|
130
132
|
"test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/app-selection.test.mjs test/contracts.test.mjs test/conversation-closing-report.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/native-windows-icon.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
|
|
131
|
-
"test:functional": "node ./bin/arcane-test.mjs test/ai-ollama-readiness.test.mjs test/core-ollama-lifecycle.test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-decisions.test.mjs test/browser-kokoro-complete-input.test.mjs test/browser-speech-providers.test.mjs test/browser-speech-artifacts.test.mjs test/browser-speech-resource-bridge.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/codex-app-server.test.mjs test/mcp-stdio-server.test.mjs test/core-classic-source.test.mjs test/core-client.test.mjs test/core-client-installation.test.mjs test/core-ai-installation.test.mjs test/core-llama-lifecycle.test.mjs test/core-development.test.mjs test/core-host.test.mjs test/core-shared-host.test.mjs test/core-model-assets.test.mjs test/core-native-services.test.mjs test/core-packaged-web.test.mjs test/core-runtime.test.mjs test/core-stdio.test.mjs test/dbopfs-committed-events.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-model-resources.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/execution-devices.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/local-image-editing.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-media.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/model-resource-bridge.test.mjs test/native-decisions.test.mjs test/native-onnx.test.mjs test/native-module-packaging.test.mjs test/native-module-resource-references.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/rendered-print.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/theme-manager.test.mjs test/twin-cloud-images.test.mjs test/twin-cloud.test.mjs test/websocket-client-import-map.test.mjs test/websocket.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
|
|
133
|
+
"test:functional": "node ./bin/arcane-test.mjs test/ai-ollama-readiness.test.mjs test/core-ollama-lifecycle.test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-decisions.test.mjs test/browser-kokoro-complete-input.test.mjs test/browser-speech-providers.test.mjs test/browser-speech-artifacts.test.mjs test/browser-speech-resource-bridge.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/codex-app-server.test.mjs test/mcp-stdio-server.test.mjs test/core-classic-source.test.mjs test/core-client.test.mjs test/core-client-installation.test.mjs test/core-ai-installation.test.mjs test/core-llama-lifecycle.test.mjs test/core-development.test.mjs test/core-host.test.mjs test/core-shared-host.test.mjs test/core-model-assets.test.mjs test/core-native-services.test.mjs test/core-packaged-web.test.mjs test/core-runtime.test.mjs test/core-stdio.test.mjs test/dbopfs-committed-events.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-model-resources.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/document-acquisition.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/execution-devices.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/local-image-editing.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-media.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/model-resource-bridge.test.mjs test/native-decisions.test.mjs test/native-onnx.test.mjs test/native-module-packaging.test.mjs test/native-module-resource-references.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/rendered-print.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/theme-manager.test.mjs test/twin-cloud-images.test.mjs test/twin-cloud.test.mjs test/websocket-client-import-map.test.mjs test/websocket.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
|
|
132
134
|
"test:integration": "node ./bin/arcane-test.mjs test/installed-package-runtime.test.mjs test/root-app-layout.test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-macos-provider.test.mjs test/native-plan.test.mjs test/native-portable-provider.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/browser-wasm-cpu.test.mjs test/wllama-webgpu-runtime.test.mjs",
|
|
133
135
|
"test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/html-import-registration.test.mjs test/html-import-packaged-script.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/prepared-speech.test.mjs test/ai-audio-output.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
|
|
134
136
|
"check": "node tools/check-source.mjs && npm test",
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {Buffer} from 'node:buffer';
|
|
2
|
+
import Is from 'strong-type';
|
|
3
|
+
import {CoreError, serializeCoreError} from '../../../browser-runtime/core/contracts.mjs';
|
|
4
|
+
|
|
5
|
+
const is = new Is(false);
|
|
6
|
+
const redirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
7
|
+
|
|
8
|
+
/** The application supplies its optional destination decision in this native process. */
|
|
9
|
+
export function createDocumentAcquisitionService({destinationPredicate, signal} = {}) {
|
|
10
|
+
if (destinationPredicate !== undefined && !is.function(destinationPredicate)) {
|
|
11
|
+
throw new TypeError('Document acquisition destinationPredicate must be a function.');
|
|
12
|
+
}
|
|
13
|
+
const lifetime = new AbortController();
|
|
14
|
+
const lifetimeSignal = signal ? AbortSignal.any([signal, lifetime.signal]) : lifetime.signal;
|
|
15
|
+
const jobs = new Set();
|
|
16
|
+
let closing;
|
|
17
|
+
|
|
18
|
+
function acquire({url, signal: requestSignal, onProgress} = {}) {
|
|
19
|
+
if (!is.string(url) || url === '') {
|
|
20
|
+
throw new TypeError('Document acquisition url must be a nonempty string.');
|
|
21
|
+
}
|
|
22
|
+
if (onProgress !== undefined && !is.function(onProgress)) {
|
|
23
|
+
throw new TypeError('Document acquisition onProgress must be a function.');
|
|
24
|
+
}
|
|
25
|
+
const operationSignal = requestSignal
|
|
26
|
+
? AbortSignal.any([lifetimeSignal, requestSignal]) : lifetimeSignal;
|
|
27
|
+
const evidence = {requestedUrl: url, requestUrl: url, redirects: []};
|
|
28
|
+
|
|
29
|
+
async function progress(phase, completed = 0) {
|
|
30
|
+
operationSignal.throwIfAborted();
|
|
31
|
+
await onProgress?.({
|
|
32
|
+
phase, completed, total: 1, unit: 'documents',
|
|
33
|
+
requestedUrl: url, url: evidence.requestUrl
|
|
34
|
+
});
|
|
35
|
+
operationSignal.throwIfAborted();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const task = Promise.resolve().then(async function acquireDocument() {
|
|
39
|
+
try {
|
|
40
|
+
await progress('accepted');
|
|
41
|
+
let destination = new URL(url);
|
|
42
|
+
while (true) {
|
|
43
|
+
evidence.requestUrl = destination.href;
|
|
44
|
+
if (destination.protocol !== 'http:' && destination.protocol !== 'https:') {
|
|
45
|
+
throw new TypeError('Document acquisition requires an HTTP or HTTPS URL.');
|
|
46
|
+
}
|
|
47
|
+
await progress('destination');
|
|
48
|
+
if (destinationPredicate !== undefined) {
|
|
49
|
+
const accepted = await destinationPredicate(destination.href, {
|
|
50
|
+
requestedUrl: url,
|
|
51
|
+
previousUrl: evidence.redirects.at(-1)?.url ?? null,
|
|
52
|
+
signal: operationSignal
|
|
53
|
+
});
|
|
54
|
+
operationSignal.throwIfAborted();
|
|
55
|
+
if (accepted !== true) {
|
|
56
|
+
throw new CoreError({
|
|
57
|
+
code: 'DOCUMENT_DESTINATION_DECLINED',
|
|
58
|
+
message: `The application declined document destination ${destination.href}.`
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
await progress('request');
|
|
63
|
+
// Manual redirects keep the application decision ahead of every request.
|
|
64
|
+
const response = await fetch(destination.href, {
|
|
65
|
+
method: 'GET', redirect: 'manual', signal: operationSignal
|
|
66
|
+
});
|
|
67
|
+
const record = {
|
|
68
|
+
url: response.url,
|
|
69
|
+
status: response.status,
|
|
70
|
+
statusText: response.statusText,
|
|
71
|
+
ok: response.ok,
|
|
72
|
+
headers: Array.from(response.headers.entries()),
|
|
73
|
+
mediaType: response.headers.get('content-type'),
|
|
74
|
+
body: new Uint8Array(),
|
|
75
|
+
complete: false
|
|
76
|
+
};
|
|
77
|
+
evidence.response = record;
|
|
78
|
+
await readResponse(response, record, operationSignal, function reading() {
|
|
79
|
+
return progress('response');
|
|
80
|
+
});
|
|
81
|
+
const location = response.headers.get('location');
|
|
82
|
+
if (redirectStatuses.has(record.status) && location !== null) {
|
|
83
|
+
evidence.redirects.push(record);
|
|
84
|
+
delete evidence.response;
|
|
85
|
+
await progress('redirect');
|
|
86
|
+
destination = new URL(location, response.url || destination.href);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const result = {
|
|
90
|
+
requestedUrl: url, finalUrl: response.url,
|
|
91
|
+
...record, redirects: evidence.redirects
|
|
92
|
+
};
|
|
93
|
+
await progress('complete', 1);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
} catch (cause) {
|
|
97
|
+
// Retain the original error as cause, including complete native properties.
|
|
98
|
+
throw new CoreError({
|
|
99
|
+
...serializeCoreError(cause), cause,
|
|
100
|
+
documentAcquisition: evidence
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
jobs.add(task);
|
|
105
|
+
function settled() { jobs.delete(task); }
|
|
106
|
+
task.then(settled, settled);
|
|
107
|
+
return task;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function acquireThroughCore(parameters, request) {
|
|
111
|
+
try {
|
|
112
|
+
const result = await acquire({
|
|
113
|
+
url: parameters.url,
|
|
114
|
+
signal: request.signal,
|
|
115
|
+
onProgress(progress) {
|
|
116
|
+
request.emit('documents.progress', {
|
|
117
|
+
requestId: request.clientRequestId ?? request.requestId,
|
|
118
|
+
...progress
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return encodeResult(result);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (!error.documentAcquisition) throw error;
|
|
125
|
+
throw new CoreError({
|
|
126
|
+
...serializeCoreError(error),
|
|
127
|
+
documentAcquisition: encodeEvidence(error.documentAcquisition)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function dispose() {
|
|
133
|
+
closing ??= Promise.resolve().then(async function closeAcquisitionService() {
|
|
134
|
+
lifetime.abort(new CoreError({
|
|
135
|
+
code: 'DOCUMENT_ACQUISITION_CLOSED',
|
|
136
|
+
message: 'The document acquisition service is closing.'
|
|
137
|
+
}));
|
|
138
|
+
await Promise.allSettled([...jobs]);
|
|
139
|
+
});
|
|
140
|
+
return closing;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
name: 'documents',
|
|
145
|
+
methods: {'documents.acquire': acquireThroughCore},
|
|
146
|
+
acquire,
|
|
147
|
+
dispose
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function readResponse(response, record, signal, onReading) {
|
|
152
|
+
const chunks = [];
|
|
153
|
+
const cleanupErrors = [];
|
|
154
|
+
let reader;
|
|
155
|
+
let cancellation;
|
|
156
|
+
let failed = false;
|
|
157
|
+
let failure;
|
|
158
|
+
|
|
159
|
+
function cancelReader(reason) {
|
|
160
|
+
if (!reader || cancellation) return;
|
|
161
|
+
cancellation = Promise.resolve().then(function cancelResponseBody() {
|
|
162
|
+
return reader.cancel(reason);
|
|
163
|
+
}).catch(function retainCleanupError(error) { cleanupErrors.push(error); });
|
|
164
|
+
}
|
|
165
|
+
function aborted() { cancelReader(signal.reason); }
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
reader = response.body?.getReader();
|
|
169
|
+
signal.addEventListener('abort', aborted, {once: true});
|
|
170
|
+
if (signal.aborted) aborted();
|
|
171
|
+
await onReading();
|
|
172
|
+
while (reader) {
|
|
173
|
+
signal.throwIfAborted();
|
|
174
|
+
const {done, value} = await reader.read();
|
|
175
|
+
if (value !== undefined) chunks.push(Buffer.from(value));
|
|
176
|
+
if (done) {
|
|
177
|
+
signal.throwIfAborted();
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
signal.throwIfAborted();
|
|
182
|
+
record.complete = true;
|
|
183
|
+
} catch (error) {
|
|
184
|
+
failed = true;
|
|
185
|
+
failure = error;
|
|
186
|
+
cancelReader(error);
|
|
187
|
+
} finally {
|
|
188
|
+
signal.removeEventListener('abort', aborted);
|
|
189
|
+
if (cancellation) await cancellation;
|
|
190
|
+
try { reader?.releaseLock(); } catch (error) { cleanupErrors.push(error); }
|
|
191
|
+
record.body = Buffer.concat(chunks);
|
|
192
|
+
}
|
|
193
|
+
if (cleanupErrors.length) {
|
|
194
|
+
throw new AggregateError(
|
|
195
|
+
failed ? [failure, ...cleanupErrors] : cleanupErrors,
|
|
196
|
+
'Document response cleanup failed.',
|
|
197
|
+
failed ? {cause: failure} : undefined
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (failed) throw failure;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function encodeResponse(record) {
|
|
204
|
+
const {body, ...metadata} = record;
|
|
205
|
+
// Encoding belongs only to the existing JSON Core transport.
|
|
206
|
+
return {...metadata, body: {encoding: 'base64', data: Buffer.from(body).toString('base64')}};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function encodeResult(result) {
|
|
210
|
+
const {redirects, ...response} = result;
|
|
211
|
+
return {...encodeResponse(response), redirects: redirects.map(encodeResponse)};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function encodeEvidence(evidence) {
|
|
215
|
+
const {response, redirects, ...metadata} = evidence;
|
|
216
|
+
return {
|
|
217
|
+
...metadata,
|
|
218
|
+
redirects: redirects.map(encodeResponse),
|
|
219
|
+
...(response ? {response: encodeResponse(response)} : {})
|
|
220
|
+
};
|
|
221
|
+
}
|
package/src/import-map.mjs
CHANGED
|
@@ -44,6 +44,7 @@ const SDK_BROWSER_SELF_IMPORTS=new Map([
|
|
|
44
44
|
['arcane-os/websocket-client','sdk/websocket-client.mjs'],
|
|
45
45
|
['arcane-os/core/client','sdk/core/client.mjs'],
|
|
46
46
|
['arcane-os/core/contracts','sdk/core/contracts.mjs'],
|
|
47
|
+
['arcane-os/document-acquisition','sdk/document-acquisition.mjs'],
|
|
47
48
|
['arcane-os/logging','sdk/logging.mjs'],
|
|
48
49
|
['arcane-os/browser-device','sdk/browser-device.mjs'],
|
|
49
50
|
['arcane-os/pwa','sdk/pwa.mjs'],
|
|
@@ -53,6 +54,7 @@ const SDK_BROWSER_SELF_IMPORTS=new Map([
|
|
|
53
54
|
['arcane-os/ai/core-onnx','sdk/ai/core-onnx.mjs'],
|
|
54
55
|
['arcane-os/ai/core-image','sdk/ai/core-image.mjs'],
|
|
55
56
|
['arcane-os/ai/core-model-assets','sdk/ai/core-model-assets.mjs'],
|
|
57
|
+
['arcane-os/ai/model-observation','sdk/ai/model-observation.mjs'],
|
|
56
58
|
['arcane-os/ai/browser-decisions','sdk/ai/browser-decisions.mjs'],
|
|
57
59
|
['arcane-os/ai/tool-text-stream','sdk/ai/tool-text-stream.mjs'],
|
|
58
60
|
['arcane-os/ai/twin-cloud','sdk/ai/twin-cloud.mjs'],
|