arcane-os 0.55.0 → 0.57.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 +51 -0
- package/README.md +5 -3
- package/browser-runtime/ai/core-local.mjs +306 -0
- package/browser-runtime/core/client.mjs +75 -14
- package/browser-runtime/core/contracts.mjs +2 -1
- package/docs/consumer-updates.md +82 -0
- package/docs/publishing.md +41 -18
- package/docs/reference/README.md +31 -1
- package/docs/reference/availability-and-normalization.md +11 -3
- package/docs/reference/cli.md +9 -1
- package/docs/reference/core-client.md +45 -2
- package/docs/reference/core-native-packaging.md +195 -11
- package/docs/reference/core-runtime.md +10 -0
- package/docs/reference/inventory/package-api.json +160 -20
- package/docs/reference/local-ai.md +197 -0
- package/docs/reference/protocols.md +91 -14
- package/docs/reference/pwa.md +17 -0
- package/docs/reference/runtime-modules.md +6 -5
- package/docs/reference/sdk-api.md +165 -26
- package/package.json +5 -1
- package/runtime/arcane/modules/LocalAIReadiness.js +3 -1
- package/schemas/arcane-app.schema.json +64 -0
- package/schemas/arcane-package.schema.json +54 -0
- package/src/app-descriptor.mjs +35 -2
- package/src/app-layout.mjs +6 -2
- package/src/cli/main.mjs +8 -2
- package/src/core/host.mjs +23 -0
- package/src/core/hosts/arcane-core-loader.cjs +2 -0
- package/src/core/hosts/linux/arcane_core_process.c +568 -0
- package/src/core/hosts/linux/arcane_core_process.h +127 -0
- package/src/core/hosts/macos/ArcaneCoreProcess.h +85 -0
- package/src/core/hosts/macos/ArcaneCoreProcess.m +357 -0
- package/src/core/hosts/sea-launcher.cjs +17 -0
- package/src/core/hosts/windows/ArcaneCoreProcess.cs +416 -0
- 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/runtime.mjs +15 -3
- package/src/core/services/local-ai.mjs +543 -0
- package/src/core-classic-source.mjs +2 -2
- package/src/dev-server.mjs +7 -0
- package/src/import-map.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 +344 -0
- package/src/local-ai/server.mjs +262 -0
- package/src/native/portable-layout.mjs +29 -4
- package/src/native/portable-provider.mjs +39 -8
- package/src/native/windows-provider.mjs +262 -0
- package/src/native-provider-loader.mjs +8 -2
- package/src/packager/core.mjs +89 -15
- package/src/process.mjs +74 -12
- package/src/toolchain.mjs +38 -8
package/src/process.mjs
CHANGED
|
@@ -33,8 +33,8 @@ function platformArguments(command,args,platform=process.platform){
|
|
|
33
33
|
return [cliPath,...args];
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function appendOutput(current,
|
|
37
|
-
return current+
|
|
36
|
+
function appendOutput(current,text){
|
|
37
|
+
return current+text;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async function emitLines(events,type,text){
|
|
@@ -45,9 +45,9 @@ async function emitLines(events,type,text){
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function deliverChunk(stream,events,type,
|
|
48
|
+
function deliverChunk(stream,events,type,text){
|
|
49
49
|
stream.pause();
|
|
50
|
-
return emitLines(events,type,
|
|
50
|
+
return emitLines(events,type,text).catch(()=>{
|
|
51
51
|
// The queue owns and propagates the first callback failure.
|
|
52
52
|
}).finally(()=>{
|
|
53
53
|
if(!stream.destroyed){
|
|
@@ -124,12 +124,16 @@ export async function runProcess(command,args=[],{
|
|
|
124
124
|
heartbeatMs=5000,
|
|
125
125
|
terminationGraceMs=DEFAULT_TERMINATION_GRACE_MS,
|
|
126
126
|
allowNonzero=false,
|
|
127
|
-
input
|
|
127
|
+
input,
|
|
128
|
+
cancellationMode='terminate-tree'
|
|
128
129
|
}={}){
|
|
129
130
|
throwIfAborted(signal);
|
|
130
131
|
if(!is.array(args)||args.some(argument=>!is.string(argument))){
|
|
131
132
|
throw new ArcaneError(ERROR_CODES.usage,'Process arguments must be a fixed array of strings.');
|
|
132
133
|
}
|
|
134
|
+
if(cancellationMode!=='terminate-tree'&&cancellationMode!=='close-input'){
|
|
135
|
+
throw new ArcaneError(ERROR_CODES.usage,'Unknown process cancellation mode.');
|
|
136
|
+
}
|
|
133
137
|
if(!is.integer(terminationGraceMs)||terminationGraceMs<100||terminationGraceMs>30_000){
|
|
134
138
|
throw new ArcaneError(
|
|
135
139
|
ERROR_CODES.usage,
|
|
@@ -153,12 +157,15 @@ export async function runProcess(command,args=[],{
|
|
|
153
157
|
return new Promise((resolve,reject)=>{
|
|
154
158
|
let stdout='';
|
|
155
159
|
let stderr='';
|
|
160
|
+
let childClosed=false;
|
|
161
|
+
let closedResult=null;
|
|
156
162
|
let settlementStarted=false;
|
|
157
163
|
let cancellationRequested=false;
|
|
158
164
|
let cancellationError=null;
|
|
159
165
|
let terminationRequested=false;
|
|
160
166
|
let escalation=null;
|
|
161
167
|
let spawnError=null;
|
|
168
|
+
let inputError=null;
|
|
162
169
|
let child;
|
|
163
170
|
let heartbeat=null;
|
|
164
171
|
const deliveries=new Set();
|
|
@@ -184,9 +191,37 @@ export async function runProcess(command,args=[],{
|
|
|
184
191
|
clearTimeout(escalation);
|
|
185
192
|
signal?.removeEventListener('abort',abort);
|
|
186
193
|
void (async()=>{
|
|
194
|
+
let callbackFailure=null;
|
|
187
195
|
try{
|
|
188
196
|
await events.drain();
|
|
189
|
-
}catch(
|
|
197
|
+
}catch(error){
|
|
198
|
+
callbackFailure=error;
|
|
199
|
+
}
|
|
200
|
+
if(cancellationMode==='close-input'&&closedResult){
|
|
201
|
+
const failures=[...new Set([
|
|
202
|
+
callback===reject?value:null,
|
|
203
|
+
callbackFailure,inputError,cancellationError,spawnError
|
|
204
|
+
].filter(error=>error!==null))];
|
|
205
|
+
if(failures.length>0){
|
|
206
|
+
const primary=failures[0];
|
|
207
|
+
// Owned cancellation/exit errors already carry this exact
|
|
208
|
+
// result. Other errors retain their identity and cause in
|
|
209
|
+
// the complete failure record instead of being rewritten.
|
|
210
|
+
if(failures.length===1&&primary?.details===closedResult){
|
|
211
|
+
reject(primary);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const failure=new ArcaneError(
|
|
215
|
+
primary?.code??ERROR_CODES.operationFailed,
|
|
216
|
+
primary?.message??`Could not complete ${command}.`,
|
|
217
|
+
{cause:primary,details:closedResult,exitCode:primary?.exitCode}
|
|
218
|
+
);
|
|
219
|
+
failure.errors=failures;
|
|
220
|
+
reject(failure);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if(callbackFailure){
|
|
190
225
|
reject(callbackFailure);
|
|
191
226
|
return;
|
|
192
227
|
}
|
|
@@ -195,10 +230,16 @@ export async function runProcess(command,args=[],{
|
|
|
195
230
|
};
|
|
196
231
|
|
|
197
232
|
const stopTree=()=>{
|
|
198
|
-
if(terminationRequested||settlementStarted){
|
|
233
|
+
if(terminationRequested||settlementStarted||childClosed){
|
|
199
234
|
return;
|
|
200
235
|
}
|
|
201
236
|
terminationRequested=true;
|
|
237
|
+
if(cancellationMode==='close-input'){
|
|
238
|
+
// The selected host owns shutdown after EOF. Keep observing its
|
|
239
|
+
// complete output and exit; accepted durable work may outlive UI.
|
|
240
|
+
child?.stdin.end();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
202
243
|
terminateProcessTree(child);
|
|
203
244
|
escalation=setTimeout(()=>{
|
|
204
245
|
if(childIsRunning(child)){
|
|
@@ -214,7 +255,7 @@ export async function runProcess(command,args=[],{
|
|
|
214
255
|
};
|
|
215
256
|
|
|
216
257
|
const abort=()=>{
|
|
217
|
-
if(cancellationRequested||settlementStarted){
|
|
258
|
+
if(cancellationRequested||settlementStarted||childClosed){
|
|
218
259
|
return;
|
|
219
260
|
}
|
|
220
261
|
cancellationRequested=true;
|
|
@@ -225,7 +266,9 @@ export async function runProcess(command,args=[],{
|
|
|
225
266
|
);
|
|
226
267
|
void events.enqueue({
|
|
227
268
|
type:'process.cancellation.requested',
|
|
228
|
-
message
|
|
269
|
+
message:cancellationMode==='close-input'
|
|
270
|
+
?`Closing ${command} input and waiting for its owned shutdown.`
|
|
271
|
+
:`Stopping ${command} and its child processes.`,
|
|
229
272
|
data:{command,pid:child?.pid??null}
|
|
230
273
|
});
|
|
231
274
|
stopTree();
|
|
@@ -263,6 +306,10 @@ export async function runProcess(command,args=[],{
|
|
|
263
306
|
return;
|
|
264
307
|
}
|
|
265
308
|
|
|
309
|
+
// Each readable retains split UTF-8 sequences and flushes at EOF.
|
|
310
|
+
// Capture and events consume the same decoded text.
|
|
311
|
+
child.stdout.setEncoding('utf8');
|
|
312
|
+
child.stderr.setEncoding('utf8');
|
|
266
313
|
child.stdout.on('data',chunk=>{
|
|
267
314
|
stdout=appendOutput(stdout,chunk);
|
|
268
315
|
ownDelivery(child.stdout,'process.stdout',chunk);
|
|
@@ -282,6 +329,10 @@ export async function runProcess(command,args=[],{
|
|
|
282
329
|
}
|
|
283
330
|
});
|
|
284
331
|
child.on('close',(code,terminationSignal)=>{
|
|
332
|
+
// The child lifetime has ended even while its output/event callbacks
|
|
333
|
+
// are still draining. A later abort cannot cancel that completed work.
|
|
334
|
+
childClosed=true;
|
|
335
|
+
signal?.removeEventListener('abort',abort);
|
|
285
336
|
clearInterval(heartbeat);
|
|
286
337
|
void (async()=>{
|
|
287
338
|
await drainDeliveries();
|
|
@@ -295,10 +346,11 @@ export async function runProcess(command,args=[],{
|
|
|
295
346
|
stdout,
|
|
296
347
|
stderr
|
|
297
348
|
};
|
|
349
|
+
closedResult=result;
|
|
298
350
|
if(terminationRequested){
|
|
299
351
|
// The direct child has exited. Make one final tree-wide attempt so
|
|
300
352
|
// a descendant that ignored the graceful request cannot outlive it.
|
|
301
|
-
terminateProcessTree(child,{force:true});
|
|
353
|
+
if(cancellationMode!=='close-input')terminateProcessTree(child,{force:true});
|
|
302
354
|
if(cancellationRequested){
|
|
303
355
|
await events.enqueue({
|
|
304
356
|
type:'process.cancelled',
|
|
@@ -306,7 +358,8 @@ export async function runProcess(command,args=[],{
|
|
|
306
358
|
data:{command,code:result.code,signal:terminationSignal}
|
|
307
359
|
});
|
|
308
360
|
}
|
|
309
|
-
|
|
361
|
+
if(cancellationError)cancellationError.details=result;
|
|
362
|
+
finish(reject,events.error??inputError??cancellationError??new ArcaneError(
|
|
310
363
|
ERROR_CODES.operationFailed,
|
|
311
364
|
`Stopped ${command} after its event callback failed.`
|
|
312
365
|
));
|
|
@@ -320,6 +373,12 @@ export async function runProcess(command,args=[],{
|
|
|
320
373
|
));
|
|
321
374
|
return;
|
|
322
375
|
}
|
|
376
|
+
if(inputError){
|
|
377
|
+
finish(reject,new ArcaneError(ERROR_CODES.operationFailed,
|
|
378
|
+
`Could not write ${command} input: ${inputError.message}`,
|
|
379
|
+
{cause:inputError,details:result}));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
323
382
|
await events.enqueue({
|
|
324
383
|
type:'process.completed',
|
|
325
384
|
message:`${command} exited with code ${String(result.code)}.`,
|
|
@@ -342,7 +401,10 @@ export async function runProcess(command,args=[],{
|
|
|
342
401
|
abort();
|
|
343
402
|
}
|
|
344
403
|
|
|
345
|
-
if(input
|
|
404
|
+
if(cancellationMode==='close-input'){
|
|
405
|
+
child.stdin.on('error',error=>{inputError=error;stopTree();});
|
|
406
|
+
if(input!==undefined&&!terminationRequested)child.stdin.write(input);
|
|
407
|
+
}else if(input!==undefined){
|
|
346
408
|
child.stdin.end(input);
|
|
347
409
|
}else{
|
|
348
410
|
child.stdin.end();
|
package/src/toolchain.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Is from 'strong-type';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {resolvePackageOutputRoot} from './app-layout.mjs';
|
|
3
|
+
import {resolveAppRoot,resolvePackageOutputRoot} from './app-layout.mjs';
|
|
4
4
|
import {readdir,lstat,realpath,mkdir,readFile,writeFile} from 'node:fs/promises';
|
|
5
5
|
import {createWorkspace,initWorkspace} from './scaffold.mjs';
|
|
6
6
|
import {
|
|
@@ -17,6 +17,8 @@ import {createPwaArtifacts} from './pwa.mjs';
|
|
|
17
17
|
import {readInstalledSdkLayout} from './sdk-runtime-layout.mjs';
|
|
18
18
|
import {withWorkspaceOperationLock} from './workspace-operation-lock.mjs';
|
|
19
19
|
import {refreshAppPackageProjection} from './app-descriptor.mjs';
|
|
20
|
+
import {configureDevelopmentLocalAI} from './local-ai/config.mjs';
|
|
21
|
+
import {createDevelopmentLocalAI} from './local-ai/dev.mjs';
|
|
20
22
|
import {
|
|
21
23
|
discoverApps as discoverPackagerApps,
|
|
22
24
|
inspectApp as inspectPackagedApp,
|
|
@@ -566,6 +568,11 @@ export async function developApplication(options = {}) {
|
|
|
566
568
|
onEvent: options.onEvent
|
|
567
569
|
},
|
|
568
570
|
async function refreshDevelopmentWorkspace(workspaceOperationLease) {
|
|
571
|
+
await configureDevelopmentLocalAI({
|
|
572
|
+
appRoot: resolveAppRoot(profile.workspaceRoot, profile.config, appId),
|
|
573
|
+
runtimes: options.localAI,
|
|
574
|
+
signal: options.signal
|
|
575
|
+
});
|
|
569
576
|
await refreshAppPackageProjection(
|
|
570
577
|
{
|
|
571
578
|
workspaceRoot: profile.workspaceRoot,
|
|
@@ -589,7 +596,19 @@ export async function developApplication(options = {}) {
|
|
|
589
596
|
return current;
|
|
590
597
|
}
|
|
591
598
|
);
|
|
592
|
-
const
|
|
599
|
+
const localAIConfig = prepared.descriptor?.native?.localAI;
|
|
600
|
+
const localAI = localAIConfig?.runtimes.length ? createDevelopmentLocalAI({
|
|
601
|
+
config: localAIConfig,
|
|
602
|
+
appRoot: prepared.appRoot,
|
|
603
|
+
directory: path.join(prepared.appRoot, '.arcane', 'local-ai', 'runtimes'),
|
|
604
|
+
application: prepared.descriptor,
|
|
605
|
+
version: prepared.descriptor.version,
|
|
606
|
+
signal: options.signal,
|
|
607
|
+
onEvent: options.onEvent
|
|
608
|
+
}) : undefined;
|
|
609
|
+
let server;
|
|
610
|
+
try {
|
|
611
|
+
server=await startDevServer({
|
|
593
612
|
workspaceRoot:prepared.workspaceRoot,
|
|
594
613
|
appId:prepared.appId,
|
|
595
614
|
mode:'source',
|
|
@@ -605,10 +624,19 @@ export async function developApplication(options = {}) {
|
|
|
605
624
|
tls:options.tls,
|
|
606
625
|
certPath:options.certPath,
|
|
607
626
|
keyPath:options.keyPath,
|
|
627
|
+
localAI,
|
|
608
628
|
signal:options.signal,
|
|
609
629
|
onEvent:options.onEvent
|
|
610
|
-
|
|
611
|
-
|
|
630
|
+
});
|
|
631
|
+
} catch (error) {
|
|
632
|
+
try {
|
|
633
|
+
await localAI?.close(error);
|
|
634
|
+
} catch (closeError) {
|
|
635
|
+
throw new AggregateError([error, closeError], 'Development startup and local AI shutdown failed.');
|
|
636
|
+
}
|
|
637
|
+
throw error;
|
|
638
|
+
}
|
|
639
|
+
return {...server,appId:prepared.appId,mode:'source',...(localAI ? {localAI} : {})};
|
|
612
640
|
}
|
|
613
641
|
|
|
614
642
|
export async function packageApplication(options={}){
|
|
@@ -840,7 +868,7 @@ export function assertIntegratedNativeToolchain({
|
|
|
840
868
|
workspaceMode,workspaceRoot,toolchainRoot,target,providerSource='arcane-checkout'
|
|
841
869
|
}={}){
|
|
842
870
|
if(workspaceMode!=='integrated')return;
|
|
843
|
-
if(providerSource==='sdk-package'&&target==='portable')return;
|
|
871
|
+
if(providerSource==='sdk-package'&&(target==='portable'||target==='windows-x64'))return;
|
|
844
872
|
if(!is.string(toolchainRoot)||!sameCanonicalPath(workspaceRoot,toolchainRoot)){
|
|
845
873
|
throw new ArcaneError(
|
|
846
874
|
ERROR_CODES.policyDenied,
|
|
@@ -996,12 +1024,14 @@ function pathsOverlap(left,right){
|
|
|
996
1024
|
async function packageNativeRelease(prepared,app,{
|
|
997
1025
|
workspaceOperationLease,
|
|
998
1026
|
selectedSdk,
|
|
1027
|
+
target,
|
|
999
1028
|
signal,
|
|
1000
1029
|
onEvent
|
|
1001
1030
|
}={}){
|
|
1002
1031
|
const release=await packageApp({
|
|
1003
1032
|
workspaceRoot:prepared.workspaceRoot,
|
|
1004
1033
|
appId:app.appId,
|
|
1034
|
+
target,
|
|
1005
1035
|
browserPwa:false,
|
|
1006
1036
|
workspaceOperationLease,
|
|
1007
1037
|
signal,
|
|
@@ -1065,7 +1095,7 @@ async function executePairedNativeBuild(options,adapter,{
|
|
|
1065
1095
|
const packageOutputs=[];
|
|
1066
1096
|
for(const app of [prepared.validation.app,...dependencyApps]){
|
|
1067
1097
|
const releaseRoot=resolvePackageOutputRoot(
|
|
1068
|
-
prepared.workspaceRoot,prepared.validation.config,app.manifest
|
|
1098
|
+
prepared.workspaceRoot,prepared.validation.config,app.manifest,{target}
|
|
1069
1099
|
);
|
|
1070
1100
|
if(pathsOverlap(releaseRoot,outputRoot)
|
|
1071
1101
|
||packageOutputs.some(existing=>pathsOverlap(existing,releaseRoot))){
|
|
@@ -1084,13 +1114,13 @@ async function executePairedNativeBuild(options,adapter,{
|
|
|
1084
1114
|
descriptorSource:prepared.validation.app.descriptorSource,
|
|
1085
1115
|
descriptorPath:prepared.validation.app.descriptorPath,
|
|
1086
1116
|
validation:prepared.validation
|
|
1087
|
-
},{workspaceOperationLease,selectedSdk,signal,onEvent});
|
|
1117
|
+
},{workspaceOperationLease,selectedSdk,target,signal,onEvent});
|
|
1088
1118
|
const dependencyReleases=[];
|
|
1089
1119
|
for(const dependency of dependencyApps){
|
|
1090
1120
|
dependencyReleases.push(await packageNativeRelease(
|
|
1091
1121
|
prepared,
|
|
1092
1122
|
dependency,
|
|
1093
|
-
{workspaceOperationLease,selectedSdk,signal,onEvent}
|
|
1123
|
+
{workspaceOperationLease,selectedSdk,target,signal,onEvent}
|
|
1094
1124
|
));
|
|
1095
1125
|
}
|
|
1096
1126
|
const toolchain=await adapter.prepare({
|