arcane-os 0.1.2 → 0.2.1

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/NOTICE +5 -3
  3. package/README.md +73 -24
  4. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +67 -18
  5. package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +16 -5
  6. package/browser-runtime/ai/browser-kokoro-worker.mjs +3 -0
  7. package/browser-runtime/ai/browser-speech-artifacts.mjs +1108 -0
  8. package/browser-runtime/ai/browser-speech-providers.mjs +780 -0
  9. package/browser-runtime/ai/browser-speech.mjs +9 -0
  10. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +1537 -167
  11. package/browser-runtime/ai/browser-wasm.mjs +46 -1
  12. package/browser-runtime/ai/browser-whisper-worker.mjs +3 -0
  13. package/browser-runtime/ai/browser-wllama-runtime.mjs +677 -132
  14. package/browser-runtime/ai/model-controller.mjs +138 -12
  15. package/browser-runtime/ai/speech-worker-client.mjs +207 -0
  16. package/browser-runtime/ai/speech-worker-runtime.mjs +516 -0
  17. package/browser-runtime/ai/wllama/index.mjs +389 -0
  18. package/docs/architecture.md +132 -22
  19. package/docs/reference/README.md +1 -1
  20. package/docs/reference/ai/browser-wasm.md +101 -42
  21. package/docs/reference/availability-and-normalization.md +19 -5
  22. package/docs/reference/behavioral-testing.md +18 -5
  23. package/docs/reference/cli.md +2 -2
  24. package/docs/reference/inventory/package-api.json +14 -14
  25. package/docs/reference/protocols.md +4 -4
  26. package/docs/reference/sdk-api.md +68 -38
  27. package/docs/work-amplification.md +8 -4
  28. package/package.json +7 -3
  29. package/runtime/ARCANE_RUNTIME_RELEASE.json +50 -20
  30. package/runtime/arcane/components/chat.html +551 -62
  31. package/runtime/arcane/components/speech.html +1113 -265
  32. package/runtime/arcane/entities/Chat.js +246 -43
  33. package/runtime/arcane/modules/AI.js +1394 -162
  34. package/runtime/arcane/modules/AIProviderRuntime.js +2289 -0
  35. package/runtime/arcane/modules/AIRuntimeState.js +872 -0
  36. package/runtime/arcane/modules/ConfiguredAIChatSession.js +382 -31
  37. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +1106 -0
  38. package/runtime/arcane/modules/DocumentLexicalSearch.js +292 -0
  39. package/runtime/arcane/modules/PersistentAIChatSession.js +268 -0
  40. package/runtime/arcane/modules/StaticDocumentCatalog.js +25 -206
  41. package/schemas/arcane-lock.schema.json +10 -6
  42. package/src/cli/main.mjs +14 -2
  43. package/src/constants.mjs +1 -1
  44. package/src/dev-server.mjs +273 -26
  45. package/src/doctor.mjs +1 -3
  46. package/src/import-map.mjs +193 -84
  47. package/src/packager/core.mjs +313 -41
  48. package/src/runtime.mjs +14 -4
  49. package/src/scaffold.mjs +45 -17
  50. package/src/sdk-browser-runtime.mjs +28 -75
  51. package/src/templates/workspace-template.mjs +27 -8
  52. package/src/toolchain.mjs +13 -2
  53. package/src/workspace-runtime.mjs +1 -1
  54. package/src/workspace.mjs +178 -25
package/src/scaffold.mjs CHANGED
@@ -7,12 +7,11 @@ import {materializeWorkspaceRuntime} from './workspace-runtime.mjs';
7
7
  import {generateImportMap} from './import-map.mjs';
8
8
  import {withWorkspaceOperationLock} from './workspace-operation-lock.mjs';
9
9
  import {SDK_NAME,SDK_VERSION,workspaceTemplate} from './templates/workspace-template.mjs';
10
- import {inspectWorkspaceProfile} from './workspace.mjs';
10
+ import {inspectWorkspaceProfile,resolveSdkPackageDeclaration} from './workspace.mjs';
11
11
  import {parseSemver} from './packager/core.mjs';
