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
@@ -15,8 +15,9 @@ import {
15
15
  } from './workspace-runtime.mjs';
16
16
  import {verifyRuntime} from './runtime.mjs';
17
17
  import {verifySdkBrowserRuntime} from './sdk-browser-runtime.mjs';
18
- import {resolveWorkspace} from './workspace.mjs';
18
+ import {resolveWorkspace,validateWorkspace} from './workspace.mjs';
19
19
  import {createEventQueue} from './event-queue.mjs';
20
+ import {SDK_NAME,SDK_VERSION} from './constants.mjs';
20
21
 
21
22
  const MIME_TYPES=new Map([
22
23
  ['.css','text/css; charset=utf-8'],
@@ -44,6 +45,17 @@ const SESSION_COOKIE='Arcane-Dev-Session';
44
45
  const PRIVATE_SOURCE_SEGMENTS=new Set([
45
46
  'arcane-app.json','arcane-package.json','test','tests','scripts','node_modules','dist','local'
46
47
  ]);
48
+ const SDK_RUNTIME_SOURCE_PROTOCOL='arcane-sdk-runtime-source/1';
49
+ const SDK_RUNTIME_SOURCE_ARCANE_ROOTS=new Set([
50
+ 'components','css','entities','img','modules','security'
51
+ ]);
52
+ const SDK_RUNTIME_SOURCE_PRIVATE_SEGMENTS=new Set([
53
+ 'node_modules','.git','.hg','.svn','cvs'
54
+ ]);
55
+ const SDK_RUNTIME_SOURCE_PRIVATE_MANIFESTS=new Set([
56
+ 'arcane-app.json','arcane-package.json','arcane.lock.json',
57
+ 'arcane_app_release.json','arcane_runtime_release.json','arcane_sdk_browser_release.json'
58
+ ]);
47
59
 
48
60
  // This server is a loopback-only development host, not a production policy
49
61
  // boundary. The bundled runtime currently needs inline component execution,
@@ -139,6 +151,173 @@ function inventoryKey(value){
139
151
  return process.platform==='win32'?value.toLowerCase():value;
140
152
  }
141
153
 
154
+ function canonicalLocationKey(value){
155
+ return inventoryKey(path.resolve(value));
156
+ }
157
+
158
+ function pathIsWithin(root,candidate){
159
+ const relative=path.relative(root,candidate);
160
+ return relative===''||(!path.isAbsolute(relative)&&relative!=='..'
161
+ &&!relative.startsWith(`..${path.sep}`));
162
+ }
163
+
164
+ function pathsOverlap(left,right){
165
+ return pathIsWithin(left,right)||pathIsWithin(right,left);
166
+ }
167
+
168
+ async function canonicalRealDirectory(requested,label){
169
+ let info;
170
+ try{
171
+ info=await lstat(requested);
172
+ }catch{
173
+ fail(`${label} must be an existing real directory.`,'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
174
+ }
175
+ if(info.isSymbolicLink()||!info.isDirectory()){
176
+ fail(`${label} must be an existing real directory.`,'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
177
+ }
178
+ let canonical;
179
+ try{
180
+ canonical=await realpath(requested);
181
+ }catch{
182
+ fail(`${label} could not be resolved as a real directory.`,'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
183
+ }
184
+ if(canonicalLocationKey(canonical)!==canonicalLocationKey(requested)){
185
+ fail(`${label} must not contain a symlink or reparse-point escape.`,'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
186
+ }
187
+ return canonical;
188
+ }
189
+
190
+ function sdkRuntimeSourcePathAllowed(relative,{arcaneRoot=false}={}){
191
+ if(relative.length===0)return false;
192
+ const normalized=relative.map(function normalizeRuntimeSourceSegment(segment){
193
+ return segment.normalize('NFC').toLowerCase();
194
+ });
195
+ if(normalized.some(function runtimeSourceSegmentIsPrivate(segment){
196
+ return segment.startsWith('.')||SDK_RUNTIME_SOURCE_PRIVATE_SEGMENTS.has(segment)
197
+ ||SDK_RUNTIME_SOURCE_PRIVATE_MANIFESTS.has(segment);
198
+ }))return false;
199
+ return !arcaneRoot||SDK_RUNTIME_SOURCE_ARCANE_ROOTS.has(normalized[0]);
200
+ }
201
+
202
+ function sdkArcaneSourcePathAllowed(relative){
203
+ return sdkRuntimeSourcePathAllowed(relative,{arcaneRoot:true});
204
+ }
205
+
206
+ function sdkDependencySourcePathAllowed(relative){
207
+ return sdkRuntimeSourcePathAllowed(relative);
208
+ }
209
+
210
+ function sdkBrowserSourcePathAllowed(relative){
211
+ return sdkRuntimeSourcePathAllowed(relative);
212
+ }
213
+
214
+ async function emitRuntimeSourceEvent(onEvent,event){
215
+ if(typeof onEvent==='function')await onEvent(event);
216
+ }
217
+
218
+ async function verifySdkRuntimeSourceRoot(sourceRoot,workspaceRoot,appId,{signal,onEvent}={}){
219
+ throwIfAborted(signal);
220
+ if(typeof sourceRoot!=='string'||!sourceRoot.trim()){
221
+ fail('sdkRuntimeSourceRoot must name an Arcane SDK directory.',
222
+ 'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
223
+ }
224
+ const requestedRoot=path.resolve(sourceRoot);
225
+ await emitRuntimeSourceEvent(onEvent,{
226
+ type:'runtime.source.mount.started',
227
+ appId,
228
+ requestedRoot,
229
+ target:'browser'
230
+ });
231
+ const canonicalRoot=await canonicalRealDirectory(requestedRoot,'SDK runtime source root');
232
+ const canonicalWorkspaceRoot=await realpath(workspaceRoot);
233
+ if(pathsOverlap(canonicalRoot,canonicalWorkspaceRoot)){
234
+ fail('SDK runtime source root must not overlap the application workspace.',
235
+ 'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
236
+ }
237
+
238
+ let packageFile;
239
+ try{
240
+ packageFile=await openSafeFile(canonicalRoot,['package.json']);
241
+ }catch{
242
+ fail('SDK runtime source package.json must be a readable bounded real file.',
243
+ 'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
244
+ }
245
+ if(!packageFile){
246
+ fail('SDK runtime source root must contain a real package.json.',
247
+ 'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
248
+ }
249
+ let packageDocument;
250
+ try{
251
+ packageDocument=JSON.parse(packageFile.bytes.toString('utf8'));
252
+ }catch{
253
+ fail('SDK runtime source package.json must be valid JSON.',
254
+ 'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
255
+ }
256
+ if(!packageDocument||Array.isArray(packageDocument)||packageDocument.name!==SDK_NAME){
257
+ fail(`SDK runtime source package name must be exactly ${SDK_NAME}.`,
258
+ 'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
259
+ }
260
+ if(packageDocument.version!==SDK_VERSION){
261
+ fail(
262
+ `SDK runtime source version must exactly match the executing SDK (${SDK_VERSION}).`,
263
+ 'ARCANE_DEV_RUNTIME_VERSION_MISMATCH'
264
+ );
265
+ }
266
+
267
+ const roots=[
268
+ {
269
+ path:'runtime/arcane',
270
+ prefix:['arcane'],
271
+ allow:sdkArcaneSourcePathAllowed
272
+ },
273
+ {
274
+ path:'runtime/strong-type',
275
+ prefix:['arcane','dependencies','strong-type'],
276
+ allow:sdkDependencySourcePathAllowed
277
+ },
278
+ {
279
+ path:'browser-runtime',
280
+ prefix:['arcane','sdk'],
281
+ allow:sdkBrowserSourcePathAllowed
282
+ }
283
+ ];
284
+ const mappings=[];
285
+ for(let index=0;index<roots.length;index+=1){
286
+ throwIfAborted(signal);
287
+ const root=roots[index];
288
+ const requested=path.join(canonicalRoot,...root.path.split('/'));
289
+ const canonical=await canonicalRealDirectory(requested,`SDK runtime source ${root.path}`);
290
+ if(!pathIsWithin(canonicalRoot,canonical)){
291
+ fail(`SDK runtime source ${root.path} must remain inside the SDK root.`,
292
+ 'ARCANE_DEV_RUNTIME_SOURCE_INVALID');
293
+ }
294
+ mappings.push({prefix:root.prefix,root:canonical,allow:root.allow});
295
+ await emitRuntimeSourceEvent(onEvent,{
296
+ type:'runtime.source.mount.progress',
297
+ current:index+1,
298
+ total:roots.length,
299
+ path:root.path
300
+ });
301
+ }
302
+ const runtime=Object.freeze({
303
+ mode:'sdk-source',
304
+ protocol:SDK_RUNTIME_SOURCE_PROTOCOL,
305
+ sdkVersion:SDK_VERSION,
306
+ mutable:true,
307
+ distributionAuthority:false,
308
+ sourceRoot:canonicalRoot
309
+ });
310
+ await emitRuntimeSourceEvent(onEvent,{
311
+ type:'runtime.source.mount.ready',
312
+ appId,
313
+ canonicalRoot,
314
+ sdkVersion:SDK_VERSION,
315
+ protocol:SDK_RUNTIME_SOURCE_PROTOCOL,
316
+ routeCount:mappings.length
317
+ });
318
+ return {mappings,runtime};
319
+ }
320
+
142
321
  function identityMap(identities,prefix=''){
143
322
  const normalizedPrefix=prefix?`${prefix}/`:'';
144
323
  const result=new Map();
@@ -346,8 +525,34 @@ function sharedPathAllowed(relative,route){
346
525
  });
347
526
  }
348
527
 
349
- async function sourceRoutes(workspaceRoot,appId,{workspaceRuntimeReceipt,signal}){
528
+ async function sourceRoutes(workspaceRoot,appId,{
529
+ workspaceRuntimeReceipt,
530
+ sdkRuntimeSourceRoot,
531
+ signal,
532
+ onEvent
533
+ }){
350
534
  const resolved=await resolveWorkspace({workspaceRoot,appId});
535
+ const appMapping={
536
+ prefix:['apps',resolved.appId],
537
+ root:resolved.appRoot,
538
+ allow:relative=>sourcePathAllowed(relative,resolved.app.manifest)
539
+ };
540
+ if(sdkRuntimeSourceRoot!==undefined){
541
+ const sdkSource=await verifySdkRuntimeSourceRoot(
542
+ sdkRuntimeSourceRoot,
543
+ resolved.workspaceRoot,
544
+ resolved.appId,
545
+ {signal,onEvent}
546
+ );
547
+ return {
548
+ workspaceRoot:resolved.workspaceRoot,
549
+ workspaceMode:resolved.config.workspaceMode,
550
+ appId:resolved.appId,
551
+ startPath:`/apps/${resolved.appId}/${resolved.app.manifest.entry}`,
552
+ runtime:sdkSource.runtime,
553
+ mappings:[appMapping,...sdkSource.mappings]
554
+ };
555
+ }
351
556
  if(resolved.config.workspaceMode==='integrated'){
352
557
  return {
353
558
  workspaceRoot:resolved.workspaceRoot,
@@ -355,11 +560,7 @@ async function sourceRoutes(workspaceRoot,appId,{workspaceRuntimeReceipt,signal}
355
560
  appId:resolved.appId,
356
561
  startPath:`/apps/${resolved.appId}/${resolved.app.manifest.entry}`,
357
562
  mappings:[
358
- {
359
- prefix:['apps',resolved.appId],
360
- root:resolved.appRoot,
361
- allow:relative=>sourcePathAllowed(relative,resolved.app.manifest)
362
- },
563
+ appMapping,
363
564
  ...resolved.config.sharedPayloads['browser-runtime'].map(route=>({
364
565
  prefix:route.destination.split('/'),
365
566
  root:path.join(resolved.workspaceRoot,...route.source.split('/')),
@@ -369,24 +570,29 @@ async function sourceRoutes(workspaceRoot,appId,{workspaceRuntimeReceipt,signal}
369
570
  };
370
571
  }
371
572
  const runtimeRoot=path.join(resolved.workspaceRoot,'arcane');
573
+ const validation=await validateWorkspace({
574
+ workspaceRoot:resolved.workspaceRoot,
575
+ appId:resolved.appId,
576
+ allowMissingManagedImportMap:true,
577
+ signal
578
+ });
579
+ const sdkInstallation=validation.sdkInstallation;
580
+ if(!sdkInstallation
581
+ ||typeof sdkInstallation.runtimeRoot!=='string'
582
+ ||typeof sdkInstallation.browserRuntimeRoot!=='string'){
583
+ fail(
584
+ 'Validated external workspace is missing its bound SDK installation authority.',
585
+ 'ARCANE_WORKSPACE_INVALID'
586
+ );
587
+ }
372
588
  let verified=workspaceRuntimeReceipt;
373
589
  if(!verified){
374
- const sdkRuntimeRoot=path.join(
375
- resolved.workspaceRoot,
376
- 'node_modules',
377
- 'arcane-os',
378
- 'runtime'
379
- );
590
+ const sdkRuntimeRoot=sdkInstallation.runtimeRoot;
380
591
  const sdkRuntimeReceipt=await verifyRuntime({
381
592
  runtimeRoot:sdkRuntimeRoot,
382
593
  signal
383
594
  });
384
- const sdkBrowserRuntimeRoot=path.join(
385
- resolved.workspaceRoot,
386
- 'node_modules',
387
- 'arcane-os',
388
- 'browser-runtime'
389
- );
595
+ const sdkBrowserRuntimeRoot=sdkInstallation.browserRuntimeRoot;
390
596
  const sdkBrowserRuntimeReceipt=await verifySdkBrowserRuntime({
391
597
  browserRuntimeRoot:sdkBrowserRuntimeRoot,
392
598
  signal
@@ -404,6 +610,17 @@ async function sourceRoutes(workspaceRoot,appId,{workspaceRuntimeReceipt,signal}
404
610
  workspaceRoot:resolved.workspaceRoot,
405
611
  signal
406
612
  });
613
+ if(typeof verified.sourceRuntimeLocation!=='string'
614
+ ||typeof verified.sourceBrowserRuntimeLocation!=='string'
615
+ ||canonicalLocationKey(verified.sourceRuntimeLocation)
616
+ !==canonicalLocationKey(sdkInstallation.runtimeRoot)
617
+ ||canonicalLocationKey(verified.sourceBrowserRuntimeLocation)
618
+ !==canonicalLocationKey(sdkInstallation.browserRuntimeRoot)){
619
+ fail(
620
+ 'Workspace runtime receipt sources do not match the bound SDK installation authority.',
621
+ 'ARCANE_INTEGRITY_FAILED'
622
+ );
623
+ }
407
624
  const arcaneIdentities=identityMap(verified.identities);
408
625
  return {
409
626
  workspaceRoot:resolved.workspaceRoot,
@@ -411,11 +628,7 @@ async function sourceRoutes(workspaceRoot,appId,{workspaceRuntimeReceipt,signal}
411
628
  appId:resolved.appId,
412
629
  startPath:`/apps/${resolved.appId}/${resolved.app.manifest.entry}`,
413
630
  mappings:[
414
- {
415
- prefix:['apps',resolved.appId],
416
- root:resolved.appRoot,
417
- allow:relative=>sourcePathAllowed(relative,resolved.app.manifest)
418
- },
631
+ appMapping,
419
632
  {
420
633
  prefix:['arcane'],
421
634
  root:runtimeRoot,
@@ -506,18 +719,33 @@ async function startOwnedDevServer({
506
719
  port=0,
507
720
  signal,
508
721
  workspaceRuntimeReceipt,
722
+ sdkRuntimeSourceRoot,
509
723
  releaseReceipt
510
724
  }={},events,releaseSignal){
511
725
  throwIfAborted(signal);
512
726
  if(mode!=='source'&&mode!=='packaged')fail(`Unsupported server mode: ${String(mode)}.`,'ARCANE_USAGE');
727
+ if(sdkRuntimeSourceRoot!==undefined&&mode!=='source'){
728
+ fail('sdkRuntimeSourceRoot is supported only in source development mode.','ARCANE_USAGE');
729
+ }
513
730
  if(host!=='127.0.0.1'&&host!=='::1'){
514
731
  fail('Development server host must be a numeric loopback address (127.0.0.1 or ::1).','ARCANE_POLICY_DENIED');
515
732
  }
516
733
  if(!Number.isInteger(port)||port<0||port>65535)fail('port must be an integer from 0 through 65535.','ARCANE_USAGE');
517
- await events.send({type:'server.starting',mode,host,port,appId});
734
+ const requestedRuntimeMode=mode==='source'&&sdkRuntimeSourceRoot!==undefined
735
+ ?'sdk-source'
736
+ :null;
737
+ await events.send({
738
+ type:'server.starting',
739
+ mode,
740
+ host,
741
+ port,
742
+ appId,
743
+ ...(requestedRuntimeMode?{runtimeMode:requestedRuntimeMode}:{})
744
+ });
518
745
  const routeSet=mode==='source'
519
746
  ?await sourceRoutes(workspaceRoot,appId,{
520
747
  workspaceRuntimeReceipt,
748
+ sdkRuntimeSourceRoot,
521
749
  signal,
522
750
  onEvent:event=>events.send(event)
523
751
  })
@@ -647,6 +875,17 @@ async function startOwnedDevServer({
647
875
  while(requestTasks.size>0){
648
876
  await Promise.allSettled([...requestTasks]);
649
877
  }
878
+ if(routeSet.runtime?.mode==='sdk-source'){
879
+ await events.send({
880
+ type:'runtime.source.mount.stopped',
881
+ appId:routeSet.appId,
882
+ reason:events.error||operationalError
883
+ ?'failed'
884
+ :signal?.aborted
885
+ ?'cancelled'
886
+ :'closed'
887
+ });
888
+ }
650
889
  await events.send({
651
890
  type:'server.stopped',
652
891
  host:address.address,
@@ -695,6 +934,10 @@ async function startOwnedDevServer({
695
934
  mode,
696
935
  workspaceRoot:routeSet.workspaceRoot,
697
936
  appId:routeSet.appId,
937
+ ...(routeSet.runtime?{
938
+ runtimeMode:routeSet.runtime.mode,
939
+ runtime:routeSet.runtime
940
+ }:{}),
698
941
  host:address.address,
699
942
  port:address.port,
700
943
  origin,
@@ -711,7 +954,11 @@ async function startOwnedDevServer({
711
954
  host:result.host,
712
955
  port:result.port,
713
956
  url,
714
- appId:result.appId
957
+ appId:result.appId,
958
+ ...(routeSet.runtime?{
959
+ runtimeMode:routeSet.runtime.mode,
960
+ runtime:routeSet.runtime
961
+ }:{})
715
962
  });
716
963
  throwIfAborted(signal);
717
964
  }catch(error){
package/src/doctor.mjs CHANGED
@@ -290,9 +290,7 @@ export async function runDoctor({
290
290
  ));
291
291
  }else if(result.workspaceMode==='external'){
292
292
  try{
293
- const installedRoot=path.join(result.workspaceRoot,'node_modules','arcane-os');
294
- const runtimeRoot=path.join(installedRoot,'runtime');
295
- const browserRuntimeRoot=path.join(installedRoot,'browser-runtime');
293
+ const {runtimeRoot,browserRuntimeRoot}=result.sdkInstallation;
296
294
  const runtimeReceipt=await verifyRuntime({runtimeRoot,signal,onEvent});
297
295
  const sdkBrowserRuntimeReceipt=await verifySdkBrowserRuntime({
298
296
  browserRuntimeRoot,