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.
Files changed (60) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +6 -2
  3. package/browser-runtime/ai/core-onnx.mjs +96 -0
  4. package/browser-runtime/ai/onnx-tensors.mjs +85 -0
  5. package/browser-runtime/core/client.mjs +152 -17
  6. package/docs/architecture.md +5 -2
  7. package/docs/platform-targets.md +30 -13
  8. package/docs/reference/README.md +22 -3
  9. package/docs/reference/availability-and-normalization.md +14 -7
  10. package/docs/reference/cli.md +22 -10
  11. package/docs/reference/core-client.md +87 -0
  12. package/docs/reference/core-native-packaging.md +129 -9
  13. package/docs/reference/core-preferences.md +49 -0
  14. package/docs/reference/diarization.md +206 -0
  15. package/docs/reference/git-text-snapshot.md +104 -0
  16. package/docs/reference/inventory/package-api.json +205 -23
  17. package/docs/reference/local-ai.md +145 -11
  18. package/docs/reference/protocols.md +28 -19
  19. package/docs/reference/pwa.md +74 -4
  20. package/docs/reference/sdk-api.md +589 -39
  21. package/package.json +11 -3
  22. package/schemas/arcane-app.schema.json +3 -3
  23. package/schemas/arcane-package.schema.json +2 -2
  24. package/schemas/native-build-plan.schema.json +27 -1
  25. package/schemas/target-adapter.schema.json +2 -0
  26. package/src/app-descriptor.mjs +1 -1
  27. package/src/cli/main.mjs +14 -4
  28. package/src/constants.mjs +2 -0
  29. package/src/core/host.mjs +14 -5
  30. package/src/core/hosts/macos/ArcaneHost.h +86 -0
  31. package/src/core/hosts/macos/ArcaneHost.m +455 -0
  32. package/src/core/hosts/macos/ArcaneLauncher.m +432 -0
  33. package/src/core/hosts/windows/ArcaneLauncher.cs +4 -1
  34. package/src/core/services/local-ai.mjs +82 -4
  35. package/src/core/services/packaged-web.mjs +89 -0
  36. package/src/core/services/preferences.mjs +201 -0
  37. package/src/core-classic-source.mjs +2 -2
  38. package/src/dev-server.mjs +2 -5
  39. package/src/diarization/build.mjs +35 -0
  40. package/src/diarization/index.mjs +432 -0
  41. package/src/diarization/native/CMakeLists.txt +9 -0
  42. package/src/diarization/native/main.cpp +269 -0
  43. package/src/diarization/service.mjs +129 -0
  44. package/src/git-text-snapshot.mjs +265 -0
  45. package/src/import-map.mjs +9 -4
  46. package/src/index.mjs +1 -0
  47. package/src/local-ai/config.mjs +1 -1
  48. package/src/local-ai/install.mjs +134 -19
  49. package/src/local-ai/onnx-worker.mjs +85 -0
  50. package/src/local-ai/onnx.mjs +368 -0
  51. package/src/native/macos-provider.mjs +291 -0
  52. package/src/native/portable-layout.mjs +10 -2
  53. package/src/native/portable-provider.mjs +10 -5
  54. package/src/native-plan.mjs +9 -1
  55. package/src/native-provider-loader.mjs +3 -1
  56. package/src/process.mjs +123 -17
  57. package/src/scaffold.mjs +1 -1
  58. package/src/targets/index.mjs +11 -1
  59. package/src/templates/workspace-template.mjs +6 -5
  60. package/src/toolchain.mjs +42 -13
