arcane-os 0.83.1 → 0.85.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 +38 -0
- package/README.md +1 -1
- package/browser-runtime/ai/model-observation.mjs +315 -0
- package/docs/reference/core-native-packaging.md +35 -0
- package/docs/reference/core-runtime.md +37 -1
- package/docs/reference/core-shared-host.md +79 -10
- package/docs/reference/execution-devices.md +6 -0
- package/docs/reference/inventory/package-api.json +64 -8
- package/docs/reference/model-observation.md +220 -0
- package/docs/reference/sdk-api.md +185 -3
- package/package.json +3 -1
- package/src/core/host.mjs +1 -1
- package/src/core/runtime-connection.mjs +168 -0
- package/src/core/runtime.mjs +18 -6
- package/src/core/services/model-observation.mjs +231 -0
- package/src/core/shared-host.mjs +270 -70
- package/src/core/stdio.mjs +18 -3
- package/src/local-ai/execution-devices-windows.ps1 +3 -1
- package/src/native/portable-layout.mjs +46 -1
package/src/core/shared-host.mjs
CHANGED
|
@@ -2,10 +2,14 @@ import {spawn} from 'node:child_process';
|
|
|
2
2
|
import {open} from 'node:fs/promises';
|
|
3
3
|
import {createServer} from 'node:net';
|
|
4
4
|
import {randomUUID} from 'node:crypto';
|
|
5
|
+
import Is from 'strong-type';
|
|
5
6
|
import {CORE_PROTOCOL, CoreError, serializeCoreError} from '../../browser-runtime/core/contracts.mjs';
|
|
6
7
|
import {createCoreRuntime} from './runtime.mjs';
|
|
7
8
|
import {createCoreFrameDecoder, encodeCoreFrame} from './stdio.mjs';
|
|
8
9
|
import {connectCoreSocket, createCoreSocketConnection, writeCoreSocket as write} from './socket-connection.mjs';
|
|
10
|
+
import {createCoreRuntimeConnection} from './runtime-connection.mjs';
|
|
11
|
+
|
|
12
|
+
const is = new Is(false);
|
|
9
13
|
|
|
10
14
|
function failure(code, message) { return new CoreError({code, message}); }
|
|
11
15
|
function reportError(error) { console.error('Shared Arcane Core failed:', error); }
|
|
@@ -23,6 +27,233 @@ function response(id, result, error) {
|
|
|
23
27
|
time: new Date().toISOString()};
|
|
24
28
|
}
|
|
25
29
|
|
|
30
|
+
/** Add local IPC to an existing runtime without acquiring its lifetime. */
|
|
31
|
+
export async function startCoreListener({runtime, endpoint, onError = reportError} = {}) {
|
|
32
|
+
if (!is.string(endpoint) || !endpoint) throw new TypeError('A Core listener endpoint must be a nonempty local pipe/socket path.');
|
|
33
|
+
if (!runtime || !is.function(runtime.handle) || !is.function(runtime.onFrame)
|
|
34
|
+
|| !is.function(runtime.current) || !is.function(runtime.subscribe)) {
|
|
35
|
+
throw new TypeError('A Core listener requires an existing Core runtime.');
|
|
36
|
+
}
|
|
37
|
+
if (['draining', 'closed'].includes(runtime.current().state)) throw failure('CORE_CLOSING', 'Core is closing.');
|
|
38
|
+
const peers = new Set();
|
|
39
|
+
const prefix = `listener-${randomUUID()}:`;
|
|
40
|
+
let accepting = true;
|
|
41
|
+
let closing;
|
|
42
|
+
let terminalError;
|
|
43
|
+
let resolveClosed;
|
|
44
|
+
let rejectClosed;
|
|
45
|
+
let resolveRuntimeClosed;
|
|
46
|
+
const runtimeClosed = new Promise(
|
|
47
|
+
function observeRuntimeClosure(resolve) { resolveRuntimeClosed = resolve; }
|
|
48
|
+
);
|
|
49
|
+
const closed = new Promise(
|
|
50
|
+
function ownListener(resolve, reject) {
|
|
51
|
+
resolveClosed = resolve;
|
|
52
|
+
rejectClosed = reject;
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
closed.catch(function observeReportedListenerFailure() {});
|
|
56
|
+
|
|
57
|
+
function diagnostic(error) {
|
|
58
|
+
report(onError, error);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function disconnect(peer) {
|
|
62
|
+
if (peer.closed) return;
|
|
63
|
+
peer.closed = true;
|
|
64
|
+
peers.delete(peer);
|
|
65
|
+
peer.connection.close().catch(diagnostic);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function send(peer, frame) {
|
|
69
|
+
if (peer.closed) return;
|
|
70
|
+
let content;
|
|
71
|
+
try {
|
|
72
|
+
content = encodeCoreFrame(frame);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
diagnostic(error);
|
|
75
|
+
peer.socket.destroy();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
peer.writes = peer.writes.then(
|
|
79
|
+
function writeNext() {
|
|
80
|
+
if (!peer.closed) return write(peer.socket, content);
|
|
81
|
+
}
|
|
82
|
+
).catch(
|
|
83
|
+
function outputFailed(error) {
|
|
84
|
+
diagnostic(error);
|
|
85
|
+
peer.socket.destroy();
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function accept(socket) {
|
|
91
|
+
const peer = {socket, closed: false, writes: Promise.resolve(), connection: null};
|
|
92
|
+
peers.add(peer);
|
|
93
|
+
peer.connection = createCoreRuntimeConnection(
|
|
94
|
+
{
|
|
95
|
+
runtime,
|
|
96
|
+
requestPrefix: prefix,
|
|
97
|
+
send(frame) { send(peer, frame); }
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
const decoder = createCoreFrameDecoder(
|
|
101
|
+
function receiveFrame(frame) {
|
|
102
|
+
if (peer.closed) return;
|
|
103
|
+
if (!accepting && frame?.type === 'request') {
|
|
104
|
+
send(peer, response(frame.id, undefined, failure('CORE_CLOSING', 'The Core listener is closing.')));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// No owning-host shutdown command or application dispatcher.
|
|
108
|
+
peer.connection.handle(frame).catch(
|
|
109
|
+
function requestFailed(error) {
|
|
110
|
+
if (frame?.type === 'request') send(peer, response(frame.id, undefined, error));
|
|
111
|
+
else send(peer, event('core.error', serializeCoreError(error)));
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
socket.on(
|
|
117
|
+
'data',
|
|
118
|
+
function receiveData(chunk) {
|
|
119
|
+
try {
|
|
120
|
+
decoder.push(chunk);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
diagnostic(error);
|
|
123
|
+
socket.destroy();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
socket.on('error', diagnostic);
|
|
128
|
+
socket.on(
|
|
129
|
+
'end',
|
|
130
|
+
function inputEnded() {
|
|
131
|
+
try {
|
|
132
|
+
decoder.finish();
|
|
133
|
+
} catch (error) {
|
|
134
|
+
diagnostic(error);
|
|
135
|
+
}
|
|
136
|
+
disconnect(peer);
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
socket.on('close', function connectionClosed() { disconnect(peer); });
|
|
140
|
+
socket.resume();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const server = createServer({pauseOnConnect: true}, accept);
|
|
144
|
+
const binding = new Promise(
|
|
145
|
+
function claimEndpoint(resolve, reject) {
|
|
146
|
+
function failed(error) {
|
|
147
|
+
server.off('listening', listening);
|
|
148
|
+
reject(error);
|
|
149
|
+
}
|
|
150
|
+
function listening() {
|
|
151
|
+
server.off('error', failed);
|
|
152
|
+
resolve();
|
|
153
|
+
}
|
|
154
|
+
server.once('error', failed);
|
|
155
|
+
server.once('listening', listening);
|
|
156
|
+
server.listen({path: endpoint});
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
function close() {
|
|
161
|
+
if (closing) return closed;
|
|
162
|
+
accepting = false;
|
|
163
|
+
closing = Promise.resolve().then(
|
|
164
|
+
async function closeListener() {
|
|
165
|
+
try {
|
|
166
|
+
await binding;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
terminalError ??= error;
|
|
169
|
+
}
|
|
170
|
+
// Window/Core shutdown retains the endpoint through service
|
|
171
|
+
// drain. Closing this listener never initiates runtime.close().
|
|
172
|
+
if (runtime.current().state === 'draining') await runtimeClosed;
|
|
173
|
+
const stopped = new Promise(
|
|
174
|
+
function stopListening(resolve, reject) {
|
|
175
|
+
server.close(
|
|
176
|
+
function listenerClosed(error) {
|
|
177
|
+
if (error && error.code !== 'ERR_SERVER_NOT_RUNNING') reject(error);
|
|
178
|
+
else resolve();
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
stopped.catch(function observeListenerStopFailure() {});
|
|
184
|
+
const connected = [...peers];
|
|
185
|
+
const cancellations = await Promise.allSettled(
|
|
186
|
+
connected.map(
|
|
187
|
+
function detachConnection(peer) {
|
|
188
|
+
peer.socket.pause();
|
|
189
|
+
return peer.connection.close();
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
);
|
|
193
|
+
for (const outcome of cancellations) {
|
|
194
|
+
if (outcome.status === 'rejected') {
|
|
195
|
+
terminalError = terminalError
|
|
196
|
+
? new AggregateError([terminalError, outcome.reason], 'Core listener closure failed.')
|
|
197
|
+
: outcome.reason;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
await Promise.all(connected.map(function flushConnection(peer) { return peer.writes; }));
|
|
201
|
+
for (const peer of connected) {
|
|
202
|
+
peer.socket.resume();
|
|
203
|
+
peer.socket.end();
|
|
204
|
+
}
|
|
205
|
+
await stopped;
|
|
206
|
+
if (terminalError) throw terminalError;
|
|
207
|
+
return {state: 'closed', endpoint};
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
closing.then(
|
|
211
|
+
function listenerClosed(result) {
|
|
212
|
+
unsubscribe();
|
|
213
|
+
resolveClosed(result);
|
|
214
|
+
},
|
|
215
|
+
function listenerFailed(error) {
|
|
216
|
+
unsubscribe();
|
|
217
|
+
diagnostic(error);
|
|
218
|
+
rejectClosed(error);
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
return closed;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const unsubscribe = runtime.subscribe(
|
|
225
|
+
function runtimeChanged(state) {
|
|
226
|
+
if (state.state === 'draining') accepting = false;
|
|
227
|
+
if (state.state === 'closed') {
|
|
228
|
+
resolveRuntimeClosed();
|
|
229
|
+
close();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
server.on(
|
|
234
|
+
'error',
|
|
235
|
+
function listenerFailed(error) {
|
|
236
|
+
terminalError = error;
|
|
237
|
+
close();
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
try {
|
|
241
|
+
await binding;
|
|
242
|
+
if (!accepting) {
|
|
243
|
+
await close();
|
|
244
|
+
throw failure('CORE_CLOSING', 'Core closed while its listener was starting.');
|
|
245
|
+
}
|
|
246
|
+
return {endpoint, closed, close};
|
|
247
|
+
} catch (error) {
|
|
248
|
+
try {
|
|
249
|
+
await close();
|
|
250
|
+
} catch (closeError) {
|
|
251
|
+
if (closeError !== error) throw new AggregateError([error, closeError], 'Core listener startup and cleanup failed.');
|
|
252
|
+
}
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
26
257
|
/** Claim one app-selected pipe/socket before importing or creating services. */
|
|
27
258
|
export async function startSharedCoreHost({
|
|
28
259
|
endpoint, application, version, configure, getReplayEvents, signal, onError = reportError
|
|
@@ -30,13 +261,10 @@ export async function startSharedCoreHost({
|
|
|
30
261
|
signal?.throwIfAborted();
|
|
31
262
|
if (typeof endpoint !== 'string' || !endpoint) throw new TypeError('A shared Core endpoint must be a nonempty local pipe/socket path.');
|
|
32
263
|
const peers = new Set();
|
|
33
|
-
const requests = new Map();
|
|
34
264
|
const incoming = new Set();
|
|
35
265
|
const lifetime = new AbortController();
|
|
36
266
|
const prefix = `shared-${randomUUID()}:`;
|
|
37
|
-
let sequence = 0;
|
|
38
267
|
let runtime;
|
|
39
|
-
let unsubscribe;
|
|
40
268
|
let closing;
|
|
41
269
|
let preparation = Promise.resolve();
|
|
42
270
|
let ready = false;
|
|
@@ -65,89 +293,61 @@ export async function startSharedCoreHost({
|
|
|
65
293
|
if (!peer.closed) return write(peer.socket, content);
|
|
66
294
|
}).catch(function outputFailed(error) { diagnostic(error); peer.socket.destroy(); });
|
|
67
295
|
}
|
|
68
|
-
function cancel(record) {
|
|
69
|
-
observe(runtime.handle({protocol: CORE_PROTOCOL, type: 'control',
|
|
70
|
-
control: 'request.cancel', requestId: record.internalId}));
|
|
71
|
-
}
|
|
72
296
|
function disconnect(peer) {
|
|
73
297
|
if (peer.closed) return;
|
|
74
298
|
peer.closed = true;
|
|
75
299
|
peers.delete(peer);
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
function stateFor(peer, state) {
|
|
79
|
-
return {...state, activeRequests: state.activeRequests.map(function requestState(record) {
|
|
80
|
-
const owned = requests.get(record.id);
|
|
81
|
-
return owned?.peer === peer ? {...record, id: owned.id} : record;
|
|
82
|
-
})};
|
|
83
|
-
}
|
|
84
|
-
function replay(peer) {
|
|
85
|
-
const state = runtime.current();
|
|
86
|
-
if (state.state === 'ready') send(peer, event('core.ready', {version: state.version, app: state.application}));
|
|
87
|
-
send(peer, event('core.state', stateFor(peer, state)));
|
|
88
|
-
for (const service of state.services) send(peer, event('core.service.state', service));
|
|
89
|
-
// Domain snapshots belong to the app/service owner, never event history.
|
|
90
|
-
for (const snapshot of getReplayEvents?.() ?? []) send(peer, event(snapshot.event, snapshot.data));
|
|
300
|
+
if (peer.connection) observe(peer.connection.close());
|
|
91
301
|
}
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
302
|
+
function attach(peer) {
|
|
303
|
+
peer.connection = createCoreRuntimeConnection(
|
|
304
|
+
{
|
|
305
|
+
runtime,
|
|
306
|
+
requestPrefix: prefix,
|
|
307
|
+
preserveContextRequestId: false,
|
|
308
|
+
getReplayEvents,
|
|
309
|
+
send(frame) { send(peer, frame); },
|
|
310
|
+
mapFrame(frame, record) {
|
|
311
|
+
// Preserve this owning adapter's established data correlation.
|
|
312
|
+
// The borrowed listener and stdio never apply this mapping.
|
|
313
|
+
if (record) {
|
|
314
|
+
return frame.type === 'event' && frame.data?.requestId === record.internalId
|
|
315
|
+
? {...frame, data: {...frame.data, requestId: record.id}} : frame;
|
|
316
|
+
}
|
|
317
|
+
if (frame.event === 'core.state') {
|
|
318
|
+
return {...frame, data: {...frame.data, activeRequests: frame.data.activeRequests.map(
|
|
319
|
+
function requestState(request) {
|
|
320
|
+
const owned = peer.connection.request(request.id);
|
|
321
|
+
return owned ? {...request, id: owned.id} : request;
|
|
322
|
+
}
|
|
323
|
+
)}};
|
|
324
|
+
}
|
|
325
|
+
const id = frame.data?.requestId;
|
|
326
|
+
const owned = peer.connection.request(id);
|
|
327
|
+
if (owned) return {...frame, data: {...frame.data, requestId: owned.id}};
|
|
328
|
+
// A retired transport correlation is not a new app-wide event.
|
|
329
|
+
if (is.string(id) && id.startsWith(prefix)) return;
|
|
330
|
+
return frame;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
);
|
|
115
334
|
}
|
|
116
335
|
async function dispatch(peer, frame) {
|
|
117
336
|
if (frame?.protocol !== CORE_PROTOCOL) throw failure('INVALID_RPC_REQUEST', 'Unknown Core protocol.');
|
|
118
337
|
if (frame.type === 'control') {
|
|
119
|
-
|
|
120
|
-
if (frame.control === 'request.cancel') {
|
|
121
|
-
const record = peer.requests.get(frame.requestId);
|
|
122
|
-
if (record) cancel(record);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
if (frame.control === 'requests.cancelAll') {
|
|
126
|
-
for (const record of peer.requests.values()) cancel(record);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
throw failure('INVALID_RPC_CONTROL', 'Unknown Core control.');
|
|
338
|
+
return peer.connection.handle(frame);
|
|
130
339
|
}
|
|
131
340
|
if (frame.type !== 'request' || typeof frame.id !== 'string' || !frame.id || typeof frame.method !== 'string') {
|
|
132
341
|
throw failure('INVALID_RPC_REQUEST', 'A Core request requires an id and method.');
|
|
133
342
|
}
|
|
134
|
-
if (peer.
|
|
343
|
+
if (peer.connection.has(frame.id)) throw failure('RPC_REQUEST_ID_ACTIVE', 'The request id is already active on this connection.');
|
|
135
344
|
if (frame.method === 'core.host.shutdown') {
|
|
136
345
|
peer.shutdownIds.push(frame.id);
|
|
137
346
|
close();
|
|
138
347
|
return;
|
|
139
348
|
}
|
|
140
349
|
if (closing) throw failure('CORE_CLOSING', 'Core is closing.');
|
|
141
|
-
|
|
142
|
-
peer.requests.set(record.id, record);
|
|
143
|
-
requests.set(record.internalId, record);
|
|
144
|
-
try {
|
|
145
|
-
// Only protocol correlation changes; parameters remain untouched.
|
|
146
|
-
await runtime.handle({...frame, id: record.internalId});
|
|
147
|
-
} finally {
|
|
148
|
-
peer.requests.delete(record.id);
|
|
149
|
-
requests.delete(record.internalId);
|
|
150
|
-
}
|
|
350
|
+
return peer.connection.handle(frame);
|
|
151
351
|
}
|
|
152
352
|
function receive(peer, frame) {
|
|
153
353
|
if (peer.closed) return;
|
|
@@ -157,8 +357,9 @@ export async function startSharedCoreHost({
|
|
|
157
357
|
}));
|
|
158
358
|
}
|
|
159
359
|
function accept(socket) {
|
|
160
|
-
const peer = {socket,
|
|
360
|
+
const peer = {socket, connection: null, shutdownIds: [], closed: false, writes: Promise.resolve()};
|
|
161
361
|
peers.add(peer);
|
|
362
|
+
if (runtime) attach(peer);
|
|
162
363
|
const decoder = createCoreFrameDecoder(function receiveFrame(frame) { receive(peer, frame); });
|
|
163
364
|
socket.on('data', function data(chunk) {
|
|
164
365
|
try { decoder.push(chunk); } catch (error) { diagnostic(error); socket.destroy(); }
|
|
@@ -196,7 +397,6 @@ export async function startSharedCoreHost({
|
|
|
196
397
|
await Promise.all([...peers].map(function drainWrites(peer) { return peer.writes; }));
|
|
197
398
|
for (const peer of peers) { peer.socket.resume(); peer.socket.end(); }
|
|
198
399
|
await stopped;
|
|
199
|
-
unsubscribe?.();
|
|
200
400
|
signal?.removeEventListener('abort', abortHost);
|
|
201
401
|
if (error) throw error;
|
|
202
402
|
return runtime?.current() ?? {state: 'closed'};
|
|
@@ -222,7 +422,7 @@ export async function startSharedCoreHost({
|
|
|
222
422
|
// The endpoint is already owned. A losing launcher never calls configure.
|
|
223
423
|
signal?.throwIfAborted();
|
|
224
424
|
runtime = createCoreRuntime({application, version});
|
|
225
|
-
|
|
425
|
+
for (const peer of peers) attach(peer);
|
|
226
426
|
signal?.addEventListener('abort', abortHost, {once: true});
|
|
227
427
|
preparation = Promise.resolve().then(function configureClaimedHost() {
|
|
228
428
|
lifetime.signal.throwIfAborted();
|
package/src/core/stdio.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {finished} from 'node:stream';
|
|
2
2
|
import Is from 'strong-type';
|
|
3
3
|
import {CoreError, serializeCoreError} from '../../browser-runtime/core/contracts.mjs';
|
|
4
|
+
import {createCoreRuntimeConnection} from './runtime-connection.mjs';
|
|
4
5
|
|
|
5
6
|
const is = new Is(false);
|
|
6
7
|
|
|
@@ -116,10 +117,24 @@ export function startCoreStdio({runtime, input = process.stdin, output = process
|
|
|
116
117
|
).catch(failOutput);
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
const
|
|
120
|
+
const connection = createCoreRuntimeConnection(
|
|
121
|
+
{
|
|
122
|
+
runtime,
|
|
123
|
+
send: queueFrame,
|
|
124
|
+
mapFrame(frame) {
|
|
125
|
+
if (frame.event !== 'core.state') return frame;
|
|
126
|
+
return {...frame, data: {...frame.data, activeRequests: frame.data.activeRequests.map(
|
|
127
|
+
function requestState(request) {
|
|
128
|
+
const owned = connection.request(request.id);
|
|
129
|
+
return owned ? {...request, id: owned.id} : request;
|
|
130
|
+
}
|
|
131
|
+
)}};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
);
|
|
120
135
|
|
|
121
136
|
function receiveFrame(frame) {
|
|
122
|
-
const task =
|
|
137
|
+
const task = connection.handle(frame);
|
|
123
138
|
pending.add(task);
|
|
124
139
|
task.then(
|
|
125
140
|
function requestSettled() {
|
|
@@ -157,7 +172,7 @@ export function startCoreStdio({runtime, input = process.stdin, output = process
|
|
|
157
172
|
}
|
|
158
173
|
|
|
159
174
|
async function releaseTransportListeners() {
|
|
160
|
-
|
|
175
|
+
await connection.close();
|
|
161
176
|
output.off('error', failOutput);
|
|
162
177
|
// destroy(error) marks a stream destroyed before its scheduled error
|
|
163
178
|
// and close events. The native completion owner observes their delivery;
|
|
@@ -107,8 +107,10 @@ function Get-PresentPciDevices {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
if ($devicesById.Count -gt 0) {
|
|
110
|
+
# Concurrent CIM property operations can duplicate some device results
|
|
111
|
+
# and omit others. Serialize only this query's internal operations.
|
|
110
112
|
$properties = @(
|
|
111
|
-
Get-PnpDeviceProperty -InstanceId ([string[]]@($devicesById.Keys)) -KeyName 'DEVPKEY_Device_BusNumber', 'DEVPKEY_Device_Address' -ErrorAction Continue 2>&1 3>&1
|
|
113
|
+
Get-PnpDeviceProperty -InstanceId ([string[]]@($devicesById.Keys)) -KeyName 'DEVPKEY_Device_BusNumber', 'DEVPKEY_Device_Address' -ThrottleLimit 1 -ErrorAction Continue 2>&1 3>&1
|
|
112
114
|
)
|
|
113
115
|
foreach ($property in $properties) {
|
|
114
116
|
if ($property -is [System.Management.Automation.ErrorRecord]) {
|
|
@@ -203,7 +203,8 @@ export function coreEntrySource(application, version, services, {localAI, runtim
|
|
|
203
203
|
"import path from 'node:path';",
|
|
204
204
|
"import {isSea} from 'node:sea';",
|
|
205
205
|
...(launchDefaults === undefined ? [] : ["import {mkdir} from 'node:fs/promises';"]),
|
|
206
|
-
"import {readCoreLaunchContext, startCoreHost, runSharedCoreHost, startSharedCoreBridge} from 'arcane-os/core/host';",
|
|
206
|
+
"import {readCoreLaunchContext, startCoreHost, startCoreListener, runSharedCoreHost, startSharedCoreBridge} from 'arcane-os/core/host';",
|
|
207
|
+
"import {createModelObservationService} from 'arcane-os/core/model-observation';",
|
|
207
208
|
'',
|
|
208
209
|
...(localAI === undefined ? [] : [
|
|
209
210
|
'// Bundled runtime paths are artifact-root-relative POSIX paths.',
|
|
@@ -249,6 +250,10 @@ export function coreEntrySource(application, version, services, {localAI, runtim
|
|
|
249
250
|
: ` ...await readCoreLaunchContext({appId: ${JSON.stringify(application.id)}, defaults: JSON.parse(${JSON.stringify(JSON.stringify(launchDefaults))})})`,
|
|
250
251
|
'};',
|
|
251
252
|
'',
|
|
253
|
+
'if (context.coreListener !== undefined && context.sharedHost !== undefined) {',
|
|
254
|
+
" throw new TypeError('coreListener attaches to the window-owned Core; sharedHost selects its separate owning host.');",
|
|
255
|
+
'}',
|
|
256
|
+
'',
|
|
252
257
|
...(launchDefaults === undefined ? [] : [
|
|
253
258
|
'// The shared endpoint and diagnostic log require their selected state directory.',
|
|
254
259
|
'if (context.sharedHost !== undefined) await mkdir(context.stateRoot, {recursive: true});',
|
|
@@ -284,10 +289,44 @@ export function coreEntrySource(application, version, services, {localAI, runtim
|
|
|
284
289
|
`const application = JSON.parse(${JSON.stringify(JSON.stringify(application))});`,
|
|
285
290
|
`const version = ${JSON.stringify(version)};`,
|
|
286
291
|
'let host;',
|
|
292
|
+
'let listener;',
|
|
287
293
|
'if (context.sharedHost === undefined) {',
|
|
288
294
|
' const services = [];',
|
|
289
295
|
' await createServices(function register(service) { services.push(service); });',
|
|
290
296
|
' host = startCoreHost({application, version, services});',
|
|
297
|
+
' if (context.coreListener !== undefined) {',
|
|
298
|
+
" process.on('SIGINT', closeCore);",
|
|
299
|
+
" process.on('SIGTERM', closeCore);",
|
|
300
|
+
' try {',
|
|
301
|
+
' const observation = createModelObservationService(',
|
|
302
|
+
' {',
|
|
303
|
+
' runtime: host.runtime,',
|
|
304
|
+
' localAI: services.find(',
|
|
305
|
+
" function localAIOwner(service) { return Object.hasOwn(service.methods ?? {}, 'localai.status'); }",
|
|
306
|
+
' ) ?? null,',
|
|
307
|
+
' image: services.find(',
|
|
308
|
+
" function imageOwner(service) { return Object.hasOwn(service.methods ?? {}, 'image.status'); }",
|
|
309
|
+
' ) ?? null,',
|
|
310
|
+
' decisions: services.find(',
|
|
311
|
+
" function decisionOwner(service) { return Object.hasOwn(service.methods ?? {}, 'decisions.status'); }",
|
|
312
|
+
' ) ?? null',
|
|
313
|
+
' }',
|
|
314
|
+
' );',
|
|
315
|
+
' host.runtime.registerService(observation);',
|
|
316
|
+
' listener = await startCoreListener({runtime: host.runtime, endpoint: context.coreListener?.endpoint});',
|
|
317
|
+
' listener.closed.catch(function listenerFailed() { process.exitCode = 1; });',
|
|
318
|
+
' } catch (error) {',
|
|
319
|
+
' try {',
|
|
320
|
+
' await host.close();',
|
|
321
|
+
' } catch (closeError) {',
|
|
322
|
+
" throw new AggregateError([error, closeError], 'Core listener startup and host drain failed.');",
|
|
323
|
+
' }',
|
|
324
|
+
' throw error;',
|
|
325
|
+
' } finally {',
|
|
326
|
+
" process.off('SIGINT', closeCore);",
|
|
327
|
+
" process.off('SIGTERM', closeCore);",
|
|
328
|
+
' }',
|
|
329
|
+
' }',
|
|
291
330
|
"} else if (process.argv.includes('--arcane-core-headless')) {",
|
|
292
331
|
' host = await runSharedCoreHost({',
|
|
293
332
|
' endpoint: context.sharedHost.endpoint, application, version,',
|
|
@@ -319,6 +358,12 @@ export function coreEntrySource(application, version, services, {localAI, runtim
|
|
|
319
358
|
" console.error('Arcane Core host failed:', error);",
|
|
320
359
|
' process.exitCode = 1;',
|
|
321
360
|
'} finally {',
|
|
361
|
+
' try {',
|
|
362
|
+
' await listener?.close();',
|
|
363
|
+
' } catch {',
|
|
364
|
+
' // The listener reports its complete failure through onError.',
|
|
365
|
+
' process.exitCode = 1;',
|
|
366
|
+
' }',
|
|
322
367
|
" process.off('SIGINT', closeCore);",
|
|
323
368
|
" process.off('SIGTERM', closeCore);",
|
|
324
369
|
'}',
|