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.
@@ -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
- for (const record of peer.requests.values()) cancel(record);
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 route(frame) {
93
- if (frame.type === 'response') {
94
- const record = requests.get(frame.id);
95
- if (record) send(record.peer, {...frame, id: record.id});
96
- return;
97
- }
98
- if (frame.type !== 'event') return;
99
- if (Object.hasOwn(frame, 'requestId')) {
100
- const owned = requests.get(frame.requestId);
101
- if (owned) send(owned.peer, {...frame, requestId: owned.id,
102
- data: frame.data?.requestId === owned.internalId ? {...frame.data, requestId: owned.id} : frame.data});
103
- return;
104
- }
105
- const id = frame.data?.requestId;
106
- const record = requests.get(id);
107
- if (record) {
108
- send(record.peer, {...frame, data: {...frame.data, requestId: record.id}});
109
- return;
110
- }
111
- // A retired transport correlation is not a new app-wide event.
112
- if (typeof id === 'string' && id.startsWith(prefix)) return;
113
- for (const peer of peers) send(peer, frame.event === 'core.state'
114
- ? {...frame, data: stateFor(peer, frame.data)} : frame);
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
- if (frame.control === 'runtime.replay') { replay(peer); return; }
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.requests.has(frame.id)) throw failure('RPC_REQUEST_ID_ACTIVE', 'The request id is already active on this connection.');
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
- const record = {peer, id: frame.id, internalId: `${prefix}${++sequence}`};
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, requests: new Map(), shutdownIds: [], closed: false, writes: Promise.resolve()};
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
- unsubscribe = runtime.onFrame(route);
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();
@@ -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 unsubscribe = runtime.onFrame(queueFrame);
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 = runtime.handle(frame);
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
- unsubscribe();
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
  '}',