arcane-os 0.56.0 → 0.58.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 +43 -0
- package/README.md +6 -2
- package/browser-runtime/ai/core-local.mjs +306 -0
- package/browser-runtime/ai/core-onnx.mjs +96 -0
- package/browser-runtime/ai/onnx-tensors.mjs +85 -0
- package/browser-runtime/core/client.mjs +151 -16
- package/docs/architecture.md +5 -2
- package/docs/platform-targets.md +30 -13
- package/docs/reference/README.md +38 -3
- package/docs/reference/availability-and-normalization.md +9 -6
- package/docs/reference/cli.md +29 -9
- package/docs/reference/core-client.md +80 -0
- package/docs/reference/core-native-packaging.md +304 -12
- 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 +330 -22
- package/docs/reference/local-ai.md +331 -0
- package/docs/reference/protocols.md +97 -20
- package/docs/reference/pwa.md +5 -2
- package/docs/reference/runtime-modules.md +6 -5
- package/docs/reference/sdk-api.md +611 -63
- package/package.json +15 -3
- package/runtime/arcane/modules/LocalAIReadiness.js +3 -1
- package/schemas/arcane-app.schema.json +55 -1
- package/schemas/arcane-package.schema.json +44 -0
- package/schemas/native-build-plan.schema.json +27 -1
- package/schemas/target-adapter.schema.json +2 -0
- package/src/app-descriptor.mjs +18 -2
- package/src/cli/main.mjs +20 -4
- package/src/constants.mjs +2 -0
- package/src/core/host.mjs +23 -0
- package/src/core/hosts/macos/ArcaneCoreProcess.h +85 -0
- package/src/core/hosts/macos/ArcaneCoreProcess.m +357 -0
- 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/ArcaneCoreProcess.cs +3 -3
- package/src/core/hosts/windows/ArcaneHost.cs +695 -0
- package/src/core/hosts/windows/ArcaneHost.manifest +22 -0
- package/src/core/hosts/windows/ArcaneLauncher.cs +239 -0
- package/src/core/services/local-ai.mjs +621 -0
- 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 +9 -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 +2 -0
- package/src/index.mjs +1 -0
- package/src/local-ai/archive.mjs +52 -0
- package/src/local-ai/config.mjs +62 -0
- package/src/local-ai/dev.mjs +390 -0
- package/src/local-ai/discover.mjs +71 -0
- package/src/local-ai/http.mjs +106 -0
- package/src/local-ai/install.mjs +459 -0
- package/src/local-ai/onnx-worker.mjs +85 -0
- package/src/local-ai/onnx.mjs +368 -0
- package/src/local-ai/server.mjs +262 -0
- package/src/native/macos-provider.mjs +291 -0
- package/src/native/portable-layout.mjs +37 -4
- package/src/native/portable-provider.mjs +44 -8
- package/src/native/windows-provider.mjs +262 -0
- package/src/native-plan.mjs +9 -1
- package/src/native-provider-loader.mjs +10 -2
- package/src/packager/core.mjs +6 -2
- package/src/process.mjs +184 -20
- 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 +33 -5
|
@@ -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
|
}
|
package/src/dev-server.mjs
CHANGED
|
@@ -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
|
|
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'
|
|
@@ -854,6 +854,7 @@ async function startOwnedDevServer({
|
|
|
854
854
|
tls,
|
|
855
855
|
certPath,
|
|
856
856
|
keyPath,
|
|
857
|
+
localAI,
|
|
857
858
|
signal,
|
|
858
859
|
sdkRuntimeSourceRoot
|
|
859
860
|
}={},events,releaseSignal){
|
|
@@ -872,12 +873,9 @@ async function startOwnedDevServer({
|
|
|
872
873
|
if (!is.boolean(http)) {
|
|
873
874
|
fail('http must be a boolean.', 'ARCANE_USAGE');
|
|
874
875
|
}
|
|
875
|
-
if (http && mode !== 'source') {
|
|
876
|
-
fail('HTTP serving is supported only in source development mode.', 'ARCANE_USAGE');
|
|
877
|
-
}
|
|
878
876
|
if (http && (requestedHttps === true || (tls !== undefined && tls !== null && tls !== false)
|
|
879
877
|
|| certPath !== undefined || keyPath !== undefined || httpPort !== 0)) {
|
|
880
|
-
fail('HTTP
|
|
878
|
+
fail('HTTP serving cannot combine TLS options or a separate httpPort; select its listener with port.', 'ARCANE_USAGE');
|
|
881
879
|
}
|
|
882
880
|
const protocol = http ? 'http:' : 'https:';
|
|
883
881
|
const requestedRuntimeMode=mode==='source'&&sdkRuntimeSourceRoot!==undefined
|
|
@@ -1263,6 +1261,7 @@ async function startOwnedDevServer({
|
|
|
1263
1261
|
response.end();
|
|
1264
1262
|
return;
|
|
1265
1263
|
}
|
|
1264
|
+
if (localAI && await localAI.handler(request, response)) return;
|
|
1266
1265
|
if(request.method!=='GET'&&request.method!=='HEAD'){
|
|
1267
1266
|
deny(response,405,'Method not allowed.');
|
|
1268
1267
|
return;
|
|
@@ -1498,6 +1497,7 @@ async function startOwnedDevServer({
|
|
|
1498
1497
|
const cleanUrl=`${origin}${routeSet.startPath}`;
|
|
1499
1498
|
const url=cleanUrl;
|
|
1500
1499
|
let closeInitiated=false;
|
|
1500
|
+
let localAIClosing=Promise.resolve();
|
|
1501
1501
|
let lifecycleSettlementStarted=false;
|
|
1502
1502
|
let operationalError=null;
|
|
1503
1503
|
let resolveLifecycle;
|
|
@@ -1518,6 +1518,8 @@ async function startOwnedDevServer({
|
|
|
1518
1518
|
signal?.removeEventListener('abort',abort);
|
|
1519
1519
|
for (const listener of listeners) listener.removeListener('error', serverFailed);
|
|
1520
1520
|
try{
|
|
1521
|
+
const [localAIResult]=await Promise.allSettled([localAIClosing]);
|
|
1522
|
+
if(localAIResult.status==='rejected')operationalError??=localAIResult.reason;
|
|
1521
1523
|
while(requestTasks.size>0){
|
|
1522
1524
|
await Promise.allSettled([...requestTasks]);
|
|
1523
1525
|
}
|
|
@@ -1556,6 +1558,8 @@ async function startOwnedDevServer({
|
|
|
1556
1558
|
}
|
|
1557
1559
|
if(!closeInitiated){
|
|
1558
1560
|
closeInitiated=true;
|
|
1561
|
+
localAIClosing=Promise.resolve().then(function closeLocalAI(){return localAI?.close(error);});
|
|
1562
|
+
localAIClosing.catch(function observeLocalAICloseFailure(closeError){operationalError??=closeError;});
|
|
1559
1563
|
try{
|
|
1560
1564
|
closeDevelopmentListeners(fileServer, tlsServer).then(
|
|
1561
1565
|
function developmentServerClosed() {
|
|
@@ -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
|
+
}
|