12
12
 
13
13
  const APP_ID_PATTERN=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
14
14
  const DISPLAY_CONTROL_PATTERN=/[\x00-\x1f\x7f]/;
15
- const LOCAL_TARBALL_PATTERN=/^file:.+\.tgz$/iu;
16
15
 
17
16
  function fail(message,code='ARCANE_WORKSPACE_INVALID'){
18
17
  const error=new Error(message);
@@ -152,31 +151,35 @@ async function assertNoLinkedAncestors(workspaceRoot,relative){
152
151
  }
153
152
  }
154
153
 
155
- function isSupportedSdkDeclaration(value){
156
- return value===SDK_VERSION||(typeof value==='string'&&LOCAL_TARBALL_PATTERN.test(value)
157
- &&!DISPLAY_CONTROL_PATTERN.test(value));
158
- }
159
-
160
- async function prepareExistingPackage(workspaceRoot,files){
154
+ async function readExistingPackage(workspaceRoot){
161
155
  const packagePath=path.join(workspaceRoot,'package.json');
162
156
  let source;
163
157
  try{source=await readFile(packagePath,'utf8');}
164
158
  catch(error){
165
- if(error?.code==='ENOENT')return Object.freeze({exists:false,updated:false});
159
+ if(error?.code==='ENOENT'){
160
+ return Object.freeze({exists:false,packagePath,source:null,document:null});
161
+ }
166
162
  throw error;
167
163
  }
168
164
  let existing;
169
165
  try{existing=JSON.parse(source);}
170
166
  catch(error){fail(`Existing package.json is not valid JSON: ${error.message}.`);}
171
167
  if(!existing||typeof existing!=='object'||Array.isArray(existing))fail('Existing package.json must be a JSON object.');
168
+ return Object.freeze({exists:true,packagePath,source,document:existing});
169
+ }
170
+
171
+ function defaultSdkDeclaration(){
172
+ return resolveSdkPackageDeclaration({devDependencies:{[SDK_NAME]:SDK_VERSION}});
173
+ }
174
+
175
+ async function prepareExistingPackage(workspaceRoot,files,existingPackage,sdkDeclaration){
176
+ const packageState=existingPackage??await readExistingPackage(workspaceRoot);
177
+ if(!packageState.exists)return Object.freeze({exists:false,updated:false});
178
+ const {packagePath,source,document:existing}=packageState;
172
179
  const generated=JSON.parse(files.get('package.json'));
173
180
  const conflicts=[];
174
181
  if(existing.private!==undefined&&existing.private!==true)conflicts.push('private must be true');
175
182
  if(existing.type!==undefined&&existing.type!=='module')conflicts.push('type must be "module"');
176
- const declared=existing.devDependencies?.[SDK_NAME]??existing.dependencies?.[SDK_NAME];
177
- if(declared!==undefined&&!isSupportedSdkDeclaration(declared)){
178
- conflicts.push(`${SDK_NAME} must be ${SDK_VERSION} or a local file: tarball`);
179
- }
180
183
  const appSelectionScripts=new Set(['build','run']);
181
184
  for(const [name,command] of Object.entries(generated.scripts)){
182
185
  if(appSelectionScripts.has(name))continue;
@@ -192,12 +195,15 @@ async function prepareExistingPackage(workspaceRoot,files){
192
195
  private:true,
193
196
  type:'module',
194
197
  scripts:{...generated.scripts,...(existing.scripts||{})},
195
- devDependencies:{...(existing.devDependencies||{}),[SDK_NAME]:declared??SDK_VERSION},
198
+ devDependencies:{
199
+ ...(existing.devDependencies||{}),
200
+ [sdkDeclaration.dependencyName]:sdkDeclaration.specifier
201
+ },
196
202
  engines:{...generated.engines,...(existing.engines||{})}
197
203
  };
198
- if(existing.dependencies?.[SDK_NAME]!==undefined){
204
+ if(existing.dependencies?.[sdkDeclaration.dependencyName]!==undefined){
199
205
  merged.dependencies={...existing.dependencies};
200
- delete merged.dependencies[SDK_NAME];
206
+ delete merged.dependencies[sdkDeclaration.dependencyName];
201
207
  if(Object.keys(merged.dependencies).length===0)delete merged.dependencies;
202
208
  }
203
209
  if(JSON.stringify(existing)===JSON.stringify(merged)){
@@ -422,6 +428,20 @@ export async function initWorkspace({
422
428
  const sdkBrowserRuntimeReceipt=workspaceMode==='external'
423
429
  ?await verifySdkBrowserRuntime({signal,onEvent})
424
430
  :null;
431
+ const existingPackage=workspaceMode==='external'
432
+ ?await readExistingPackage(resolvedRoot)
433
+ :null;
434
+ const sdkDeclaration=workspaceMode==='external'
435
+ ?profile?.config.sdkPackageSource!==undefined
436
+ ?resolveSdkPackageDeclaration(existingPackage.document??{}, {
437
+ allowMissing:true,
438
+ packageSource:profile.config.sdkPackageSource
439
+ })
440
+ :(existingPackage.exists
441
+ ?resolveSdkPackageDeclaration(existingPackage.document,{allowMissing:true})
442
+ :null)
443
+ ??defaultSdkDeclaration()
444
+ :null;
425
445
  const template=workspaceMode==='integrated'
426
446
  ?workspaceTemplate({
427
447
  appId,
@@ -437,12 +457,20 @@ export async function initWorkspace({
437
457
  displayName,
438
458
  runtimeRelease:runtimeReceipt,
439
459
  sdkBrowserRuntimeRelease:sdkBrowserRuntimeReceipt,
460
+ sdkDependencyName:sdkDeclaration.dependencyName,
461
+ sdkDependencySpecifier:sdkDeclaration.specifier,
462
+ sdkPackageSource:sdkDeclaration.packageSource,
440
463
  target,
441
464
  appIcon:await scaffoldIcon(target)
442
465
  });
443
466
  const packagePlan=workspaceMode==='integrated'
444
467
  ?Object.freeze({exists:true,updated:false})
445
- :await prepareExistingPackage(resolvedRoot,template.files);
468
+ :await prepareExistingPackage(
469
+ resolvedRoot,
470
+ template.files,
471
+ existingPackage,
472
+ sdkDeclaration
473
+ );
446
474
  if(workspaceMode==='external'){
447
475
  await assertExistingLockAdmission(resolvedRoot,template.files);
448
476
  }
@@ -1,6 +1,6 @@
1
1
  import {createHash} from 'node:crypto';
2
2
  import {constants as FS_CONSTANTS} from 'node:fs';
3
- import {lstat,open,readdir,realpath} from 'node:fs/promises';
3
+ import {lstat,open,readFile,readdir,realpath} from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import {fileURLToPath} from 'node:url';
6
6
  import {SDK_VERSION} from './constants.mjs';
@@ -8,96 +8,49 @@ import {SDK_VERSION} from './constants.mjs';
8
8
  const MANIFEST_NAME='ARCANE_SDK_BROWSER_RELEASE.json';
9
9
  const BUILDER='arcane-sdk-browser-runtime-v1';
10
10
  const PROTOCOL='arcane-sdk-browser-runtime/1';
11
- export const SDK_BROWSER_RUNTIME_MANIFEST_SHA256=
12
- '88395493b411fd5461fbb2bb065ae2b745f6d1672f796583fd248ec97f71f4f7';
13
- export const SDK_BROWSER_RUNTIME_CONTENT_SHA256=
14
- '5e03f45a732db51cb5a2b2193cc79ecda34501d07a9b2e82e794e5fa37d55d00';
15
11
  const REPOSITORY='https://github.com/TheWizardNexus/arcane-os-sdk.git';
16
12
  const SHA256_PATTERN=/^[a-f0-9]{64}$/u;
17
13
  const READ_ONLY_NO_FOLLOW=FS_CONSTANTS.O_RDONLY|(FS_CONSTANTS.O_NOFOLLOW??0);
18
14
  const MAX_VERIFIED_FILE_BYTES=64*1024*1024;
19
15
  const packageRoot=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
20
16
  const defaultRoot=path.join(packageRoot,'browser-runtime');
17
+ const canonicalManifestBytes=await readFile(path.join(defaultRoot,MANIFEST_NAME));
18
+ const canonicalRelease=JSON.parse(canonicalManifestBytes.toString('utf8'));
19
+ if(
20
+ canonicalRelease?.schemaVersion!==1
21
+ ||canonicalRelease.builder!==BUILDER
22
+ ||canonicalRelease.sdkVersion!==SDK_VERSION
23
+ ||!SHA256_PATTERN.test(canonicalRelease.contentSha256)
24
+ ){
25
+ throw new Error('The canonical SDK browser runtime receipt is invalid.');
26
+ }
27
+ export const SDK_BROWSER_RUNTIME_MANIFEST_SHA256=
28
+ createHash('sha256').update(canonicalManifestBytes).digest('hex');
29
+ export const SDK_BROWSER_RUNTIME_CONTENT_SHA256=canonicalRelease.contentSha256;
21
30
  const issuedReceipts=new WeakSet();
22
- const expectedAiComponents=Object.freeze({
23
- schemaVersion:1,
24
- kind:'arcane-ai-browser-wasm-components',
25
- protocol:'arcane-ai-browser-wasm/1',
26
- packageExport:'arcane-os/ai/browser-wasm',
27
- browserEntry:'ai/browser-wasm.mjs',
28
- runtimePolicy:{
29
- modelAuthorities:'caller-supplied-exact-bytes-and-sha256',
30
- modelWeightsPacked:false,
31
- remoteModelHelpers:false,
32
- compatibilityRuntime:false,
33
- toolCalls:'structural-only-never-executed'
34
- },
35
- components:[
36
- {
37
- name:'@wllama/wllama',
38
- version:'3.6.0',
39
- licenseSpdx:'MIT',
40
- sourceRepository:'https://github.com/ngxson/wllama.git',
41
- sourceRevision:'f16050d8d51a00602c6a2a6b8ac9c09f490eea7f',
42
- npm:{
43
- resolved:'https://registry.npmjs.org/@wllama/wllama/-/wllama-3.6.0.tgz',
44
- integrity:'sha512-NN3ZBXqaaUwGXTQubkNvsCaLPjN2XVa0bVS40OYCE8zquYmRc2W3oHYEgwvuSWWDB8aUqTLyMioySCXNkcnD1w==',
45
- tarballBytes:5671369,
46
- tarballSha256:'137c35ceccb4911a9b0ce9b427889f75991654ec6a6d1dd8fabd879b14b07a1b'
47
- },
48
- files:[
49
- {
50
- role:'runtime-module',
51
- path:'ai/wllama/index.mjs',
52
- sourcePath:'node_modules/@wllama/wllama/esm/index.js',
53
- bytes:373519,
54
- sha256:'4637e42d636010493a9b274fbbe70bfd8120365da726b1d9e589d85ca84a00d6'
55
- },
56
- {
57
- role:'runtime-wasm',
58
- path:'ai/wllama/wllama.wasm',
59
- sourcePath:'node_modules/@wllama/wllama/esm/wasm/wllama.wasm',
60
- bytes:8524865,
61
- sha256:'95c6ff9ef2a03ff2c63bc91db132f0126a0bd0456b272cd8ae2e0f592fb059f6'
62
- },
63
- {
64
- role:'license',
65
- path:'ai/wllama/LICENCE',
66
- sourcePath:'node_modules/@wllama/wllama/LICENCE',
67
- bytes:1071,
68
- sha256:'5866e3bd7e3cbd3f7c8bea6efd8a1e7fa7cc8de68c30f428aff7c6584a0fb720'
69
- }
70
- ]
71
- },
72
- {
73
- name:'llama.cpp',
74
- version:'b10454',
75
- licenseSpdx:'MIT',
76
- sourceRepository:'https://github.com/ggerganov/llama.cpp.git',
77
- sourceRevision:'4df29be4f4c3673f428170fda944a5b19f743bb8',
78
- embeddedBy:'@wllama/wllama@3.6.0',
79
- files:[
80
- {
81
- role:'license',
82
- path:'ai/wllama/llama.cpp-LICENSE',
83
- sourceUrl:'https://raw.githubusercontent.com/ggerganov/llama.cpp/4df29be4f4c3673f428170fda944a5b19f743bb8/LICENSE',
84
- bytes:1078,
85
- sha256:'94f29bbed6a22c35b992c5c6ebf0e7c92f13b836b90f36f461c9cf2f0f1d010d'
86
- }
87
- ]
88
- }
89
- ]
90
- });
31
+ const canonicalAiComponentsBytes=await readFile(
32
+ path.join(defaultRoot,'ai','ARCANE_AI_BROWSER_WASM_COMPONENTS.json')
33
+ );
34
+ const expectedAiComponents=Object.freeze(
35
+ JSON.parse(canonicalAiComponentsBytes.toString('utf8'))
36
+ );
91
37
 
92
38
  const expectedFiles=Object.freeze([
93
39
  ['ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json','browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json','sdk-source-identity'],
40
+ ['ai/browser-kokoro-worker.mjs','browser-runtime/ai/browser-kokoro-worker.mjs','sdk-source-identity'],
41
+ ['ai/browser-speech-artifacts.mjs','browser-runtime/ai/browser-speech-artifacts.mjs','sdk-source-identity'],
42
+ ['ai/browser-speech-providers.mjs','browser-runtime/ai/browser-speech-providers.mjs','sdk-source-identity'],
43
+ ['ai/browser-speech.mjs','browser-runtime/ai/browser-speech.mjs','sdk-source-identity'],
94
44
  ['ai/browser-wasm-llm-provider.mjs','browser-runtime/ai/browser-wasm-llm-provider.mjs','sdk-source-identity'],
95
45
  ['ai/browser-wasm.mjs','browser-runtime/ai/browser-wasm.mjs','sdk-source-identity'],
46
+ ['ai/browser-whisper-worker.mjs','browser-runtime/ai/browser-whisper-worker.mjs','sdk-source-identity'],
96
47
  ['ai/browser-wllama-runtime.mjs','browser-runtime/ai/browser-wllama-runtime.mjs','sdk-source-identity'],
97
48
  ['ai/internal/sha256.mjs','browser-runtime/ai/internal/sha256.mjs','sdk-source-identity'],
98
49
  ['ai/model-controller.mjs','browser-runtime/ai/model-controller.mjs','sdk-source-identity'],
50
+ ['ai/speech-worker-client.mjs','browser-runtime/ai/speech-worker-client.mjs','sdk-source-identity'],
51
+ ['ai/speech-worker-runtime.mjs','browser-runtime/ai/speech-worker-runtime.mjs','sdk-source-identity'],
99
52
  ['ai/wllama/LICENCE','node_modules/@wllama/wllama/LICENCE','vendor-package-identity'],
100
- ['ai/wllama/index.mjs','node_modules/@wllama/wllama/esm/index.js','vendor-package-identity'],
53
+ ['ai/wllama/index.mjs','browser-runtime/ai/wllama/index.mjs','deterministic-derived-vendor'],
101
54
  ['ai/wllama/llama.cpp-LICENSE','browser-runtime/ai/wllama/llama.cpp-LICENSE','vendor-source-identity'],
102
55
  ['ai/wllama/wllama.wasm','node_modules/@wllama/wllama/esm/wasm/wllama.wasm','vendor-package-identity'],
103
56
  ['dependencies/event-pubsub/index.js','node_modules/event-pubsub/index.js','vendor-package-identity'],
@@ -4,6 +4,9 @@ import {
4
4
  SDK_BROWSER_RUNTIME_MANIFEST_SHA256
5
5
  } from '../sdk-browser-runtime.mjs';
6
6
 
7
+ const NPM_PACKAGE_NAME_PATTERN=/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
8
+ const LOCAL_TARBALL_PATTERN=/^file:.+\.tgz$/iu;
9
+
7
10
  function json(value){
8
11
  return `${JSON.stringify(value,null,2)}\n`;
9
12
  }
@@ -28,12 +31,28 @@ export function workspaceTemplate({
28
31
  displayName,
29
32
  runtimeRelease,
30
33
  sdkBrowserRuntimeRelease,
34
+ sdkDependencyName=SDK_NAME,
35
+ sdkDependencySpecifier=SDK_VERSION,
36
+ sdkPackageSource=`node_modules/${sdkDependencyName}`,
31
37
  appOnly=false,
32
38
  namedImports=true,
33
39
  minimumCoreVersion='0.8.12',
34
40
  target='browser',
35
41
  appIcon
36
42
  }){
43
+ const canonicalSpecifier=sdkDependencyName===SDK_NAME
44
+ &&(sdkDependencySpecifier===SDK_VERSION
45
+ ||(typeof sdkDependencySpecifier==='string'
46
+ &&LOCAL_TARBALL_PATTERN.test(sdkDependencySpecifier)
47
+ &&!/[\x00-\x1f\x7f]/.test(sdkDependencySpecifier)));
48
+ const aliasSpecifier=sdkDependencyName!==SDK_NAME
49
+ &&sdkDependencySpecifier===`npm:${SDK_NAME}@${SDK_VERSION}`;
50
+ if(typeof sdkDependencyName!=='string'||sdkDependencyName.length>214
51
+ ||!NPM_PACKAGE_NAME_PATTERN.test(sdkDependencyName)
52
+ ||sdkPackageSource!==`node_modules/${sdkDependencyName}`
53
+ ||(!canonicalSpecifier&&!aliasSpecifier)){
54
+ throw new Error('Invalid scaffold SDK installation authority.');
55
+ }
37
56
  const supportedTargets=['browser','portable','windows-x64','linux-x64','linux-arm64','android-arm64'];
38
57
  if(!supportedTargets.includes(target)){
39
58
  throw new Error(`Unsupported scaffold target: ${String(target)}.`);
@@ -47,7 +66,7 @@ export function workspaceTemplate({
47
66
  const name=displayName||`Arcane ${titleCase(appId)}`;
48
67
  const packageName=`arcane-${appId}`;
49
68
  const runtimeContentSha256=runtimeRelease?.contentSha256;
50
- const upstreamCommit=runtimeRelease?.source?.commit;
69
+ const upstreamCommit=runtimeRelease?.source?.legacyProjection?.commit;
51
70
  const browserManifestSha256=sdkBrowserRuntimeRelease?.manifestSha256;
52
71
  const browserContentSha256=sdkBrowserRuntimeRelease?.contentSha256;
53
72
  const browserSource=sdkBrowserRuntimeRelease?.source;
@@ -153,15 +172,15 @@ appropriate.
153
172
  files.set('AGENTS.md',`# ${name} development instructions
154
173
 
155
174
  - Use plain JavaScript, HTML, and CSS; do not introduce TypeScript or TSX.
156
- - Keep reusable mechanisms in Arcane OS and app-specific behavior under \`apps/${appId}/\`.
175
+ - Keep reusable portable mechanisms in the Arcane SDK and app-specific behavior under \`apps/${appId}/\`.
157
176
  - Keep \`arcane/css/theme.css\` before app styles and import \`arcane/ThemeBootstrap\` before app code runs.
158
177
  - Use \`rgb(...)\` or \`rgba(...)\` for new CSS colors.
159
178
  - Build one named app and one explicit target at a time. Native targets may be unavailable until their adapters are installed.
160
- - Run \`npm run check\` before committing.
179
+ - Run tests and checks only when the user explicitly selects verification or a distribution artifact requires it.
161
180
  `);
162
181
  files.set('README.md',`# ${name}
163
182
 
164
- This repository contains the Arcane OS application \`${appId}\`. Its source stays outside the Arcane OS repository and uses the \`${SDK_NAME}\` toolchain.
183
+ This repository contains the portable Arcane application \`${appId}\`. It embeds immutable SDK runtime bytes for distribution and has no runtime dependency on an Arcane OS source checkout.
165
184
 
166
185
  ## Start
167
186
 
@@ -222,7 +241,7 @@ Every browser release also carries Arcane OS licensing material under \`licenses
222
241
  }:{})
223
242
  },
224
243
  devDependencies:{
225
- [SDK_NAME]:SDK_VERSION
244
+ [sdkDependencyName]:sdkDependencySpecifier
226
245
  },
227
246
  engines:{node:'>=22.23.2'}
228
247
  }));
@@ -239,7 +258,7 @@ Every browser release also carries Arcane OS licensing material under \`licenses
239
258
  exclude:[]
240
259
  },
241
260
  {
242
- source:'node_modules/arcane-os',
261
+ source:sdkPackageSource,
243
262
  destination:'licenses/arcane-os',
244
263
  include:['LICENSE','COMMERCIAL-LICENSE.md','NOTICE'],
245
264
  exclude:[]
@@ -251,12 +270,12 @@ Every browser release also carries Arcane OS licensing material under \`licenses
251
270
  schemaVersion:1,
252
271
  sdk:{name:SDK_NAME,version:SDK_VERSION},
253
272
  runtime:{
254
- manifest:'node_modules/arcane-os/runtime/ARCANE_RUNTIME_RELEASE.json',
273
+ manifest:`${sdkPackageSource}/runtime/ARCANE_RUNTIME_RELEASE.json`,
255
274
  contentSha256:runtimeContentSha256,
256
275
  upstreamCommit
257
276
  },
258
277
  sdkBrowserRuntime:{
259
- manifest:'node_modules/arcane-os/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json',
278
+ manifest:`${sdkPackageSource}/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json`,
260
279
  manifestSha256:browserManifestSha256,
261
280
  contentSha256:browserContentSha256,
262
281
  builder:sdkBrowserRuntimeRelease?.builder,
package/src/toolchain.mjs CHANGED
@@ -380,11 +380,19 @@ async function preparedWorkspace(options){
380
380
  onEvent:options.onEvent
381
381
  });
382
382
  const external=workspace.workspaceMode==='external';
383
+ if(external&&(!workspace.sdkInstallation
384
+ ||typeof workspace.sdkInstallation.runtimeRoot!=='string'
385
+ ||typeof workspace.sdkInstallation.browserRuntimeRoot!=='string')){
386
+ throw new ArcaneError(
387
+ ERROR_CODES.workspaceInvalid,
388
+ 'Validated external workspace is missing its bound SDK installation authority.'
389
+ );
390
+ }
383
391
  const runtimeRoot=external
384
- ?path.join(workspace.workspaceRoot,'node_modules','arcane-os','runtime')
392
+ ?workspace.sdkInstallation.runtimeRoot
385
393
  :workspace.workspaceRoot;
386
394
  const browserRuntimeRoot=external
387
- ?path.join(workspace.workspaceRoot,'node_modules','arcane-os','browser-runtime')
395
+ ?workspace.sdkInstallation.browserRuntimeRoot
388
396
  :null;
389
397
  const runtimeReceipt=external&&!options.deferRuntimeVerification
390
398
  ?await verifyRuntime({
@@ -791,6 +799,9 @@ export async function developApplication(options={}){
791
799
  mode:'source',
792
800
  workspaceRuntimeReceipt:prepared.workspaceRuntimeReceipt,
793
801
  workspaceMode:prepared.workspaceMode,
802
+ ...(options.sdkRuntimeSourceRoot===undefined?{}:{
803
+ sdkRuntimeSourceRoot:options.sdkRuntimeSourceRoot
804
+ }),
794
805
  host:options.host,
795
806
  port:options.port,
796
807
  signal:options.signal,
@@ -543,7 +543,7 @@ export async function verifyWorkspaceRuntime({
543
543
  sdkVersion:runtimeReceipt.sdkVersion,
544
544
  sources:Object.freeze({
545
545
  arcane:Object.freeze({
546
- authority:'arcane-os-upstream',
546
+ authority:'arcane-os-sdk',
547
547
  location:runtimeReceipt.canonicalLocation,
548
548
  manifestSha256:runtimeReceipt.manifestSha256,
549
549
  contentSha256:runtimeReceipt.contentSha256,