@@ -0,0 +1,89 @@
1
+ import {mkdir, readFile, writeFile} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {startDevServer} from '../../dev-server.mjs';
4
+ import {serializeCoreError} from '../../../browser-runtime/core/contracts.mjs';
5
+
6
+ /** Serve one packaged web root inside its existing Core lifetime. */
7
+ export function createPackagedWebService({artifactRoot}, context = {}) {
8
+ let server;
9
+ let closing = false;
10
+ let lifetime;
11
+ return {
12
+ name: 'packaged-web',
13
+ async start(runtime) {
14
+ const manifest = JSON.parse(await readFile(path.join(artifactRoot, 'arcane-native.json'), 'utf8'));
15
+ // The launcher chooses app state independently of its installation.
16
+ // Forward the application's launch file unchanged; the fallback is
17
+ // a separate native argument, not a rewritten application payload.
18
+ const stateFlag = process.argv.indexOf('--arcane-host-state-root');
19
+ const stateRoot = context.stateRoot ?? (stateFlag < 0 ? undefined : process.argv[stateFlag + 1]);
20
+ if (typeof stateRoot !== 'string' || !stateRoot) {
21
+ throw new TypeError('Packaged web serving requires the launcher-selected stateRoot.');
22
+ }
23
+ await mkdir(stateRoot, {recursive: true});
24
+ const originFile = path.join(stateRoot, 'packaged-web-origin.json');
25
+ let port = 0;
26
+ let saved = false;
27
+ try {
28
+ const origin = JSON.parse(await readFile(originFile, 'utf8'));
29
+ port = origin.port;
30
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
31
+ throw new TypeError('The saved packaged origin needs its previously selected listener port.');
32
+ }
33
+ saved = true;
34
+ } catch (error) {
35
+ if (error.code !== 'ENOENT') throw error;
36
+ }
37
+ try {
38
+ server = await startDevServer({
39
+ mode: 'packaged', http: true,
40
+ releaseRoot: path.resolve(artifactRoot, manifest.webRoot),
41
+ host: '127.0.0.1', port
42
+ });
43
+ // Only a first successful listener selects a port. A later bind
44
+ // conflict remains an error instead of moving saved browser data
45
+ // to a different origin. Concurrent first launches cannot replace
46
+ // one another's selection.
47
+ if (!saved) {
48
+ await writeFile(originFile, JSON.stringify({host: '127.0.0.1', port: server.port}) + '\n', {flag: 'wx'});
49
+ }
50
+ const url = new URL(manifest.start, server.origin).href;
51
+ runtime.emit('core.web.ready', {origin: server.origin, url, port: server.port});
52
+ lifetime = server.lifecycle.then(
53
+ function packagedServerEnded() {
54
+ if (!closing) {
55
+ runtime.emit('core.web.failed', serializeCoreError(
56
+ new Error('The packaged application listener closed unexpectedly.')
57
+ ));
58
+ }
59
+ },
60
+ function packagedServerFailed(error) {
61
+ if (!closing) runtime.emit('core.web.failed', serializeCoreError(error));
62
+ throw error;
63
+ }
64
+ );
65
+ // Retain the original rejection for drain while observing it
66
+ // now; the native diagnostic event owns immediate reporting.
67
+ lifetime.catch(function observePackagedServerFailure() {});
68
+ } catch (error) {
69
+ closing = true;
70
+ if (server) {
71
+ try { await server.close(); }
72
+ catch (closeError) {
73
+ throw new AggregateError([error, closeError], 'Packaged application startup and shutdown failed.');
74
+ }
75
+ }
76
+ throw error;
77
+ }
78
+ },
79
+ async drain() {
80
+ closing = true;
81
+ if (!server) return;
82
+ const results = await Promise.allSettled([server.close(), server.closed, lifetime]);
83
+ const failures = [...new Set(results.filter(function rejected(result) {
84
+ return result.status === 'rejected';
85
+ }).map(function reason(result) {return result.reason;}))];
86
+ if (failures.length) throw new AggregateError(failures, 'Packaged application serving shutdown failed.');
87
+ }
88
+ };
89
+ }
@@ -0,0 +1,201 @@
1
+ import {mkdir, mkdtemp, open, readFile, rename, rmdir, unlink} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import Is from 'strong-type';
4
+
5
+ const is = new Is(false);
6
+
7
+ // A selected file has one read/modify/write order within this Core process,
8
+ // including when separate service instances select the same file.
9
+ const fileOperations = new Map();
10
+
11
+ /**
12
+ * Native implementation of the existing Arcane.preferences methods.
13
+ * The application selects its file; namespaces, defaults and preference schemas
14
+ * remain with the caller. Factory construction performs no I/O.
15
+ *
16
+ * Writes have service lifetime. Once accepted, renderer cancellation cannot
17
+ * discard them. drain()/dispose() stop acceptance and await owned operations.
18
+ * Success follows a flushed file replacement, not a promise of filesystem
19
+ * survival across every possible operating-system or hardware failure.
20
+ */
21
+ export function createPreferencesService({file} = {}) {
22
+ if (!is.string(file) || file === '') {
23
+ throw new TypeError('Preferences require an application-selected file path.');
24
+ }
25
+ const filename = path.resolve(file);
26
+ const pending = new Set();
27
+ let closing = null;
28
+
29
+ function accept(operation) {
30
+ if (closing) {
31
+ const error = new Error('The preferences service is closing.');
32
+ error.code = 'CORE_CLOSING';
33
+ return Promise.reject(error);
34
+ }
35
+ const result = orderFileOperation(filename, operation);
36
+ pending.add(result);
37
+ function releaseOperation() { pending.delete(result); }
38
+ result.then(releaseOperation, releaseOperation);
39
+ return result;
40
+ }
41
+
42
+ function list(parameters, {signal} = {}) {
43
+ return accept(async function readKeys() {
44
+ const document = await readPreferences(filename, signal);
45
+ return {keys: Object.keys(document.entries).sort()};
46
+ });
47
+ }
48
+
49
+ function get({key}, {signal} = {}) {
50
+ return accept(async function readValue() {
51
+ requireKey(key);
52
+ const document = await readPreferences(filename, signal);
53
+ const found = Object.hasOwn(document.entries, key);
54
+ return {key, found, value: found ? document.entries[key] : null};
55
+ });
56
+ }
57
+
58
+ function set({key, value}) {
59
+ return accept(async function saveValue() {
60
+ requireKey(key);
61
+ const document = await readPreferences(filename);
62
+ setEntry(document.entries, key, value);
63
+ await writePreferences(filename, document);
64
+ return {key, value};
65
+ });
66
+ }
67
+
68
+ function setMany({entries}) {
69
+ return accept(async function saveBatch() {
70
+ if (!entries || !is.object(entries) || is.array(entries)) {
71
+ throw new TypeError('Preference entries must be a JSON object.');
72
+ }
73
+ const document = await readPreferences(filename);
74
+ const batch = Object.entries(entries);
75
+ for (const [key, value] of batch) setEntry(document.entries, key, value);
76
+ await writePreferences(filename, document);
77
+ return {keys: batch.map(function entryKey([key]) { return key; }).sort(), count: batch.length};
78
+ });
79
+ }
80
+
81
+ function deletePreference({key}) {
82
+ return accept(async function removeValue() {
83
+ requireKey(key);
84
+ const document = await readPreferences(filename);
85
+ const deleted = Object.hasOwn(document.entries, key);
86
+ if (deleted) {
87
+ delete document.entries[key];
88
+ await writePreferences(filename, document);
89
+ }
90
+ return {key, deleted};
91
+ });
92
+ }
93
+
94
+ function drain() {
95
+ if (closing) return closing;
96
+ closing = Promise.allSettled([...pending]).then(function operationsDrained(results) {
97
+ const failures = results.filter(function failed(result) {
98
+ return result.status === 'rejected';
99
+ }).map(function reason(result) { return result.reason; });
100
+ if (failures.length) throw new AggregateError(failures, 'Preference operations failed while draining.');
101
+ });
102
+ return closing;
103
+ }
104
+
105
+ return {
106
+ name: 'preferences',
107
+ methods: {
108
+ 'preferences.list': list,
109
+ 'preferences.get': get,
110
+ 'preferences.set': {lifetime: 'service', handle: set},
111
+ 'preferences.setMany': {lifetime: 'service', handle: setMany},
112
+ 'preferences.delete': {lifetime: 'service', handle: deletePreference}
113
+ },
114
+ drain,
115
+ dispose: drain
116
+ };
117
+ }
118
+
119
+ function orderFileOperation(file, operation) {
120
+ const previous = fileOperations.get(file) ?? Promise.resolve();
121
+ const result = previous.then(operation, operation);
122
+ function releaseQueue() {
123
+ if (fileOperations.get(file) === tail) fileOperations.delete(file);
124
+ }
125
+ // The caller retains the rejecting result. This fulfilled scheduling tail
126
+ // lets a later operation proceed after an earlier reported failure.
127
+ const tail = result.then(releaseQueue, releaseQueue);
128
+ fileOperations.set(file, tail);
129
+ return result;
130
+ }
131
+
132
+ async function readPreferences(file, signal) {
133
+ signal?.throwIfAborted();
134
+ let source;
135
+ try {
136
+ source = await readFile(file, {encoding: 'utf8', signal});
137
+ } catch (error) {
138
+ if (error.code !== 'ENOENT') throw error;
139
+ signal?.throwIfAborted();
140
+ return {schemaVersion: 1, entries: {}};
141
+ }
142
+ const document = JSON.parse(source);
143
+ if (!document || document.schemaVersion !== 1 || !document.entries
144
+ || !is.object(document.entries) || is.array(document.entries)) {
145
+ throw new TypeError(`Preferences at ${file} do not contain the existing schemaVersion 1 entries record.`);
146
+ }
147
+ return document;
148
+ }
149
+
150
+ function requireKey(key) {
151
+ if (!is.string(key)) throw new TypeError('A preference key must be a string.');
152
+ }
153
+
154
+ function setEntry(entries, key, value) {
155
+ // Define an ordinary own data property: "__proto__" is a preference key,
156
+ // not an instruction to change the entries object's prototype.
157
+ Object.defineProperty(entries, key, {value, enumerable: true, writable: true, configurable: true});
158
+ }
159
+
160
+ function jsonValue(key, value) {
161
+ const kind = typeof value;
162
+ if (value === undefined || kind === 'function' || kind === 'symbol'
163
+ || (kind === 'number' && !Number.isFinite(value))) {
164
+ throw new TypeError(`Preference value at ${JSON.stringify(key)} cannot be represented completely in JSON.`);
165
+ }
166
+ return value;
167
+ }
168
+
169
+ async function writePreferences(file, document) {
170
+ // Serialization happens before any filesystem write. The JSON boundary
171
+ // reports unrepresentable values instead of silently omitting them.
172
+ const source = JSON.stringify(document, jsonValue);
173
+ const directory = path.dirname(file);
174
+ await mkdir(directory, {recursive: true});
175
+ const temporaryDirectory = await mkdtemp(path.join(directory, '.arcane-preferences-'));
176
+ const temporary = path.join(temporaryDirectory, 'preferences.json');
177
+ const failures = [];
178
+ let handle = null;
179
+ try {
180
+ handle = await open(temporary, 'w');
181
+ await handle.writeFile(source, 'utf8');
182
+ await handle.sync();
183
+ await handle.close();
184
+ handle = null;
185
+ await rename(temporary, file);
186
+ } catch (error) {
187
+ failures.push(error);
188
+ } finally {
189
+ if (handle) {
190
+ try { await handle.close(); } catch (error) { failures.push(error); }
191
+ }
192
+ try { await unlink(temporary); } catch (error) {
193
+ if (error.code !== 'ENOENT') failures.push(error);
194
+ }
195
+ try { await rmdir(temporaryDirectory); } catch (error) { failures.push(error); }
196
+ }
197
+ if (failures.length === 1) throw failures[0];
198
+ if (failures.length > 1) throw new AggregateError(failures, 'Preference replacement and cleanup failed.');
199
+ }
200
+
201
+ export default createPreferencesService;
@@ -5,7 +5,7 @@ import {readFile} from 'node:fs/promises';
5
5
  * The two owned modules have declaration exports and two explicit imports;
