arcane-os 0.57.0 → 0.59.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 +37 -0
- package/README.md +6 -2
- package/browser-runtime/ai/core-onnx.mjs +96 -0
- package/browser-runtime/ai/onnx-tensors.mjs +85 -0
- package/browser-runtime/core/client.mjs +152 -17
- package/docs/architecture.md +5 -2
- package/docs/platform-targets.md +30 -13
- package/docs/reference/README.md +22 -3
- package/docs/reference/availability-and-normalization.md +14 -7
- package/docs/reference/cli.md +22 -10
- package/docs/reference/core-client.md +87 -0
- package/docs/reference/core-native-packaging.md +129 -9
- package/docs/reference/core-preferences.md +49 -0
- package/docs/reference/diarization.md +206 -0
- package/docs/reference/git-text-snapshot.md +104 -0
- package/docs/reference/inventory/package-api.json +205 -23
- package/docs/reference/local-ai.md +145 -11
- package/docs/reference/protocols.md +28 -19
- package/docs/reference/pwa.md +74 -4
- package/docs/reference/sdk-api.md +589 -39
- package/package.json +11 -3
- package/schemas/arcane-app.schema.json +3 -3
- package/schemas/arcane-package.schema.json +2 -2
- package/schemas/native-build-plan.schema.json +27 -1
- package/schemas/target-adapter.schema.json +2 -0
- package/src/app-descriptor.mjs +1 -1
- package/src/cli/main.mjs +14 -4
- package/src/constants.mjs +2 -0
- package/src/core/host.mjs +14 -5
- package/src/core/hosts/macos/ArcaneHost.h +86 -0
- package/src/core/hosts/macos/ArcaneHost.m +455 -0
- package/src/core/hosts/macos/ArcaneLauncher.m +432 -0
- package/src/core/hosts/windows/ArcaneLauncher.cs +4 -1
- package/src/core/services/local-ai.mjs +82 -4
- package/src/core/services/packaged-web.mjs +89 -0
- package/src/core/services/preferences.mjs +201 -0
- package/src/core-classic-source.mjs +2 -2
- package/src/dev-server.mjs +2 -5
- package/src/diarization/build.mjs +35 -0
- package/src/diarization/index.mjs +432 -0
- package/src/diarization/native/CMakeLists.txt +9 -0
- package/src/diarization/native/main.cpp +269 -0
- package/src/diarization/service.mjs +129 -0
- package/src/git-text-snapshot.mjs +265 -0
- package/src/import-map.mjs +9 -4
- package/src/index.mjs +1 -0
- package/src/local-ai/config.mjs +1 -1
- package/src/local-ai/install.mjs +134 -19
- package/src/local-ai/onnx-worker.mjs +85 -0
- package/src/local-ai/onnx.mjs +368 -0
- package/src/native/macos-provider.mjs +291 -0
- package/src/native/portable-layout.mjs +10 -2
- package/src/native/portable-provider.mjs +10 -5
- package/src/native-plan.mjs +9 -1
- package/src/native-provider-loader.mjs +3 -1
- package/src/process.mjs +123 -17
- package/src/scaffold.mjs +1 -1
- package/src/targets/index.mjs +11 -1
- package/src/templates/workspace-template.mjs +6 -5
- package/src/toolchain.mjs +42 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.59.0
|
|
4
|
+
|
|
5
|
+
- Reconcile marked generated PWA references when a root application's PWA is
|
|
6
|
+
disabled. Add explicit `importMap({retireGeneratedPwa:true})` retirement for
|
|
7
|
+
the caller-confirmed generated root manifest reference and four standard PWA
|
|
8
|
+
output files. Ordinary disabled refresh preserves unmarked references and
|
|
9
|
+
files; authored content, unselected pages and browser storage remain intact.
|
|
10
|
+
- Forward the native host's selected state directory to Core service factories
|
|
11
|
+
as a launch-context default. Every explicit launch JSON field takes precedence
|
|
12
|
+
unchanged. Windows launcher source now supplies the same separate argument;
|
|
13
|
+
using that launcher correction requires the matching rebuilt host archive.
|
|
14
|
+
- Forward optional `Arcane.speech.synthesize(request, {signal})` cancellation
|
|
15
|
+
through the existing request lifetime, preserving complete payloads and the
|
|
16
|
+
180,000 ms default. Actual engine interruption remains service-owned; no new
|
|
17
|
+
speech engine, runtime dependency or data migration is introduced.
|
|
18
|
+
|
|
19
|
+
## 0.58.0
|
|
20
|
+
|
|
21
|
+
- Add native ONNX sessions through `arcane-os/local-ai/onnx` and browser Core
|
|
22
|
+
access through `arcane-os/ai/core-onnx`. Explicit runtime selection uses the
|
|
23
|
+
upstream Node runtime distribution, retained per-session workers, complete
|
|
24
|
+
tensor transport, authoritative loaded state and owned cancellation/exit.
|
|
25
|
+
Apps select their graphs, feeds and processing; no model is chosen or fetched.
|
|
26
|
+
- Add native Nemotron diarization runtime preparation, helper build and Core
|
|
27
|
+
integration. Preserve complete framed input/output, independent streams,
|
|
28
|
+
cancellation and process drain. Runtime/model selection remains explicit.
|
|
29
|
+
- Add the reusable Core preferences service and Git text snapshots, preserving
|
|
30
|
+
complete caller data and existing application-owned storage and repository
|
|
31
|
+
choices. Extend raw process I/O without changing existing UTF-8 defaults.
|
|
32
|
+
- Add shared WebKit document lifecycle and macOS native host composition source,
|
|
33
|
+
portable target contracts, and explicit packaged HTTP serving through the
|
|
34
|
+
existing HTTP server. Default HTTPS and ordinary browser behavior remain.
|
|
35
|
+
macOS executable build and platform acceptance remain separately pending.
|
|
36
|
+
- No automatic runtime/model installation, application default switch or saved
|
|
37
|
+
data migration is introduced. Native host archive delivery is separate from
|
|
38
|
+
npm publication; this release does not claim an available Windows/Mac archive.
|
|
39
|
+
|
|
3
40
|
## 0.57.0
|
|
4
41
|
|
|
5
42
|
- Add local model chat through explicit llama.cpp/Ollama runtime requirements,
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
|
|
|
20
20
|
share the theme, packaging, event, cancellation,
|
|
21
21
|
and browser run contracts while retaining their selected application layout.
|
|
22
22
|
|
|
23
|
-
This checkout defines the `0.
|
|
23
|
+
This checkout defines the `0.59.0` SDK contract. Development applications declare
|
|
24
24
|
`arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
|
|
25
25
|
designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
|
|
26
26
|
to refresh and compare the installed SDK and lockfile with the published channel;
|
|
@@ -131,7 +131,7 @@ each device. Keep these local files ignored by Git. The same certificate pair
|
|
|
131
131
|
works for any selected app in that workspace. The command reports missing TLS
|
|
132
132
|
files instead of starting an HTTP listener.
|
|
133
133
|
|
|
134
|
-
HTTPS is the default for development and
|
|
134
|
+
HTTPS is the default for development and packaged browser previews.
|
|
135
135
|
Plain `npm run dev` binds to localhost with the same workspace certificate pair.
|
|
136
136
|
An explicit `--host` overrides the bind address, and `--port` selects the HTTPS port.
|
|
137
137
|
The paired HTTP listener returns 308 redirects and uses an OS-assigned port
|
|
@@ -152,6 +152,10 @@ This selects one HTTP content listener and prints HTTP network URLs. It reads
|
|
|
152
152
|
no certificate pair and serves the same application and generated PWA routes.
|
|
153
153
|
PWA and storage availability still depend on the browser's secure-context
|
|
154
154
|
rules; see [HTTP development](docs/reference/cli.md#explicit-http-development).
|
|
155
|
+
API callers may also explicitly select `http:true` with `mode:'packaged'` to
|
|
156
|
+
serve the selected packaged web root through the same listener owner. See
|
|
157
|
+
[`startDevServer()`](docs/reference/sdk-api.md#startdevserver) for readiness,
|
|
158
|
+
start-URL and shutdown behavior.
|
|
155
159
|
|
|
156
160
|
Open the URL printed by the server. The generated page owns its import map and
|
|
157
161
|
Arcane theme; its application module is `modules/App.js` at the repository root.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {getInstalledCoreClient} from '../core/client.mjs';
|
|
2
|
+
import {createArcaneEventSource} from '../event-manager.mjs';
|
|
3
|
+
import {encodeTensorMap, decodeTensorMap, encodeTensorFetches} from './onnx-tensors.mjs';
|
|
4
|
+
|
|
5
|
+
function unavailable(message) {
|
|
6
|
+
const error = new Error(message);
|
|
7
|
+
error.code = 'ARCANE_ONNX_CORE_UNAVAILABLE';
|
|
8
|
+
return error;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Optional system ONNX access. Construction neither connects nor installs. */
|
|
12
|
+
export function createCoreONNXRuntime({client = getInstalledCoreClient()} = {}) {
|
|
13
|
+
const owner = {};
|
|
14
|
+
const events = createArcaneEventSource(owner, {source: 'core-onnx', eventTypes: ['onnx.state']});
|
|
15
|
+
const lifetime = new AbortController();
|
|
16
|
+
const pending = new Set();
|
|
17
|
+
let state = {id: 'onnx', installed: false, available: false, state: 'unknown', models: []};
|
|
18
|
+
let revision = 0;
|
|
19
|
+
let closed = false;
|
|
20
|
+
|
|
21
|
+
function current() { return {...state, busy: pending.size > 0, closed}; }
|
|
22
|
+
function publish() { events.dispatch('onnx.state', current()); }
|
|
23
|
+
function accept(value) {
|
|
24
|
+
state = value ?? {id: 'onnx', installed: false, available: false, state: 'unavailable', models: []};
|
|
25
|
+
revision += 1;
|
|
26
|
+
publish();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const unsubscribe = client?.events.on('localai.state', function localStateChanged(snapshot) {
|
|
30
|
+
if (!closed) accept(snapshot?.runtimes?.find(function onnxRuntime(runtime) { return runtime.id === 'onnx'; }));
|
|
31
|
+
}) ?? function noCoreSubscription() {};
|
|
32
|
+
|
|
33
|
+
async function invoke(method, parameters, {signal, timeoutMs = 0} = {}) {
|
|
34
|
+
if (closed) throw unavailable('The Core ONNX accessor is closed.');
|
|
35
|
+
if (!client) throw unavailable('Local ONNX requires an available Core connection.');
|
|
36
|
+
const request = {};
|
|
37
|
+
const operationSignal = signal ? AbortSignal.any([signal, lifetime.signal]) : lifetime.signal;
|
|
38
|
+
operationSignal.throwIfAborted();
|
|
39
|
+
pending.add(request);
|
|
40
|
+
publish();
|
|
41
|
+
try {
|
|
42
|
+
return await client.invoke(method, parameters, {signal: operationSignal, timeoutMs});
|
|
43
|
+
} finally {
|
|
44
|
+
pending.delete(request);
|
|
45
|
+
if (!closed) publish();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function inspect(options) {
|
|
50
|
+
const before = revision;
|
|
51
|
+
try {
|
|
52
|
+
const result = await invoke('onnx.status', {}, options);
|
|
53
|
+
if (!closed && revision === before) accept(result);
|
|
54
|
+
return current();
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (!closed && !options?.signal?.aborted && revision === before) {
|
|
57
|
+
accept({...state, available: false, state: 'unavailable', error});
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function load({id, model, sessionOptions, signal, timeoutMs} = {}) {
|
|
64
|
+
return invoke('onnx.load', {id, model, sessionOptions}, {signal, timeoutMs});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function run({id, feeds, fetches, runOptions, signal, timeoutMs} = {}) {
|
|
68
|
+
const result = await invoke('onnx.run', {
|
|
69
|
+
id, feeds: encodeTensorMap(feeds),
|
|
70
|
+
fetches: encodeTensorFetches(fetches), runOptions
|
|
71
|
+
}, {signal, timeoutMs});
|
|
72
|
+
return decodeTensorMap(result);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function unload({id, signal, timeoutMs} = {}) {
|
|
76
|
+
return invoke('onnx.unload', {id}, {signal, timeoutMs});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function subscribe(listener) {
|
|
80
|
+
const stop = events.on('onnx.state', function changed(event) { listener(event.detail); });
|
|
81
|
+
try { listener(current()); } catch (error) { stop(); throw error; }
|
|
82
|
+
return stop;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function close() {
|
|
86
|
+
if (closed) return;
|
|
87
|
+
closed = true;
|
|
88
|
+
lifetime.abort();
|
|
89
|
+
unsubscribe();
|
|
90
|
+
state = {...state, available: false, state: 'closed'};
|
|
91
|
+
publish();
|
|
92
|
+
events.dispose();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {load, run, unload, inspect, current, subscribe, close};
|
|
96
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import Is from '../dependencies/strong-type/index.js';
|
|
2
|
+
|
|
3
|
+
const is = new Is(false);
|
|
4
|
+
const typedArrays = {
|
|
5
|
+
Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array,
|
|
6
|
+
Int32Array, Uint32Array, Float32Array, Float64Array,
|
|
7
|
+
BigInt64Array, BigUint64Array, Float16Array: globalThis.Float16Array
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** Tensor framing belongs at JSON transport boundaries, outside inference. */
|
|
11
|
+
export function encodeTensorMap(tensors) {
|
|
12
|
+
if (tensors === undefined || tensors === null) return tensors;
|
|
13
|
+
return Object.fromEntries(Object.entries(tensors).map(function encodeEntry([name, tensor]) {
|
|
14
|
+
return [name, tensor === null ? null : {
|
|
15
|
+
type: tensor.type, dims: tensor.dims, data: encodeTensorData(tensor.data)
|
|
16
|
+
}];
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function decodeTensorMap(tensors) {
|
|
21
|
+
if (tensors === undefined || tensors === null) return tensors;
|
|
22
|
+
return Object.fromEntries(Object.entries(tensors).map(function decodeEntry([name, tensor]) {
|
|
23
|
+
return [name, tensor === null ? null : {
|
|
24
|
+
type: tensor.type, dims: tensor.dims, data: decodeTensorData(tensor.data)
|
|
25
|
+
}];
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function encodeTensorFetches(fetches) {
|
|
30
|
+
return is.array(fetches) ? fetches : encodeTensorMap(fetches);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function decodeTensorFetches(fetches) {
|
|
34
|
+
return is.array(fetches) ? fetches : decodeTensorMap(fetches);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function encodeTensorData(data) {
|
|
38
|
+
if (is.typedArray(data)) {
|
|
39
|
+
const arrayType = Object.prototype.toString.call(data).slice(8, -1);
|
|
40
|
+
const content = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
41
|
+
return {encoding: 'base64', arrayType, content: encodeBase64(content)};
|
|
42
|
+
}
|
|
43
|
+
if (is.array(data)) return data.map(encodeScalar);
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function decodeTensorData(data) {
|
|
48
|
+
if (data?.encoding === 'base64') {
|
|
49
|
+
const Constructor = typedArrays[data.arrayType];
|
|
50
|
+
if (!is.function(Constructor)) {
|
|
51
|
+
throw new TypeError(`Tensor data representation ${data.arrayType} is unavailable in this runtime.`);
|
|
52
|
+
}
|
|
53
|
+
const content = decodeBase64(data.content);
|
|
54
|
+
return new Constructor(content.buffer);
|
|
55
|
+
}
|
|
56
|
+
if (is.array(data)) return data.map(decodeScalar);
|
|
57
|
+
return data;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function encodeScalar(value) {
|
|
61
|
+
if (is.bigint(value)) return {encoding: 'bigint', value: String(value)};
|
|
62
|
+
if (is.negativeZero(value)) return {encoding: 'number', value: '-0'};
|
|
63
|
+
if (is.number(value) && !is.finite(value)) return {encoding: 'number', value: String(value)};
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function decodeScalar(value) {
|
|
68
|
+
if (value?.encoding === 'bigint') return BigInt(value.value);
|
|
69
|
+
if (value?.encoding === 'number') return Number(value.value);
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function encodeBase64(content) {
|
|
74
|
+
if (is.function(content.toBase64)) return content.toBase64();
|
|
75
|
+
let binary = '';
|
|
76
|
+
for (const value of content) binary += String.fromCharCode(value);
|
|
77
|
+
return btoa(binary);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function decodeBase64(content) {
|
|
81
|
+
if (is.function(Uint8Array.fromBase64)) return Uint8Array.fromBase64(content);
|
|
82
|
+
return Uint8Array.from(atob(content), function characterCode(character) {
|
|
83
|
+
return character.charCodeAt(0);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -14,6 +14,7 @@ export function createCoreClient({
|
|
|
14
14
|
eventOwnerReady,
|
|
15
15
|
autoConnect=true,
|
|
16
16
|
replayRuntimeState=false,
|
|
17
|
+
webKitDocumentLifecycle=false,
|
|
17
18
|
onError=error=>global.console?.error('Arcane Core client failed.',error)
|
|
18
19
|
}={}){
|
|
19
20
|
const pending=new Map();
|
|
@@ -29,6 +30,7 @@ export function createCoreClient({
|
|
|
29
30
|
let eventOwnerFailure=null;
|
|
30
31
|
let transportFailure=null;
|
|
31
32
|
let transportFailureReported=false;
|
|
33
|
+
let webKitDocument=null;
|
|
32
34
|
|
|
33
35
|
function report(error){
|
|
34
36
|
if(error===transportFailure){
|
|
@@ -67,8 +69,13 @@ export function createCoreClient({
|
|
|
67
69
|
report(eventOwnerFailure);
|
|
68
70
|
});
|
|
69
71
|
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
+
function acceptsNativeDelivery(activation){
|
|
73
|
+
return !closed&&(!webKitDocument||webKitDocument.current(activation));
|
|
74
|
+
}
|
|
75
|
+
function emit(event,data,activation=webKitDocument?.activation){
|
|
76
|
+
withEvents(function deliverEvent(){
|
|
77
|
+
if(acceptsNativeDelivery(activation))source.dispatch(CORE_EVENT,{event,data});
|
|
78
|
+
});
|
|
72
79
|
}
|
|
73
80
|
function complete(event,data){
|
|
74
81
|
if(completedEvents.has(event))return false;
|
|
@@ -110,13 +117,23 @@ export function createCoreClient({
|
|
|
110
117
|
if(typeof listener!=='function')throw new TypeError('Arcane event listener must be a function.');
|
|
111
118
|
if(!completedEvents.has(event))return events.once(event,listener);
|
|
112
119
|
const value=completedEvents.get(event);
|
|
120
|
+
const activation=webKitDocument?.activation;
|
|
113
121
|
let active=!closed;
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
let dispose=null;
|
|
123
|
+
function deliverReadyEvent(ready){
|
|
124
|
+
if(!active)return;
|
|
116
125
|
active=false;
|
|
117
|
-
|
|
126
|
+
dispose?.();
|
|
127
|
+
try{listener(ready);}catch(error){report(error);}
|
|
128
|
+
}
|
|
129
|
+
// A ready-state subscription survives a retirement that happens
|
|
130
|
+
// before its queued replay, just like an ordinary registration.
|
|
131
|
+
if(webKitDocument)dispose=events.once(event,deliverReadyEvent);
|
|
132
|
+
Promise.resolve().then(function replayReadyEvent(){
|
|
133
|
+
if(!active||!acceptsNativeDelivery(activation))return;
|
|
134
|
+
deliverReadyEvent(value);
|
|
118
135
|
});
|
|
119
|
-
return function unsubscribe(){active=false;};
|
|
136
|
+
return function unsubscribe(){active=false;dispose?.();};
|
|
120
137
|
},
|
|
121
138
|
completed(event){return CORE_READY_EVENTS.includes(event)&&completedEvents.has(event);}
|
|
122
139
|
};
|
|
@@ -131,8 +148,8 @@ export function createCoreClient({
|
|
|
131
148
|
else request.resolve(result);
|
|
132
149
|
return true;
|
|
133
150
|
}
|
|
134
|
-
function receive(input){
|
|
135
|
-
if(
|
|
151
|
+
function receive(input,activation){
|
|
152
|
+
if(!acceptsNativeDelivery(activation))return false;
|
|
136
153
|
let message=input;
|
|
137
154
|
if(typeof message==='string'){
|
|
138
155
|
try{message=JSON.parse(message);}
|
|
@@ -149,7 +166,9 @@ export function createCoreClient({
|
|
|
149
166
|
// Final responses follow earlier event deliveries, including while a
|
|
150
167
|
// classic host is connecting the shared SDK event owner. A stream's
|
|
151
168
|
// completion must not remove its subscription before its chunks arrive.
|
|
152
|
-
withEvents(
|
|
169
|
+
withEvents(function deliverResponse(){
|
|
170
|
+
if(acceptsNativeDelivery(activation))settle(message.id,message.ok?null:new CoreError(message.error),message.result);
|
|
171
|
+
});
|
|
153
172
|
return true;
|
|
154
173
|
}
|
|
155
174
|
function installCallback(name,value){
|
|
@@ -182,6 +201,107 @@ export function createCoreClient({
|
|
|
182
201
|
catch(error){record.reject(error);}
|
|
183
202
|
return true;
|
|
184
203
|
}
|
|
204
|
+
function createWebKitDocumentTransport(webkit) {
|
|
205
|
+
let activation = null;
|
|
206
|
+
let active = false;
|
|
207
|
+
let registration = null;
|
|
208
|
+
function current(candidate) {
|
|
209
|
+
return !closed && active && candidate === activation;
|
|
210
|
+
}
|
|
211
|
+
function inactive() {
|
|
212
|
+
return new CoreError(
|
|
213
|
+
{code: 'ARCANE_WEBKIT_DOCUMENT_INACTIVE', message: 'This document activation no longer owns the native connection.'}
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
async function post(record) {
|
|
217
|
+
parseAcknowledgement(
|
|
218
|
+
await webkit.postMessage(record), {required: true}
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
function activate(restored = false) {
|
|
222
|
+
if (closed || active) return;
|
|
223
|
+
activation = uuid();
|
|
224
|
+
active = true;
|
|
225
|
+
const candidate = activation;
|
|
226
|
+
// Only transport ingress waits for native attachment. Rendering and
|
|
227
|
+
// the synchronous facade remain independent of this acknowledgement.
|
|
228
|
+
registration = Promise.resolve().then(
|
|
229
|
+
function announceDocument() {
|
|
230
|
+
if (!current(candidate)) throw inactive();
|
|
231
|
+
return post(
|
|
232
|
+
{type: 'activate', activation: candidate}
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
complete(
|
|
237
|
+
'transport.ready', {protocol: CORE_PROTOCOL, transport: 'webkitgtk'}
|
|
238
|
+
);
|
|
239
|
+
registration.then(
|
|
240
|
+
function replayDocumentState() {
|
|
241
|
+
if (current(candidate) && (restored || replayRuntimeState)) {
|
|
242
|
+
sendControl(
|
|
243
|
+
{protocol: CORE_PROTOCOL, type: 'control', control: 'runtime.replay'}
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
).catch(report);
|
|
248
|
+
}
|
|
249
|
+
function retire() {
|
|
250
|
+
if (!active) return false;
|
|
251
|
+
const candidate = activation;
|
|
252
|
+
// Invalidate delivery synchronously, including callbacks queued in
|
|
253
|
+
// WebKit or waiting for the shared event owner. Subscriptions live
|
|
254
|
+
// with this document and remain registered across restoration.
|
|
255
|
+
active = false;
|
|
256
|
+
completedEvents.clear();
|
|
257
|
+
for (const [id, request] of pending) settle(id, aborted(request.method));
|
|
258
|
+
const json = JSON.stringify(
|
|
259
|
+
{protocol: CORE_PROTOCOL, type: 'control', control: 'requests.cancelAll'}
|
|
260
|
+
);
|
|
261
|
+
post(
|
|
262
|
+
{type: 'retire', activation: candidate, json}
|
|
263
|
+
).catch(report);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
function restoredDocument(event) {
|
|
267
|
+
if (event.persisted) activate(true);
|
|
268
|
+
}
|
|
269
|
+
if (typeof global.addEventListener === 'function') {
|
|
270
|
+
global.addEventListener(
|
|
271
|
+
'pageshow', restoredDocument, {capture: true}
|
|
272
|
+
);
|
|
273
|
+
cleanups.push(
|
|
274
|
+
function removeDocumentRestoration() {
|
|
275
|
+
global.removeEventListener(
|
|
276
|
+
'pageshow', restoredDocument, {capture: true}
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
cleanups.push(
|
|
282
|
+
function releaseDocument() { active = false; }
|
|
283
|
+
);
|
|
284
|
+
installCallback('__arcaneWebKitDocumentCurrent', current);
|
|
285
|
+
webKitDocument = {current, activate, retire, get activation() { return active ? activation : null; }};
|
|
286
|
+
return {
|
|
287
|
+
name: 'webkitgtk',
|
|
288
|
+
async send(frame) {
|
|
289
|
+
const candidate = activation;
|
|
290
|
+
if (!current(candidate)) throw inactive();
|
|
291
|
+
const json = JSON.stringify(frame);
|
|
292
|
+
await registration;
|
|
293
|
+
// A page may retire while native attachment is pending. It
|
|
294
|
+
// must never submit its queued request after that retirement.
|
|
295
|
+
if (!current(candidate)) throw inactive();
|
|
296
|
+
// Abort, timeout or cancelAll may also end a request while
|
|
297
|
+
// this activation remains live. Unsent work stays cancelled.
|
|
298
|
+
if (frame.type === 'request' && !pending.has(frame.id)) return;
|
|
299
|
+
await post(
|
|
300
|
+
{type: 'frame', activation: candidate, json}
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
185
305
|
function chooseTransport(){
|
|
186
306
|
const webview=global.chrome?.webview;
|
|
187
307
|
if(webview?.hostObjects){
|
|
@@ -204,6 +324,12 @@ export function createCoreClient({
|
|
|
204
324
|
const webkit=global.webkit?.messageHandlers?.arcane;
|
|
205
325
|
if(typeof global.__arcaneWebKitPostMessage==='function'||webkit){
|
|
206
326
|
installCallback('__arcaneReceive',receive);
|
|
327
|
+
if(webKitDocumentLifecycle){
|
|
328
|
+
if(typeof webkit?.postMessage!=='function'){
|
|
329
|
+
throw new CoreError({code:'ARCANE_TRANSPORT_INVALID',message:'WebKit document lifecycle requires the arcane message handler with replies.'});
|
|
330
|
+
}
|
|
331
|
+
return createWebKitDocumentTransport(webkit);
|
|
332
|
+
}
|
|
207
333
|
installCallback('__arcaneWebKitAcknowledge',acknowledge);
|
|
208
334
|
return {name:'webkitgtk',async send(frame){
|
|
209
335
|
const serialized=JSON.stringify(frame);
|
|
@@ -293,8 +419,9 @@ export function createCoreClient({
|
|
|
293
419
|
throw error;
|
|
294
420
|
}
|
|
295
421
|
if(closed)throw transportFailure??new CoreError({code:'ARCANE_CLIENT_CLOSED',message:'The Core client is closed.'});
|
|
422
|
+
if(webKitDocument)webKitDocument.activate();
|
|
296
423
|
complete('transport.ready',{protocol:CORE_PROTOCOL,transport:transport.name});
|
|
297
|
-
if(replayRuntimeState&&!closed)sendControl({protocol:CORE_PROTOCOL,type:'control',control:'runtime.replay'});
|
|
424
|
+
if(replayRuntimeState&&!closed&&!webKitDocument)sendControl({protocol:CORE_PROTOCOL,type:'control',control:'runtime.replay'});
|
|
298
425
|
// A native callback may close the client synchronously during replay.
|
|
299
426
|
if(closed)throw transportFailure??new CoreError({code:'ARCANE_CLIENT_CLOSED',message:'The Core client is closed.'});
|
|
300
427
|
return transport;
|
|
@@ -305,7 +432,7 @@ export function createCoreClient({
|
|
|
305
432
|
:typeof global.__arcaneWebKitPostMessage==='function'||global.webkit?.messageHandlers?.arcane?'webkitgtk'
|
|
306
433
|
:typeof global.arcaneAndroid?.postMessage==='function'?'android-webview'
|
|
307
434
|
:global.__ARCANE_DEV_HTTP__?'development-http':'standalone');
|
|
308
|
-
return {connected:transport!==null&&!closed,transport:name,
|
|
435
|
+
return {connected:transport!==null&&!closed&&(!webKitDocument||webKitDocument.activation!==null),transport:name,
|
|
309
436
|
native:['webview2','webkitgtk','android-webview'].includes(name),
|
|
310
437
|
managedLocalAI:['webview2','webkitgtk'].includes(name)};
|
|
311
438
|
}
|
|
@@ -389,18 +516,23 @@ export function createCoreClient({
|
|
|
389
516
|
function close(){
|
|
390
517
|
if(closed)return false;
|
|
391
518
|
closed=true;
|
|
392
|
-
|
|
519
|
+
if(webKitDocument)webKitDocument.retire();
|
|
520
|
+
else cancelAll();
|
|
393
521
|
disposeTransport(new CoreError({code:'ARCANE_CLIENT_CLOSED',message:'The Core client is closed.'}));
|
|
394
522
|
return true;
|
|
395
523
|
}
|
|
524
|
+
function documentHidden(){
|
|
525
|
+
if(webKitDocument)webKitDocument.retire();
|
|
526
|
+
else cancelAll();
|
|
527
|
+
}
|
|
396
528
|
if(typeof global.addEventListener==='function'){
|
|
397
|
-
global.addEventListener('pagehide',
|
|
398
|
-
cleanups.push(()=>global.removeEventListener('pagehide',
|
|
529
|
+
global.addEventListener('pagehide',documentHidden,{capture:true});
|
|
530
|
+
cleanups.push(()=>global.removeEventListener('pagehide',documentHidden,{capture:true}));
|
|
399
531
|
}
|
|
400
532
|
if(autoConnect){
|
|
401
533
|
try{connect();}catch(error){if(error.code!=='ARCANE_TRANSPORT_UNAVAILABLE')report(error);}
|
|
402
534
|
}
|
|
403
|
-
return {protocol:CORE_PROTOCOL,Error:CoreError,invoke,receive,connect,close,failTransport,cancelAll,events,eventsReady,runtime:{current:runtimeSnapshot},uuid};
|
|
535
|
+
return {protocol:CORE_PROTOCOL,Error:CoreError,invoke,receive,connect,close,failTransport,cancelAll,acceptsNativeDelivery,events,eventsReady,runtime:{current:runtimeSnapshot},uuid};
|
|
404
536
|
}
|
|
405
537
|
|
|
406
538
|
/** Existing namespace call shapes; method implementations remain host-owned. */
|
|
@@ -436,7 +568,7 @@ export function createCoreFacade(client){
|
|
|
436
568
|
remove:name=>invoke('environment.delete',{name})},
|
|
437
569
|
mail:{send:request=>invoke('mail.send',request??{},{timeoutMs:450000})},
|
|
438
570
|
speech:{status:()=>invoke('speech.status',{},{timeoutMs:10000}),
|
|
439
|
-
synthesize:request=>invoke('speech.synthesize',request??{},{timeoutMs:180000}),
|
|
571
|
+
synthesize:(request,options)=>invoke('speech.synthesize',request??{},{timeoutMs:180000,signal:options?.signal}),
|
|
440
572
|
transcribe:request=>invoke('speech.transcribe',request??{},{timeoutMs:180000})},
|
|
441
573
|
localAI:{status:()=>invoke('localai.status',{},{timeoutMs:15000}),
|
|
442
574
|
ensurePlatform:()=>invoke('localai.platform.ensure',{},long),recover:request=>invoke('localai.services.recover',request??{},long),
|
|
@@ -512,7 +644,10 @@ export function installCoreClient(global=globalThis,options={}){
|
|
|
512
644
|
const previous=global.__arcaneReceive;
|
|
513
645
|
global.__arcaneReceive=client.receive;
|
|
514
646
|
const previousFailure=global.__arcaneTransportFailed;
|
|
515
|
-
function nativeTransportFailed(error){
|
|
647
|
+
function nativeTransportFailed(error,activation){
|
|
648
|
+
if(!client.acceptsNativeDelivery(activation))return false;
|
|
649
|
+
return client.failTransport(error);
|
|
650
|
+
}
|
|
516
651
|
global.__arcaneTransportFailed=nativeTransportFailed;
|
|
517
652
|
const close=client.close;
|
|
518
653
|
const failTransport=client.failTransport;
|
package/docs/architecture.md
CHANGED
|
@@ -191,13 +191,16 @@ redirect listener, defaulting to an OS-assigned port. Existing raw `tls` inputs
|
|
|
191
191
|
retain their native HTTPS transport under the SDK, as described by the module's
|
|
192
192
|
advanced TLS extension guidance; all content uses its public serving methods.
|
|
193
193
|
|
|
194
|
-
Explicit `arcane dev --http` or
|
|
194
|
+
Explicit `arcane dev --http` or API `http:true` selects the module's
|
|
195
195
|
single HTTP listener on `port`. It skips certificate resolution and serves the
|
|
196
196
|
same selected source mappings, generated PWA routes, and conditional responses.
|
|
197
|
+
In API packaged mode, it serves the complete selected packaged web root and
|
|
198
|
+
its declared application start URL instead of source mappings.
|
|
197
199
|
No HTTPS listener or redirect is started in this mode. Listener readiness,
|
|
198
200
|
failure, cancellation and shutdown retain the same operation owner; reported
|
|
199
201
|
URLs use the actual HTTP protocol and bound port. Browser secure-context
|
|
200
|
-
requirements remain browser-owned.
|
|
202
|
+
requirements remain browser-owned. Source and packaged modes retain HTTPS
|
|
203
|
+
when `http:true` is not selected; a certificate error never switches transport.
|
|
201
204
|
|
|
202
205
|
Development is an intentionally fast feedback loop. Keep each increment small
|
|
203
206
|
and independently understandable so its effect has one clear cause and a
|
package/docs/platform-targets.md
CHANGED
|
@@ -16,10 +16,15 @@ toolchain, platform, architecture, format, signing mode, declared dependency
|
|
|
16
16
|
releases, and destination. Verification is a separate operation only when the
|
|
17
17
|
user explicitly selects it for the release artifact.
|
|
18
18
|
|
|
19
|
-
The portable
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
The portable, Windows x64 and macOS targets pair with installed SDK providers by
|
|
20
|
+
default. Windows uses the numeric SDK release's precompiled WebView2/SEA host.
|
|
21
|
+
macOS source composition selects that version's architecture-specific AppKit,
|
|
22
|
+
WebKit and bundled Node host asset; actual asset publication and Mac execution
|
|
23
|
+
are separate release evidence, not implied by the target registry.
|
|
24
|
+
An explicit `--arcane-root` retains fixed-provider selection for the registered
|
|
25
|
+
portable, Windows, Linux and Android checkout targets. macOS uses the installed
|
|
26
|
+
SDK provider and has no checkout override. Linux and Android still require that
|
|
27
|
+
override and their platform toolchains. Every native request also requires
|
|
23
28
|
the canonical app descriptor to declare the exact target selected on the
|
|
24
29
|
command line. The SDK package does not silently search for a toolchain, infer a
|
|
25
30
|
descriptor target, embed the Arcane machine bundle, or substitute browser
|
|
@@ -35,8 +40,8 @@ npm exec -- arcane native-doctor --target portable
|
|
|
35
40
|
npm exec -- arcane build --target portable
|
|
36
41
|
|
|
37
42
|
# In an app scaffolded with --target windows-x64:
|
|
38
|
-
npm exec -- arcane build --target windows-x64
|
|
39
|
-
npm exec -- arcane run --target windows-x64
|
|
43
|
+
npm exec -- arcane build --target windows-x64
|
|
44
|
+
npm exec -- arcane run --target windows-x64
|
|
40
45
|
|
|
41
46
|
# In an app scaffolded with --target linux-x64:
|
|
42
47
|
npm exec -- arcane build --target linux-x64 --arcane-root "../Arcane OS"
|
|
@@ -62,7 +67,7 @@ from its installed npm dependency. After `npm install`, run
|
|
|
62
67
|
browser maps are present. The initial scaffold reports that map as pending while
|
|
63
68
|
the SDK dependency is absent. The scaffold includes the raster icon required
|
|
64
69
|
by the current native platform. Use the
|
|
65
|
-
matching scaffold target (`portable`, `windows-x64`, `linux-x64`, `linux-arm64`,
|
|
70
|
+
matching scaffold target (`portable`, `windows-x64`, `macos-arm64`, `macos-x64`, `linux-x64`, `linux-arm64`,
|
|
66
71
|
or `android-arm64`) before running the corresponding command.
|
|
67
72
|
|
|
68
73
|
`native-prepare` remains a standalone diagnostic. The normal build recipe omits
|
|
@@ -72,7 +77,8 @@ The portable output is an app-scoped payload directory with complete selected
|
|
|
72
77
|
app and dependency files, a generated Core entry and classic client source.
|
|
73
78
|
It is not an executable and has no direct run operation. Explicit service
|
|
74
79
|
composition uses `createPortableNativeProvider({services:[{module,options}]})`;
|
|
75
|
-
the default
|
|
80
|
+
the default uses an explicit descriptor service selection when supplied and
|
|
81
|
+
does not discover or copy OS product services. See
|
|
76
82
|
[Core native packaging](reference/core-native-packaging.md).
|
|
77
83
|
The external workspace defaults to `build/portable/`. Integrated Arcane work
|
|
78
84
|
must name an `--output-root` outside that checkout. Explicit checkout providers
|
|
@@ -89,19 +95,30 @@ for the complete compatibility and breaking-change rule.
|
|
|
89
95
|
|---|---|---|
|
|
90
96
|
| `browser` | `directory` | Available |
|
|
91
97
|
| `portable` | `portable` directory | SDK package provider by default; explicit checkout override retained; not executable |
|
|
92
|
-
| `windows-x64` | `exe` bundle |
|
|
98
|
+
| `windows-x64` | `exe` bundle | SDK package provider and matching precompiled release host by default; explicit checkout override retained |
|
|
99
|
+
| `macos-arm64` | `.app` bundle | SDK provider source; needs a matching selected or published Apple silicon host asset |
|
|
100
|
+
| `macos-x64` | `.app` bundle | SDK provider source; needs a matching selected or published Intel host asset |
|
|
93
101
|
| `linux-x64` | `deb` | Available with explicit `--arcane-root`; unsigned local development only |
|
|
94
102
|
| `linux-arm64` | `deb` | Available with explicit `--arcane-root` on a compatible native ARM64 toolchain; unsigned local development only |
|
|
95
103
|
| `android-arm64` | `apk` | Available with explicit `--arcane-root`; development-signed, architecture-neutral, and physical/native ARM64 for run |
|
|
96
104
|
|
|
97
105
|
macOS can consume browser applications and portable JavaScript capabilities
|
|
98
|
-
within their declared runtimes. The
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
within their declared runtimes. The native target registry includes separate
|
|
107
|
+
Apple silicon and Intel `.app` compositions; portable CLI requests also accept
|
|
108
|
+
macOS. A Mac provider preserves the complete portable payload under its bundle's
|
|
109
|
+
Resources directory and composes the SDK host separately. A source-development
|
|
102
110
|
native wrapper is also not an exposed `arcane dev` target. Native `run` uses the
|
|
103
111
|
selected packaged application through its paired provider.
|
|
104
112
|
|
|
113
|
+
The package includes macOS `ArcaneCoreProcess`, `ArcaneHost` and launcher source
|
|
114
|
+
for explicit child launch, framed pipes, document lifecycle and graceful drain.
|
|
115
|
+
The SDK provider composes them with an explicitly selected Darwin Node runtime.
|
|
116
|
+
Source availability is not evidence of Mac compilation or execution. See the
|
|
117
|
+
[macOS composition boundary](reference/core-native-packaging.md#macos-application-composition).
|
|
118
|
+
Windows app assembly needs the normal Node/npm toolchain and `tar`, not a C#
|
|
119
|
+
compiler. Its generated executable uses bundled Node and requires .NET Framework
|
|
120
|
+
4.6.2 or later plus WebView2 Runtime on the destination Windows x64 machine.
|
|
121
|
+
|
|
105
122
|
Every native target accepts one selected app release and its complete bundled
|
|
106
123
|
dependency closure through the provider boundary. The providers retain the
|
|
107
124
|
toolchain state required for build and, where supported, launch; app source and
|