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
package/src/process.mjs
CHANGED
|
@@ -45,10 +45,30 @@ async function emitLines(events,type,text){
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function
|
|
48
|
+
function outputSelected(option,stream){
|
|
49
|
+
return is.boolean(option)?option:option?.[stream]??true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function outputEncodingSelected(option,stream){
|
|
53
|
+
const selected=option!==null&&is.object(option)?option[stream]:option;
|
|
54
|
+
const encoding=selected===undefined?'utf8':selected;
|
|
55
|
+
if(encoding!=='utf8'&&encoding!==null){
|
|
56
|
+
throw new TypeError(`The ${stream} outputEncoding must be 'utf8' or null.`);
|
|
57
|
+
}
|
|
58
|
+
return encoding;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function deliverChunk(stream,events,type,chunk,{onOutput,emitOutput,onFailure}){
|
|
49
62
|
stream.pause();
|
|
50
|
-
return
|
|
51
|
-
|
|
63
|
+
return (async function deliverOutput(){
|
|
64
|
+
try{
|
|
65
|
+
if(onOutput)await onOutput({stream:type==='process.stdout'?'stdout':'stderr',chunk});
|
|
66
|
+
}catch(error){
|
|
67
|
+
onFailure(error);
|
|
68
|
+
}
|
|
69
|
+
if(emitOutput)await emitLines(events,type,chunk);
|
|
70
|
+
})().catch(()=>{
|
|
71
|
+
// The event queue separately owns and propagates observer failures.
|
|
52
72
|
}).finally(()=>{
|
|
53
73
|
if(!stream.destroyed){
|
|
54
74
|
stream.resume();
|
|
@@ -121,15 +141,40 @@ export async function runProcess(command,args=[],{
|
|
|
121
141
|
env,
|
|
122
142
|
signal,
|
|
123
143
|
onEvent,
|
|
144
|
+
onOutput,
|
|
145
|
+
captureOutput=true,
|
|
146
|
+
emitOutputEvents=true,
|
|
147
|
+
outputEncoding='utf8',
|
|
124
148
|
heartbeatMs=5000,
|
|
125
149
|
terminationGraceMs=DEFAULT_TERMINATION_GRACE_MS,
|
|
126
150
|
allowNonzero=false,
|
|
127
|
-
input
|
|
151
|
+
input,
|
|
152
|
+
cancellationMode='terminate-tree'
|
|
128
153
|
}={}){
|
|
129
154
|
throwIfAborted(signal);
|
|
155
|
+
if(onOutput!==undefined&&!is.function(onOutput)){
|
|
156
|
+
throw new TypeError('onOutput must be a function.');
|
|
157
|
+
}
|
|
158
|
+
const outputEncodings={
|
|
159
|
+
stdout:outputEncodingSelected(outputEncoding,'stdout'),
|
|
160
|
+
stderr:outputEncodingSelected(outputEncoding,'stderr')
|
|
161
|
+
};
|
|
162
|
+
for(const stream of ['stdout','stderr']){
|
|
163
|
+
if(outputEncodings[stream]===null&&(!onOutput
|
|
164
|
+
||outputSelected(captureOutput,stream)!==false
|
|
165
|
+
||outputSelected(emitOutputEvents,stream)!==false)){
|
|
166
|
+
throw new TypeError(`Raw ${stream} requires onOutput, captureOutput:false and emitOutputEvents:false for that stream.`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if(!onOutput&&(!outputSelected(captureOutput,'stdout')||!outputSelected(captureOutput,'stderr'))){
|
|
170
|
+
throw new TypeError('Uncaptured process output requires an onOutput consumer.');
|
|
171
|
+
}
|
|
130
172
|
if(!is.array(args)||args.some(argument=>!is.string(argument))){
|
|
131
173
|
throw new ArcaneError(ERROR_CODES.usage,'Process arguments must be a fixed array of strings.');
|
|
132
174
|
}
|
|
175
|
+
if(cancellationMode!=='terminate-tree'&&cancellationMode!=='close-input'){
|
|
176
|
+
throw new ArcaneError(ERROR_CODES.usage,'Unknown process cancellation mode.');
|
|
177
|
+
}
|
|
133
178
|
if(!is.integer(terminationGraceMs)||terminationGraceMs<100||terminationGraceMs>30_000){
|
|
134
179
|
throw new ArcaneError(
|
|
135
180
|
ERROR_CODES.usage,
|
|
@@ -151,20 +196,39 @@ export async function runProcess(command,args=[],{
|
|
|
151
196
|
throwIfAborted(signal);
|
|
152
197
|
|
|
153
198
|
return new Promise((resolve,reject)=>{
|
|
154
|
-
let stdout='';
|
|
155
|
-
let stderr='';
|
|
199
|
+
let stdout=outputEncodings.stdout!==null&&outputSelected(captureOutput,'stdout')?'':null;
|
|
200
|
+
let stderr=outputEncodings.stderr!==null&&outputSelected(captureOutput,'stderr')?'':null;
|
|
201
|
+
let childClosed=false;
|
|
202
|
+
let closedResult=null;
|
|
156
203
|
let settlementStarted=false;
|
|
157
204
|
let cancellationRequested=false;
|
|
158
205
|
let cancellationError=null;
|
|
159
206
|
let terminationRequested=false;
|
|
160
207
|
let escalation=null;
|
|
161
208
|
let spawnError=null;
|
|
209
|
+
let inputError=null;
|
|
210
|
+
let inputIterator=null;
|
|
211
|
+
let inputPump=null;
|
|
212
|
+
let inputReturn=null;
|
|
213
|
+
let inputDone=false;
|
|
214
|
+
const outputErrors=[];
|
|
162
215
|
let child;
|
|
163
216
|
let heartbeat=null;
|
|
164
217
|
const deliveries=new Set();
|
|
165
218
|
|
|
166
219
|
const ownDelivery=(stream,type,chunk)=>{
|
|
167
|
-
const
|
|
220
|
+
const name=type==='process.stdout'?'stdout':'stderr';
|
|
221
|
+
const delivery=deliverChunk(stream,events,type,chunk,{
|
|
222
|
+
onOutput,
|
|
223
|
+
emitOutput:outputEncodings[name]!==null&&outputSelected(emitOutputEvents,name),
|
|
224
|
+
onFailure:function outputFailed(error){
|
|
225
|
+
// Preserve complete failed callback input even when the
|
|
226
|
+
// caller elected not to retain successful protocol output.
|
|
227
|
+
outputErrors.push(new ArcaneError(ERROR_CODES.operationFailed,
|
|
228
|
+
`The ${name} output callback failed.`,{cause:error,details:{stream:name,chunk}}));
|
|
229
|
+
stopTree();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
168
232
|
deliveries.add(delivery);
|
|
169
233
|
void delivery.then(()=>deliveries.delete(delivery));
|
|
170
234
|
};
|
|
@@ -175,6 +239,26 @@ export async function runProcess(command,args=[],{
|
|
|
175
239
|
}
|
|
176
240
|
};
|
|
177
241
|
|
|
242
|
+
function stopInput(){
|
|
243
|
+
if(inputIterator&&!inputDone&&!inputReturn){
|
|
244
|
+
inputReturn=Promise.resolve().then(async function returnInput(){
|
|
245
|
+
await inputIterator.return?.();
|
|
246
|
+
}).catch(function inputReturnFailed(error){
|
|
247
|
+
inputError=inputError&&inputError!==error
|
|
248
|
+
?new AggregateError([inputError,error],'Process input and its cleanup failed.'):error;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
child?.stdin.end();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function drainInput(){
|
|
255
|
+
stopInput();
|
|
256
|
+
// AsyncIterable producers own cancellation of an outstanding next().
|
|
257
|
+
// Their return() must cooperate so owned cleanup can really finish.
|
|
258
|
+
await inputPump;
|
|
259
|
+
await inputReturn;
|
|
260
|
+
}
|
|
261
|
+
|
|
178
262
|
const finish=(callback,value)=>{
|
|
179
263
|
if(settlementStarted){
|
|
180
264
|
return;
|
|
@@ -184,9 +268,38 @@ export async function runProcess(command,args=[],{
|
|
|
184
268
|
clearTimeout(escalation);
|
|
185
269
|
signal?.removeEventListener('abort',abort);
|
|
186
270
|
void (async()=>{
|
|
271
|
+
await drainInput();
|
|
272
|
+
let callbackFailure=null;
|
|
187
273
|
try{
|
|
188
274
|
await events.drain();
|
|
189
|
-
}catch(
|
|
275
|
+
}catch(error){
|
|
276
|
+
callbackFailure=error;
|
|
277
|
+
}
|
|
278
|
+
if((cancellationMode==='close-input'&&closedResult)||inputIterator||onOutput){
|
|
279
|
+
const failures=[...new Set([
|
|
280
|
+
callback===reject?value:null,
|
|
281
|
+
callbackFailure,inputError,cancellationError,spawnError,...outputErrors
|
|
282
|
+
].filter(error=>error!==null))];
|
|
283
|
+
if(failures.length>0){
|
|
284
|
+
const primary=failures[0];
|
|
285
|
+
// Owned cancellation/exit errors already carry this exact
|
|
286
|
+
// result. Other errors retain their identity and cause in
|
|
287
|
+
// the complete failure record instead of being rewritten.
|
|
288
|
+
if(failures.length===1&&primary?.details===closedResult){
|
|
289
|
+
reject(primary);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const failure=new ArcaneError(
|
|
293
|
+
primary?.code??ERROR_CODES.operationFailed,
|
|
294
|
+
primary?.message??`Could not complete ${command}.`,
|
|
295
|
+
{cause:primary,details:closedResult,exitCode:primary?.exitCode}
|
|
296
|
+
);
|
|
297
|
+
failure.errors=failures;
|
|
298
|
+
reject(failure);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if(callbackFailure){
|
|
190
303
|
reject(callbackFailure);
|
|
191
304
|
return;
|
|
192
305
|
}
|
|
@@ -195,10 +308,16 @@ export async function runProcess(command,args=[],{
|
|
|
195
308
|
};
|
|
196
309
|
|
|
197
310
|
const stopTree=()=>{
|
|
198
|
-
if(terminationRequested||settlementStarted){
|
|
311
|
+
if(terminationRequested||settlementStarted||childClosed){
|
|
199
312
|
return;
|
|
200
313
|
}
|
|
201
314
|
terminationRequested=true;
|
|
315
|
+
stopInput();
|
|
316
|
+
if(cancellationMode==='close-input'){
|
|
317
|
+
// The selected host owns shutdown after EOF. Keep observing its
|
|
318
|
+
// complete output and exit; accepted durable work may outlive UI.
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
202
321
|
terminateProcessTree(child);
|
|
203
322
|
escalation=setTimeout(()=>{
|
|
204
323
|
if(childIsRunning(child)){
|
|
@@ -214,7 +333,7 @@ export async function runProcess(command,args=[],{
|
|
|
214
333
|
};
|
|
215
334
|
|
|
216
335
|
const abort=()=>{
|
|
217
|
-
if(cancellationRequested||settlementStarted){
|
|
336
|
+
if(cancellationRequested||settlementStarted||childClosed){
|
|
218
337
|
return;
|
|
219
338
|
}
|
|
220
339
|
cancellationRequested=true;
|
|
@@ -225,7 +344,9 @@ export async function runProcess(command,args=[],{
|
|
|
225
344
|
);
|
|
226
345
|
void events.enqueue({
|
|
227
346
|
type:'process.cancellation.requested',
|
|
228
|
-
message
|
|
347
|
+
message:cancellationMode==='close-input'
|
|
348
|
+
?`Closing ${command} input and waiting for its owned shutdown.`
|
|
349
|
+
:`Stopping ${command} and its child processes.`,
|
|
229
350
|
data:{command,pid:child?.pid??null}
|
|
230
351
|
});
|
|
231
352
|
stopTree();
|
|
@@ -263,16 +384,16 @@ export async function runProcess(command,args=[],{
|
|
|
263
384
|
return;
|
|
264
385
|
}
|
|
265
386
|
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
child.stdout.setEncoding(
|
|
269
|
-
child.stderr.setEncoding(
|
|
387
|
+
// Text streams retain split UTF-8 sequences and flush at EOF. Explicit
|
|
388
|
+
// raw streams bypass decoding and deliver native Buffers only to onOutput.
|
|
389
|
+
if(outputEncodings.stdout!==null)child.stdout.setEncoding(outputEncodings.stdout);
|
|
390
|
+
if(outputEncodings.stderr!==null)child.stderr.setEncoding(outputEncodings.stderr);
|
|
270
391
|
child.stdout.on('data',chunk=>{
|
|
271
|
-
stdout=appendOutput(stdout,chunk);
|
|
392
|
+
if(stdout!==null)stdout=appendOutput(stdout,chunk);
|
|
272
393
|
ownDelivery(child.stdout,'process.stdout',chunk);
|
|
273
394
|
});
|
|
274
395
|
child.stderr.on('data',chunk=>{
|
|
275
|
-
stderr=appendOutput(stderr,chunk);
|
|
396
|
+
if(stderr!==null)stderr=appendOutput(stderr,chunk);
|
|
276
397
|
ownDelivery(child.stderr,'process.stderr',chunk);
|
|
277
398
|
});
|
|
278
399
|
child.on('error',error=>{
|
|
@@ -286,8 +407,13 @@ export async function runProcess(command,args=[],{
|
|
|
286
407
|
}
|
|
287
408
|
});
|
|
288
409
|
child.on('close',(code,terminationSignal)=>{
|
|
410
|
+
// The child lifetime has ended even while its output/event callbacks
|
|
411
|
+
// are still draining. A later abort cannot cancel that completed work.
|
|
412
|
+
childClosed=true;
|
|
413
|
+
signal?.removeEventListener('abort',abort);
|
|
289
414
|
clearInterval(heartbeat);
|
|
290
415
|
void (async()=>{
|
|
416
|
+
await drainInput();
|
|
291
417
|
await drainDeliveries();
|
|
292
418
|
if(settlementStarted)return;
|
|
293
419
|
const result={
|
|
@@ -299,10 +425,11 @@ export async function runProcess(command,args=[],{
|
|
|
299
425
|
stdout,
|
|
300
426
|
stderr
|
|
301
427
|
};
|
|
428
|
+
closedResult=result;
|
|
302
429
|
if(terminationRequested){
|
|
303
430
|
// The direct child has exited. Make one final tree-wide attempt so
|
|
304
431
|
// a descendant that ignored the graceful request cannot outlive it.
|
|
305
|
-
terminateProcessTree(child,{force:true});
|
|
432
|
+
if(cancellationMode!=='close-input')terminateProcessTree(child,{force:true});
|
|
306
433
|
if(cancellationRequested){
|
|
307
434
|
await events.enqueue({
|
|
308
435
|
type:'process.cancelled',
|
|
@@ -310,7 +437,8 @@ export async function runProcess(command,args=[],{
|
|
|
310
437
|
data:{command,code:result.code,signal:terminationSignal}
|
|
311
438
|
});
|
|
312
439
|
}
|
|
313
|
-
|
|
440
|
+
if(cancellationError)cancellationError.details=result;
|
|
441
|
+
finish(reject,events.error??inputError??outputErrors[0]??cancellationError??new ArcaneError(
|
|
314
442
|
ERROR_CODES.operationFailed,
|
|
315
443
|
`Stopped ${command} after its event callback failed.`
|
|
316
444
|
));
|
|
@@ -324,6 +452,16 @@ export async function runProcess(command,args=[],{
|
|
|
324
452
|
));
|
|
325
453
|
return;
|
|
326
454
|
}
|
|
455
|
+
if(inputError){
|
|
456
|
+
finish(reject,new ArcaneError(ERROR_CODES.operationFailed,
|
|
457
|
+
`Could not write ${command} input: ${inputError.message}`,
|
|
458
|
+
{cause:inputError,details:result}));
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if(outputErrors.length){
|
|
462
|
+
finish(reject,outputErrors[0]);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
327
465
|
await events.enqueue({
|
|
328
466
|
type:'process.completed',
|
|
329
467
|
message:`${command} exited with code ${String(result.code)}.`,
|
|
@@ -346,7 +484,33 @@ export async function runProcess(command,args=[],{
|
|
|
346
484
|
abort();
|
|
347
485
|
}
|
|
348
486
|
|
|
349
|
-
if(input
|
|
487
|
+
if(input&&is.function(input[Symbol.asyncIterator])){
|
|
488
|
+
child.stdin.on('error',error=>{inputError=error;stopTree();});
|
|
489
|
+
inputPump=(async function pumpInput(){
|
|
490
|
+
try{
|
|
491
|
+
inputIterator=input[Symbol.asyncIterator]();
|
|
492
|
+
while(!terminationRequested&&!childClosed){
|
|
493
|
+
const next=await inputIterator.next();
|
|
494
|
+
if(next.done){inputDone=true;break;}
|
|
495
|
+
if(terminationRequested||childClosed)break;
|
|
496
|
+
await new Promise(function writeChunk(resolve,reject){
|
|
497
|
+
child.stdin.write(next.value,function written(error){
|
|
498
|
+
if(error)reject(error);
|
|
499
|
+
else resolve();
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
child.stdin.end();
|
|
504
|
+
}catch(error){
|
|
505
|
+
inputError=inputError&&inputError!==error
|
|
506
|
+
?new AggregateError([inputError,error],'Process input failed.'):error;
|
|
507
|
+
stopTree();
|
|
508
|
+
}
|
|
509
|
+
})();
|
|
510
|
+
}else if(cancellationMode==='close-input'){
|
|
511
|
+
child.stdin.on('error',error=>{inputError=error;stopTree();});
|
|
512
|
+
if(input!==undefined&&!terminationRequested)child.stdin.write(input);
|
|
513
|
+
}else if(input!==undefined){
|
|
350
514
|
child.stdin.end(input);
|
|
351
515
|
}else{
|
|
352
516
|
child.stdin.end();
|
package/src/scaffold.mjs
CHANGED
|
@@ -44,7 +44,7 @@ function validateInputs(appId,displayName){
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function validateScaffoldTarget(target){
|
|
47
|
-
const targets=['browser','portable','windows-x64','linux-x64','linux-arm64','android-arm64'];
|
|
47
|
+
const targets=['browser','portable','windows-x64','macos-arm64','macos-x64','linux-x64','linux-arm64','android-arm64'];
|
|
48
48
|
if(!targets.includes(target)){
|
|
49
49
|
fail(`Invalid scaffold target: ${String(target)}. Expected one of ${targets.join(', ')}.`,'ARCANE_USAGE');
|
|
50
50
|
}
|
package/src/targets/index.mjs
CHANGED
|
@@ -19,7 +19,7 @@ const DEFINITIONS=[
|
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
id:'portable',displayName:'Portable native application',status:'pairing-required',
|
|
22
|
-
platforms:['windows','linux'],architectures:['x64','arm64'],formats:['portable'],
|
|
22
|
+
platforms:['windows','linux','macos'],architectures:['x64','arm64'],formats:['portable'],
|
|
23
23
|
signingModes:['unsigned-local-test'],
|
|
24
24
|
reason:'Portable app payloads use the SDK package provider by default; explicit provider pairing remains available.'
|
|
25
25
|
},
|
|
@@ -28,6 +28,16 @@ const DEFINITIONS=[
|
|
|
28
28
|
platforms:['windows'],architectures:['x64'],formats:['exe'],signingModes:['unsigned-local-test'],
|
|
29
29
|
reason:'Windows output requires an Arcane OS native provider.'
|
|
30
30
|
},
|
|
31
|
+
{
|
|
32
|
+
id:'macos-arm64',displayName:'macOS Apple silicon application',status:'pairing-required',
|
|
33
|
+
platforms:['macos'],architectures:['arm64'],formats:['app'],signingModes:['unsigned-local-test'],
|
|
34
|
+
reason:'macOS output uses the SDK provider with the selected architecture host asset.'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id:'macos-x64',displayName:'macOS Intel application',status:'pairing-required',
|
|
38
|
+
platforms:['macos'],architectures:['x64'],formats:['app'],signingModes:['unsigned-local-test'],
|
|
39
|
+
reason:'macOS output uses the SDK provider with the selected architecture host asset.'
|
|
40
|
+
},
|
|
31
41
|
{
|
|
32
42
|
id:'linux-x64',displayName:'Linux x64 executable',status:'pairing-required',
|
|
33
43
|
platforms:['linux'],architectures:['x64'],formats:['deb'],signingModes:['unsigned-local-test'],
|
|
@@ -84,7 +84,7 @@ export function workspaceTemplate({
|
|
|
84
84
|
||(!canonicalSpecifier&&!aliasSpecifier)){
|
|
85
85
|
throw new Error('Invalid scaffold SDK installation authority.');
|
|
86
86
|
}
|
|
87
|
-
const supportedTargets=['browser','portable','windows-x64','linux-x64','linux-arm64','android-arm64'];
|
|
87
|
+
const supportedTargets=['browser','portable','windows-x64','macos-arm64','macos-x64','linux-x64','linux-arm64','android-arm64'];
|
|
88
88
|
if(!supportedTargets.includes(target)){
|
|
89
89
|
throw new Error(`Unsupported scaffold target: ${String(target)}.`);
|
|
90
90
|
}
|
|
@@ -105,17 +105,18 @@ export function workspaceTemplate({
|
|
|
105
105
|
const packageInclude=[`${appId}.css`,'index.html'];
|
|
106
106
|
if(native)packageInclude.push('img/icon.png');
|
|
107
107
|
packageInclude.push('manifest.json','modules');
|
|
108
|
+
const macosTarget=target==='macos-arm64'||target==='macos-x64';
|
|
108
109
|
const nativeGuide=native?`
|
|
109
110
|
## ${target} native target
|
|
110
111
|
|
|
111
112
|
This scaffold declares both the browser and ${target} targets and includes the
|
|
112
113
|
required raster application icon.
|
|
113
|
-
Pair it with one explicit Arcane OS checkout:
|
|
114
|
+
${macosTarget?'Use the installed SDK provider and its matching macOS release host asset:':'Pair it with one explicit Arcane OS checkout:'}
|
|
114
115
|
|
|
115
116
|
\`\`\`sh
|
|
116
|
-
npm exec -- arcane native-doctor --target ${target} --arcane-root "<path-to-Arcane-OS>"
|
|
117
|
-
npm run build -- --arcane-root "<path-to-Arcane-OS>"
|
|
118
|
-
${target==='portable'?'':`npm run run -- --arcane-root "<path-to-Arcane-OS>"\n`}
|
|
117
|
+
npm exec -- arcane native-doctor --target ${target}${macosTarget?'':' --arcane-root "<path-to-Arcane-OS>"'}
|
|
118
|
+
npm run build${macosTarget?'':' -- --arcane-root "<path-to-Arcane-OS>"'}
|
|
119
|
+
${target==='portable'?'':`npm run run${macosTarget?'':' -- --arcane-root "<path-to-Arcane-OS>"'}\n`}
|
|
119
120
|
\`\`\`
|
|
120
121
|
|
|
121
122
|
The selected Arcane provider must support this target or the command fails without
|
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'&&
|
|
871
|
+
if(providerSource==='sdk-package'&&['portable','windows-x64','macos-arm64','macos-x64'].includes(target))return;
|
|
844
872
|
if(!is.string(toolchainRoot)||!sameCanonicalPath(workspaceRoot,toolchainRoot)){
|
|
845
873
|
throw new ArcaneError(
|
|
846
874
|
ERROR_CODES.policyDenied,
|