6
6
  * this projection is deliberately not a general JavaScript bundler.
7
7
  */
8
- export async function createCoreClassicSource({eventOwnerModuleURL,replayRuntimeState=false}={}){
8
+ export async function createCoreClassicSource({eventOwnerModuleURL,replayRuntimeState=false,webKitDocumentLifecycle=false}={}){
9
9
  if(typeof eventOwnerModuleURL!=='string'||!eventOwnerModuleURL){
10
10
  throw new TypeError('The classic Core client needs the served SDK event-manager module URL.');
11
11
  }
@@ -37,7 +37,7 @@ ${implementation}
37
37
  },{once:true});
38
38
  })
39
39
  :loadSharedEventOwner();
40
- installCoreClient(global,{eventOwner:arcaneEvents,eventOwnerReady,replayRuntimeState:${replayRuntimeState===true}});
40
+ installCoreClient(global,{eventOwner:arcaneEvents,eventOwnerReady,replayRuntimeState:${replayRuntimeState===true}${webKitDocumentLifecycle===true?',webKitDocumentLifecycle:true':''}});
41
41
  })(globalThis);
42
42
  `;
43
43
  }
@@ -831,7 +831,7 @@ async function resolveDevelopmentTls({workspaceRoot,tls,certPath,keyPath,signal}
831
831
  }catch(error){
832
832
  if(error.code==='ENOENT'){
833
833
  fail(
834
- `Arcane development and packaged previews require HTTPS. `
834
+ `Arcane development and packaged previews default to HTTPS. `
835
835
  +`Provide a certificate at ${certificatePath} and a key at ${privateKeyPath}, `
836
836
  +'or select existing files with --cert and --key.',
837
837
  'ARCANE_DEV_TLS_MISSING'
@@ -873,12 +873,9 @@ async function startOwnedDevServer({
873
873
  if (!is.boolean(http)) {
874
874
  fail('http must be a boolean.', 'ARCANE_USAGE');
875
875
  }
876
- if (http && mode !== 'source') {
877
- fail('HTTP serving is supported only in source development mode.', 'ARCANE_USAGE');
878
- }
879
876
  if (http && (requestedHttps === true || (tls !== undefined && tls !== null && tls !== false)
880
877
  || certPath !== undefined || keyPath !== undefined || httpPort !== 0)) {
881
- fail('HTTP development cannot combine TLS options or a separate httpPort; select its listener with port.', 'ARCANE_USAGE');
878
+ fail('HTTP serving cannot combine TLS options or a separate httpPort; select its listener with port.', 'ARCANE_USAGE');
882
879
  }
883
880
  const protocol = http ? 'http:' : 'https:';
884
881
  const requestedRuntimeMode=mode==='source'&&sdkRuntimeSourceRoot!==undefined
@@ -0,0 +1,35 @@
1
+ import path from 'node:path';
2
+ import {fileURLToPath} from 'node:url';
3
+ import {mkdir} from 'node:fs/promises';
4
+ import Is from 'strong-type';
5
+ import {runProcess} from '../process.mjs';
6
+
7
+ const is = new Is(false);
8
+
9
+ /** Build this host's first-party helper against its selected upstream runtime. */
10
+ export async function buildDiarizationHelper({runtime, outputRoot, signal, onEvent} = {}) {
11
+ if (!is.string(runtime?.cmakeDirectory) || !is.string(outputRoot)) {
12
+ throw new TypeError('The selected NeMo runtime cmakeDirectory and helper outputRoot are required.');
13
+ }
14
+ const root = path.resolve(outputRoot);
15
+ const build = path.join(root, 'build');
16
+ const source = fileURLToPath(new URL('./native/', import.meta.url));
17
+ await mkdir(root, {recursive: true});
18
+ const options = {signal, onEvent};
19
+ const configure = await runProcess('cmake', [
20
+ '-S', source,
21
+ '-B', build,
22
+ `-DNeMoSpeech_DIR=${path.resolve(runtime.cmakeDirectory)}`,
23
+ `-DCMAKE_INSTALL_PREFIX=${root}`,
24
+ '-DCMAKE_BUILD_TYPE=Release'
25
+ ], options);
26
+ const compile = await runProcess('cmake', ['--build', build, '--config', 'Release'], options);
27
+ const install = await runProcess('cmake', ['--install', build, '--config', 'Release'], options);
28
+ return {
29
+ platform: process.platform,
30
+ architecture: process.arch,
31
+ root,
32
+ executable: path.join(root, 'bin', process.platform === 'win32' ? 'arcane-diarization.exe' : 'arcane-diarization'),
33
+ diagnostics: {configure, compile, install}
34
+ };
35
+ }