arcane-os 0.88.1 → 0.90.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 +34 -0
- package/browser-runtime/ai/browser-decisions.mjs +31 -4
- package/browser-runtime/ai/core-local.mjs +105 -25
- package/browser-runtime/ai/decision-runtime.mjs +7 -0
- package/browser-runtime/ai/decision-worker.mjs +9 -1
- package/browser-runtime/core/client.mjs +6 -0
- package/browser-runtime/core/contracts.mjs +10 -0
- package/docs/reference/ai/browser-decisions.md +89 -7
- package/docs/reference/core-client.md +59 -0
- package/docs/reference/core-native-packaging.md +62 -10
- package/docs/reference/core-repositories.md +104 -8
- package/docs/reference/git-text-snapshot.md +16 -1
- package/docs/reference/inventory/package-api.json +25 -25
- package/docs/reference/inventory/runtime-components.json +1 -1
- package/docs/reference/inventory/runtime-modules.json +4 -4
- package/docs/reference/local-ai.md +108 -9
- package/docs/reference/mcp-stdio.md +76 -1
- package/docs/reference/native-app-control.md +17 -5
- package/docs/reference/protocols.md +14 -0
- package/docs/reference/runtime-modules.md +9 -3
- package/docs/reference/sdk-api.md +202 -35
- package/package.json +2 -2
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +1 -5
- package/schemas/arcane-app.schema.json +13 -3
- package/schemas/arcane-package.schema.json +10 -1
- package/src/app-descriptor.mjs +6 -2
- package/src/cli/app-control.mjs +1 -1
- package/src/core/hosts/windows/ArcaneHost.cs +273 -5
- package/src/core/hosts/windows/ArcaneLauncher.cs +9 -0
- package/src/core/hosts/windows/ArcaneWindowControl.cs +7 -7
- package/src/core/repositories.mjs +17 -6
- package/src/core/services/local-ai.mjs +183 -22
- package/src/git-text-snapshot.mjs +4 -2
- package/src/installed-sdk-runtime.mjs +2 -0
- package/src/local-ai/config.mjs +16 -0
- package/src/local-ai/dev.mjs +2 -2
- package/src/local-ai/discover.mjs +3 -0
- package/src/local-ai/install.mjs +130 -20
- package/src/local-ai/llama-execution-windows.ps1 +158 -0
- package/src/local-ai/llama-execution.mjs +169 -0
- package/src/local-ai/server.mjs +38 -8
- package/src/native/portable-layout.mjs +13 -3
- package/src/repository.mjs +33 -8
- package/src/workspace-runtime.mjs +97 -1
package/src/local-ai/server.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import Is from 'strong-type';
|
|
|
4
4
|
import {ArcaneError,ERROR_CODES,errorRecord,normalizeError,throwIfAborted} from '../errors.mjs';
|
|
5
5
|
import {createEventQueue} from '../event-queue.mjs';
|
|
6
6
|
import {runProcess} from '../process.mjs';
|
|
7
|
+
import {resolveLlamaExecution} from './llama-execution.mjs';
|
|
7
8
|
|
|
8
9
|
const is=new Is(false);
|
|
9
10
|
|
|
@@ -48,17 +49,21 @@ async function probeServer(id,url,signal){
|
|
|
48
49
|
return observed;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
export function createLocalAIServer({id,configuration={},runtime,appRoot,signal,onEvent,onState}={}){
|
|
52
|
+
export function createLocalAIServer({id,configuration={},runtime,appRoot,signal,onEvent,onState,onExit,executionDevices,requireManaged=false}={}){
|
|
52
53
|
if(!['llama.cpp','ollama'].includes(id)){
|
|
53
54
|
throw new ArcaneError(ERROR_CODES.usage,`Unsupported local AI server: ${String(id)}.`);
|
|
54
55
|
}
|
|
55
56
|
const url=localAIServerURL(id,configuration);
|
|
56
57
|
const endpoint=new URL(url);
|
|
58
|
+
const managedSelection=requireManaged||(id==='llama.cpp'&&(configuration.executionTarget!==undefined
|
|
59
|
+
||(runtime?.requestedBackend&&runtime.requestedBackend!=='auto')));
|
|
57
60
|
const controller=new AbortController();
|
|
58
61
|
const operationSignal=signal?AbortSignal.any([signal,controller.signal]):controller.signal;
|
|
59
62
|
const events=createEventQueue(onEvent,{onFailure:fail});
|
|
60
63
|
let current={id,url,state:'starting',available:false,owned:false};
|
|
61
64
|
let owned=false;
|
|
65
|
+
let external=false;
|
|
66
|
+
let execution;
|
|
62
67
|
let processTask=null;
|
|
63
68
|
let processObservation=null;
|
|
64
69
|
let startupTask=null;
|
|
@@ -70,6 +75,8 @@ export function createLocalAIServer({id,configuration={},runtime,appRoot,signal,
|
|
|
70
75
|
let terminalFailure=null;
|
|
71
76
|
let resolveReady;
|
|
72
77
|
let rejectReady;
|
|
78
|
+
let resolveManagement;
|
|
79
|
+
const management=new Promise(function discoverManagement(resolve){resolveManagement=resolve;});
|
|
73
80
|
const output={stdout:'',stderr:''};
|
|
74
81
|
const ready=new Promise(function createReadiness(resolve,reject){
|
|
75
82
|
resolveReady=resolve;
|
|
@@ -80,7 +87,9 @@ export function createLocalAIServer({id,configuration={},runtime,appRoot,signal,
|
|
|
80
87
|
void ready.catch(function observeReadinessFailure(){return undefined;});
|
|
81
88
|
|
|
82
89
|
async function publishState(state,error){
|
|
83
|
-
current={id,url,state,available:state==='ready',owned
|
|
90
|
+
current={id,url,state,available:state==='ready',owned,
|
|
91
|
+
managed:!external&&Boolean(runtime?.executable),
|
|
92
|
+
...(id==='llama.cpp'?{execution:execution??null}:{}),...(error?{error:errorRecord(error)}:{})};
|
|
84
93
|
await onState?.(current);
|
|
85
94
|
await events.send({
|
|
86
95
|
type:`local-ai.server.${state}`,
|
|
@@ -147,6 +156,8 @@ export function createLocalAIServer({id,configuration={},runtime,appRoot,signal,
|
|
|
147
156
|
// endpoint at this actual launch boundary rather than presenting
|
|
148
157
|
// that earlier installation-selection snapshot as current health.
|
|
149
158
|
const observed=await probeServer(id,url,operationSignal);
|
|
159
|
+
external=true;
|
|
160
|
+
resolveManagement(false);
|
|
150
161
|
if(runtime?.requestedVersion&&runtime.requestedVersion!=='latest'){
|
|
151
162
|
const requested=runtime.requestedVersion.replace(/^v/u,'');
|
|
152
163
|
if(!is.string(observed.version)||observed.version.replace(/^v/u,'')!==requested){
|
|
@@ -158,10 +169,18 @@ export function createLocalAIServer({id,configuration={},runtime,appRoot,signal,
|
|
|
158
169
|
throwIfAborted(operationSignal);
|
|
159
170
|
// An HTTP error is an existing service's state. Only a refused
|
|
160
171
|
// connection allows starting the selected local executable.
|
|
161
|
-
if(!localAIConnectionRefused(error))
|
|
172
|
+
if(!localAIConnectionRefused(error)){
|
|
173
|
+
external=true;
|
|
174
|
+
resolveManagement(false);
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
162
177
|
if(!localHostname(endpoint.hostname))throw error;
|
|
163
178
|
}
|
|
164
179
|
if(existing){
|
|
180
|
+
external=true;
|
|
181
|
+
if(managedSelection){
|
|
182
|
+
throw new ArcaneError('LOCAL_AI_EXTERNAL_SELECTION_UNSUPPORTED','The existing llama.cpp server owns its model and device selection. Use an available listener for SDK-managed model loading.');
|
|
183
|
+
}
|
|
165
184
|
await publishState('ready');
|
|
166
185
|
throwIfAborted(operationSignal);
|
|
167
186
|
settleReady();
|
|
@@ -170,6 +189,7 @@ export function createLocalAIServer({id,configuration={},runtime,appRoot,signal,
|
|
|
170
189
|
if(!runtime?.executable){
|
|
171
190
|
throw new ArcaneError(ERROR_CODES.prerequisiteMissing,`No installed ${id} executable was supplied.`);
|
|
172
191
|
}
|
|
192
|
+
resolveManagement(true);
|
|
173
193
|
if(endpoint.protocol!=='http:'){
|
|
174
194
|
throw new ArcaneError(ERROR_CODES.usage,`${id} managed startup requires its direct HTTP listener URL.`);
|
|
175
195
|
}
|
|
@@ -187,12 +207,19 @@ export function createLocalAIServer({id,configuration={},runtime,appRoot,signal,
|
|
|
187
207
|
throwIfAborted(operationSignal);
|
|
188
208
|
const host=endpoint.hostname.replace(/^\[|\]$/gu,'');
|
|
189
209
|
const port=endpoint.port||'80';
|
|
210
|
+
const selected=id==='llama.cpp'?await resolveLlamaExecution({
|
|
211
|
+
executionTarget:configuration.executionTarget,executionDevices,runtime,signal:operationSignal,onEvent:events.send
|
|
212
|
+
}):null;
|
|
213
|
+
if(selected)execution=selected.execution;
|
|
190
214
|
const arguments_=id==='ollama'?['serve',...args]:[
|
|
191
215
|
'--host',host,'--port',port,'--jinja',
|
|
216
|
+
...(managedSelection?args:[]),
|
|
192
217
|
...(configuration.model?['--model',path.resolve(appRoot,configuration.model)]:['--models-dir',modelsDirectory]),
|
|
193
|
-
...args
|
|
218
|
+
...(!managedSelection?args:[]),
|
|
219
|
+
...(configuration.alias?['--alias',configuration.alias]:[]),
|
|
220
|
+
...selected.args
|
|
194
221
|
];
|
|
195
|
-
const env=id==='ollama'?{OLLAMA_HOST:endpoint.origin,OLLAMA_MODELS:modelsDirectory}:
|
|
222
|
+
const env=id==='ollama'?{OLLAMA_HOST:endpoint.origin,OLLAMA_MODELS:modelsDirectory}:selected.env;
|
|
196
223
|
owned=true;
|
|
197
224
|
await publishState('starting');
|
|
198
225
|
throwIfAborted(operationSignal);
|
|
@@ -215,7 +242,7 @@ export function createLocalAIServer({id,configuration={},runtime,appRoot,signal,
|
|
|
215
242
|
return;
|
|
216
243
|
}
|
|
217
244
|
await fail(error);
|
|
218
|
-
});
|
|
245
|
+
}).finally(async function releaseProcessResources(){await onExit?.();});
|
|
219
246
|
void processObservation.catch(function observeProcessCallbackFailure(error){
|
|
220
247
|
current={...current,state:'error',available:false,error:errorRecord(error)};
|
|
221
248
|
settleReady(error);
|
|
@@ -250,7 +277,10 @@ export function createLocalAIServer({id,configuration={},runtime,appRoot,signal,
|
|
|
250
277
|
});
|
|
251
278
|
}
|
|
252
279
|
|
|
253
|
-
startupTask=start().catch(fail)
|
|
280
|
+
startupTask=start().catch(fail).finally(async function releaseUnstartedResources(){
|
|
281
|
+
resolveManagement(false);
|
|
282
|
+
if(!processTask)await onExit?.();
|
|
283
|
+
});
|
|
254
284
|
void startupTask.catch(function observeStartupCallbackFailure(error){
|
|
255
285
|
current={...current,state:'error',available:false,error:errorRecord(error)};
|
|
256
286
|
settleReady(error);
|
|
@@ -258,5 +288,5 @@ export function createLocalAIServer({id,configuration={},runtime,appRoot,signal,
|
|
|
258
288
|
operationSignal.addEventListener('abort',observeLifetimeAbort,{once:true});
|
|
259
289
|
if(operationSignal.aborted)observeLifetimeAbort();
|
|
260
290
|
|
|
261
|
-
return {ready,url,close,get owned(){return owned;},get current(){return current;}};
|
|
291
|
+
return {ready,management,url,close,get owned(){return owned;},get external(){return external;},get current(){return current;}};
|
|
262
292
|
}
|
|
@@ -155,8 +155,8 @@ export function coreEntrySource(application, version, services, {localAI, runtim
|
|
|
155
155
|
const imageSelected = localAI?.runtimes?.some(function selectedImage(requirement) {
|
|
156
156
|
return (typeof requirement === 'string' ? requirement : requirement.id) === 'stable-diffusion.cpp';
|
|
157
157
|
}) ?? false;
|
|
158
|
-
const modelAssetsSelected = imageSelected || (localAI?.runtimes?.some(function
|
|
159
|
-
return (typeof requirement === 'string' ? requirement : requirement.id)
|
|
158
|
+
const modelAssetsSelected = imageSelected || (localAI?.runtimes?.some(function selectedProjectedRuntime(requirement) {
|
|
159
|
+
return ['onnx', 'llama.cpp'].includes(typeof requirement === 'string' ? requirement : requirement.id);
|
|
160
160
|
}) ?? false);
|
|
161
161
|
const whisperSelected = localAI?.runtimes?.some(function selectedWhisper(requirement) {
|
|
162
162
|
return (typeof requirement === 'string' ? requirement : requirement.id) === 'whisper.cpp';
|
|
@@ -199,13 +199,21 @@ export function coreEntrySource(application, version, services, {localAI, runtim
|
|
|
199
199
|
' register(packagedWeb);');
|
|
200
200
|
}
|
|
201
201
|
return [
|
|
202
|
-
"import {fileURLToPath} from 'node:url';",
|
|
202
|
+
"import {fileURLToPath, pathToFileURL} from 'node:url';",
|
|
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
206
|
"import {readCoreLaunchContext, startCoreHost, startCoreListener, runSharedCoreHost, startSharedCoreBridge} from 'arcane-os/core/host';",
|
|
207
207
|
"import {createModelObservationService} from 'arcane-os/core/model-observation';",
|
|
208
208
|
'',
|
|
209
|
+
"const appEntryFlag = process.argv.indexOf('--arcane-app-entry', 2);",
|
|
210
|
+
'if (appEntryFlag !== -1) {',
|
|
211
|
+
' const appEntry = process.argv[appEntryFlag + 1];',
|
|
212
|
+
" if (appEntry === undefined || appEntry === '') throw new TypeError('--arcane-app-entry requires an app-relative module path.');",
|
|
213
|
+
" const appRoot = fileURLToPath(new URL('../app/', import.meta.url));",
|
|
214
|
+
' await import(pathToFileURL(path.resolve(appRoot, appEntry)).href);',
|
|
215
|
+
'} else {',
|
|
216
|
+
...[
|
|
209
217
|
...(localAI === undefined ? [] : [
|
|
210
218
|
'// Bundled runtime paths are artifact-root-relative POSIX paths.',
|
|
211
219
|
'function runtimePath(relative) {',
|
|
@@ -366,6 +374,8 @@ export function coreEntrySource(application, version, services, {localAI, runtim
|
|
|
366
374
|
' }',
|
|
367
375
|
" process.off('SIGINT', closeCore);",
|
|
368
376
|
" process.off('SIGTERM', closeCore);",
|
|
377
|
+
'}'
|
|
378
|
+
].map(function indentOrdinaryCore(line) { return line ? ` ${line}` : line; }),
|
|
369
379
|
'}',
|
|
370
380
|
''
|
|
371
381
|
].join('\n');
|
package/src/repository.mjs
CHANGED
|
@@ -56,20 +56,33 @@ export async function readGitText(args, {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
/**
|
|
59
|
+
/** Derive a Git argument only when the caller explicitly selects a local base. */
|
|
60
|
+
export function captureRepositoryRemote(remote, remoteBase) {
|
|
61
|
+
if (remoteBase === undefined) return remote;
|
|
62
|
+
if (!is.string(remoteBase) || !remoteBase.isWellFormed() || remoteBase.includes('\0')) {
|
|
63
|
+
throw new TypeError('remoteBase must be text representable as a native filesystem path.');
|
|
64
|
+
}
|
|
65
|
+
return remote === undefined ? undefined : path.resolve(remoteBase, remote);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Keep original selection text separate from the accepted command argument. */
|
|
60
69
|
export function captureRepositoryTarget(target) {
|
|
61
70
|
if (target === undefined) return undefined;
|
|
62
|
-
const {remote, ref} = target;
|
|
71
|
+
const {remote, ref, remoteBase} = target;
|
|
63
72
|
for (const [name, value] of Object.entries({remote, ref})) {
|
|
64
73
|
if (!is.string(value) || value === '' || !value.isWellFormed() || value.includes('\0')) {
|
|
65
74
|
throw new TypeError(`target.${name} must be nonempty text representable as a native process argument.`);
|
|
66
75
|
}
|
|
67
76
|
}
|
|
68
|
-
return {
|
|
77
|
+
return {
|
|
78
|
+
target: {remote, ref, ...(remoteBase === undefined ? {} : {remoteBase})},
|
|
79
|
+
remoteArgument: captureRepositoryRemote(remote, remoteBase)
|
|
80
|
+
};
|
|
69
81
|
}
|
|
70
82
|
|
|
71
|
-
export function repositoryPushArguments(
|
|
72
|
-
return
|
|
83
|
+
export function repositoryPushArguments(selection) {
|
|
84
|
+
return selection === undefined ? ['push']
|
|
85
|
+
: ['push', '--no-follow-tags', '--', selection.remoteArgument, `HEAD:${selection.target.ref}`];
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
/** Observe an existing workspace without initializing or refreshing it. */
|
|
@@ -167,6 +180,12 @@ export async function repositoryPull({
|
|
|
167
180
|
}={}){
|
|
168
181
|
throwIfAborted(signal);
|
|
169
182
|
const selectedTarget=captureRepositoryTarget(target);
|
|
183
|
+
return pullRepositoryTarget(selectedTarget,{workspaceRoot,signal,onEvent,run});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Internal workspace path: its target was already captured before queueing. */
|
|
187
|
+
export async function pullRepositoryTarget(selectedTarget,{workspaceRoot,signal,onEvent,run=runProcess}){
|
|
188
|
+
throwIfAborted(signal);
|
|
170
189
|
const before=await repositoryStatus({workspaceRoot,signal,onEvent,run});
|
|
171
190
|
if(!before.clean){
|
|
172
191
|
throw new ArcaneError(
|
|
@@ -176,14 +195,14 @@ export async function repositoryPull({
|
|
|
176
195
|
);
|
|
177
196
|
}
|
|
178
197
|
const args=selectedTarget===undefined?['pull','--ff-only']
|
|
179
|
-
:['pull','--ff-only','--',selectedTarget.
|
|
198
|
+
:['pull','--ff-only','--',selectedTarget.remoteArgument,`${selectedTarget.target.ref}:`];
|
|
180
199
|
const result=await git(args,{cwd:workspaceRoot,signal,onEvent,run});
|
|
181
200
|
return {
|
|
182
201
|
action:'pull',
|
|
183
202
|
repositoryRoot:before.repositoryRoot,
|
|
184
203
|
branch:before.branch,
|
|
185
204
|
output:result.stdout.trim(),
|
|
186
|
-
...(selectedTarget===undefined?{}:{target:selectedTarget,stdout:result.stdout,stderr:result.stderr})
|
|
205
|
+
...(selectedTarget===undefined?{}:{target:selectedTarget.target,stdout:result.stdout,stderr:result.stderr})
|
|
187
206
|
};
|
|
188
207
|
}
|
|
189
208
|
|
|
@@ -196,6 +215,12 @@ export async function repositoryPush({
|
|
|
196
215
|
}={}){
|
|
197
216
|
throwIfAborted(signal);
|
|
198
217
|
const selectedTarget=captureRepositoryTarget(target);
|
|
218
|
+
return pushRepositoryTarget(selectedTarget,{workspaceRoot,signal,onEvent,run});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Internal workspace path: its target was already captured before queueing. */
|
|
222
|
+
export async function pushRepositoryTarget(selectedTarget,{workspaceRoot,signal,onEvent,run=runProcess}){
|
|
223
|
+
throwIfAborted(signal);
|
|
199
224
|
const before=await repositoryStatus({workspaceRoot,signal,onEvent,run});
|
|
200
225
|
if(!before.branch){
|
|
201
226
|
throw new ArcaneError(
|
|
@@ -209,7 +234,7 @@ export async function repositoryPush({
|
|
|
209
234
|
repositoryRoot:before.repositoryRoot,
|
|
210
235
|
branch:before.branch,
|
|
211
236
|
output:(result.stdout||result.stderr).trim(),
|
|
212
|
-
...(selectedTarget===undefined?{}:{target:selectedTarget,stdout:result.stdout,stderr:result.stderr})
|
|
237
|
+
...(selectedTarget===undefined?{}:{target:selectedTarget.target,stdout:result.stdout,stderr:result.stderr})
|
|
213
238
|
};
|
|
214
239
|
}
|
|
215
240
|
|
|
@@ -85,15 +85,96 @@ async function removeTemporaryTree(location){
|
|
|
85
85
|
await rm(location,{recursive:true,force:true}).catch(()=>{});
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
async function materializeBrowserDecisions(destination, signal, onEvent) {
|
|
89
|
+
const transformers = 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.3.0/';
|
|
90
|
+
const onnx = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.31.0-dev.20260914-8d85527a0/';
|
|
91
|
+
const files = [
|
|
92
|
+
{name: 'transformers.min.js', url: `${transformers}dist/transformers.min.js`},
|
|
93
|
+
{name: 'ort-wasm-simd-threaded.asyncify.mjs', url: `${onnx}dist/ort-wasm-simd-threaded.asyncify.mjs`},
|
|
94
|
+
{name: 'ort-wasm-simd-threaded.asyncify.wasm', url: `${onnx}dist/ort-wasm-simd-threaded.asyncify.wasm`},
|
|
95
|
+
{name: 'TRANSFORMERS-LICENSE', url: `${transformers}LICENSE`},
|
|
96
|
+
{name: 'ONNX-RUNTIME-LICENSE', url: `${onnx}LICENSE`}
|
|
97
|
+
];
|
|
98
|
+
const controller = new AbortController();
|
|
99
|
+
function cancelDistribution() {
|
|
100
|
+
controller.abort(signal.reason);
|
|
101
|
+
}
|
|
102
|
+
signal?.addEventListener('abort', cancelDistribution, {once: true});
|
|
103
|
+
if (signal?.aborted) cancelDistribution();
|
|
104
|
+
let completed = 0;
|
|
105
|
+
try {
|
|
106
|
+
throwIfAborted(controller.signal);
|
|
107
|
+
await emit(
|
|
108
|
+
onEvent,
|
|
109
|
+
{type: 'workspace.decisions.started', completed, total: files.length}
|
|
110
|
+
);
|
|
111
|
+
throwIfAborted(controller.signal);
|
|
112
|
+
await mkdir(destination, {recursive: true});
|
|
113
|
+
async function acquireDistributionFile(file) {
|
|
114
|
+
try {
|
|
115
|
+
throwIfAborted(controller.signal);
|
|
116
|
+
const response = await fetch(file.url, {signal: controller.signal});
|
|
117
|
+
const content = new Uint8Array(await response.arrayBuffer());
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
const error = new Error(`Browser decision distribution returned HTTP ${response.status} ${response.statusText}.`);
|
|
120
|
+
error.code = 'ARCANE_DECISION_DISTRIBUTION_DOWNLOAD_FAILED';
|
|
121
|
+
error.response = {
|
|
122
|
+
url: response.url,
|
|
123
|
+
status: response.status,
|
|
124
|
+
statusText: response.statusText,
|
|
125
|
+
headers: [...response.headers],
|
|
126
|
+
content
|
|
127
|
+
};
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
throwIfAborted(controller.signal);
|
|
131
|
+
// Upstream distribution content is opaque, including its own
|
|
132
|
+
// imports and license text. No SDK source rewriting applies.
|
|
133
|
+
await writeFile(path.join(destination, file.name), content);
|
|
134
|
+
throwIfAborted(controller.signal);
|
|
135
|
+
completed += 1;
|
|
136
|
+
await emit(
|
|
137
|
+
onEvent,
|
|
138
|
+
{type: 'workspace.decisions.progress', file: file.name, url: file.url, completed, total: files.length}
|
|
139
|
+
);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
controller.abort(error);
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// One fixed distribution, acquired once for the selected projection;
|
|
146
|
+
// no model/app/architecture multiplier or installation script.
|
|
147
|
+
const outcomes = await Promise.allSettled(files.map(acquireDistributionFile));
|
|
148
|
+
const failures = new Set();
|
|
149
|
+
for (const outcome of outcomes) {
|
|
150
|
+
if (outcome.status === 'rejected') failures.add(outcome.reason);
|
|
151
|
+
}
|
|
152
|
+
if (failures.size === 1) throw [...failures][0];
|
|
153
|
+
if (failures.size > 1) {
|
|
154
|
+
throw new AggregateError([...failures], 'Browser decision distribution acquisition failed.');
|
|
155
|
+
}
|
|
156
|
+
throwIfAborted(controller.signal);
|
|
157
|
+
} finally {
|
|
158
|
+
signal?.removeEventListener('abort', cancelDistribution);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
transformersVersion: '4.3.0',
|
|
162
|
+
onnxRuntimeVersion: '1.31.0-dev.20260914-8d85527a0',
|
|
163
|
+
files: files.map(function distributionFilename(file) { return file.name; })
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
88
167
|
export async function materializeWorkspaceRuntimeContent({
|
|
89
168
|
workspaceRoot,
|
|
90
169
|
runtimeRoot=path.join(getSdkRoot(),'runtime'),
|
|
91
170
|
browserRuntimeRoot=getSdkBrowserRuntimeRoot(),
|
|
92
171
|
sdkVersion=SDK_VERSION,
|
|
172
|
+
browserDecisions=false,
|
|
93
173
|
signal,
|
|
94
174
|
onEvent
|
|
95
175
|
}={}){
|
|
96
176
|
if(!workspaceRoot)fail('workspaceRoot is required to materialize a workspace runtime.');
|
|
177
|
+
if(!is.boolean(browserDecisions))throw new TypeError('browserDecisions must be a boolean.');
|
|
97
178
|
throwIfAborted(signal);
|
|
98
179
|
const workspace=await realDirectory(workspaceRoot,'Workspace root');
|
|
99
180
|
const runtime=await realDirectory(runtimeRoot,'SDK runtime root');
|
|
@@ -109,6 +190,7 @@ export async function materializeWorkspaceRuntimeContent({
|
|
|
109
190
|
const backupRoot=path.join(workspace,`${BACKUP_PREFIX}${token}`);
|
|
110
191
|
let backedUp=false;
|
|
111
192
|
let promoted=false;
|
|
193
|
+
let decisionDistribution;
|
|
112
194
|
|
|
113
195
|
await mkdir(stagingRoot);
|
|
114
196
|
try{
|
|
@@ -134,6 +216,14 @@ export async function materializeWorkspaceRuntimeContent({
|
|
|
134
216
|
);
|
|
135
217
|
}
|
|
136
218
|
|
|
219
|
+
if (browserDecisions) {
|
|
220
|
+
decisionDistribution = await materializeBrowserDecisions(
|
|
221
|
+
path.join(sdkDestination, 'ai', 'decisions-runtime'),
|
|
222
|
+
signal,
|
|
223
|
+
onEvent
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
137
227
|
throwIfAborted(signal);
|
|
138
228
|
try{
|
|
139
229
|
const existing=await lstat(destinationRoot);
|
|
@@ -160,7 +250,13 @@ export async function materializeWorkspaceRuntimeContent({
|
|
|
160
250
|
return {
|
|
161
251
|
kind:'arcane-workspace-runtime-content',
|
|
162
252
|
workspaceRoot:workspace,
|
|
163
|
-
runtimeRoot:destinationRoot
|
|
253
|
+
runtimeRoot:destinationRoot,
|
|
254
|
+
...(decisionDistribution ? {
|
|
255
|
+
browserDecisions: {
|
|
256
|
+
...decisionDistribution,
|
|
257
|
+
directory: path.join(destinationRoot, 'sdk', 'ai', 'decisions-runtime')
|
|
258
|
+
}
|
|
259
|
+
} : {})
|
|
164
260
|
};
|
|
165
261
|
}catch(error){
|
|
166
262
|
if(promoted)await removeTemporaryTree(destinationRoot);
|