arcane-os 0.1.1 → 0.2.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +17 -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 +475 -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 +8 -5
  20. package/docs/reference/ai/browser-wasm.md +394 -0
  21. package/docs/reference/availability-and-normalization.md +31 -0
  22. package/docs/reference/behavioral-testing.md +21 -0
  23. package/docs/reference/cli.md +4 -4
  24. package/docs/reference/inventory/package-api.json +84 -4
  25. package/docs/reference/protocols.md +9 -8
  26. package/docs/reference/sdk-api.md +238 -4
  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 +280 -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 +713 -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 +293 -27
  37. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +682 -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 +6 -4
  42. package/src/cli/main.mjs +14 -2
  43. package/src/constants.mjs +1 -1
  44. package/src/dev-server.mjs +246 -14
  45. package/src/doctor.mjs +1 -1
  46. package/src/import-map.mjs +59 -1
  47. package/src/packager/core.mjs +2 -2
  48. package/src/runtime.mjs +14 -4
  49. package/src/sdk-browser-runtime.mjs +28 -75
  50. package/src/templates/workspace-template.mjs +4 -4
  51. package/src/toolchain.mjs +3 -0
  52. package/src/workspace-runtime.mjs +1 -1
  53. package/src/workspace.mjs +1 -1
@@ -17,6 +17,7 @@ import {verifyRuntime} from './runtime.mjs';
17
17
  import {verifySdkBrowserRuntime} from './sdk-browser-runtime.mjs';
18
18
  import {resolveWorkspace} 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'],
@@ -31,6 +32,7 @@ const MIME_TYPES=new Map([
31
32
  ['.png','image/png'],
32
33
  ['.svg','image/svg+xml; charset=utf-8'],
33
34
  ['.txt','text/plain; charset=utf-8'],
35
+ ['.wasm','application/wasm'],
34
36
  ['.webp','image/webp'],
35
37
  ['.woff','font/woff'],
36
38
  ['.woff2','font/woff2']
@@ -43,6 +45,17 @@ const SESSION_COOKIE='Arcane-Dev-Session';
43
45
  const PRIVATE_SOURCE_SEGMENTS=new Set([
44
46
  'arcane-app.json','arcane-package.json','test','tests','scripts','node_modules','dist','local'
45
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
+ ]);
46
59
 
47
60
  // This server is a loopback-only development host, not a production policy
48
61
  // boundary. The bundled runtime currently needs inline component execution,
@@ -51,7 +64,7 @@ const PRIVATE_SOURCE_SEGMENTS=new Set([
51
64
  // development CSP from being exposed as a general network service.
52
65
  const DEVELOPMENT_CSP=[
53
66
  "default-src 'self'",
54
- "script-src 'self' 'unsafe-inline'",
67
+ "script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'",
55
68
  "style-src 'self' 'unsafe-inline'",
56
69
  "img-src 'self' data: blob: http: https:",
57
70
  "font-src 'self' data:",
@@ -138,6 +151,173 @@ function inventoryKey(value){
138
151
  return process.platform==='win32'?value.toLowerCase():value;
139
152
  }
140
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
+
141
321
  function identityMap(identities,prefix=''){
142
322
  const normalizedPrefix=prefix?`${prefix}/`:'';
143
323
  const result=new Map();
@@ -345,8 +525,34 @@ function sharedPathAllowed(relative,route){
345
525
  });
346
526
  }
347
527
 
348
- async function sourceRoutes(workspaceRoot,appId,{workspaceRuntimeReceipt,signal}){
528
+ async function sourceRoutes(workspaceRoot,appId,{
529
+ workspaceRuntimeReceipt,
530
+ sdkRuntimeSourceRoot,
531
+ signal,
532
+ onEvent
533
+ }){
349
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
+ }
350
556
  if(resolved.config.workspaceMode==='integrated'){
351
557
  return {
352
558
  workspaceRoot:resolved.workspaceRoot,
@@ -354,11 +560,7 @@ async function sourceRoutes(workspaceRoot,appId,{workspaceRuntimeReceipt,signal}
354
560
  appId:resolved.appId,
355
561
  startPath:`/apps/${resolved.appId}/${resolved.app.manifest.entry}`,
356
562
  mappings:[
357
- {
358
- prefix:['apps',resolved.appId],
359
- root:resolved.appRoot,
360
- allow:relative=>sourcePathAllowed(relative,resolved.app.manifest)
361
- },
563
+ appMapping,
362
564
  ...resolved.config.sharedPayloads['browser-runtime'].map(route=>({
363
565
  prefix:route.destination.split('/'),
364
566
  root:path.join(resolved.workspaceRoot,...route.source.split('/')),
@@ -410,11 +612,7 @@ async function sourceRoutes(workspaceRoot,appId,{workspaceRuntimeReceipt,signal}
410
612
  appId:resolved.appId,
411
613
  startPath:`/apps/${resolved.appId}/${resolved.app.manifest.entry}`,
412
614
  mappings:[
413
- {
414
- prefix:['apps',resolved.appId],
415
- root:resolved.appRoot,
416
- allow:relative=>sourcePathAllowed(relative,resolved.app.manifest)
417
- },
615
+ appMapping,
418
616
  {
419
617
  prefix:['arcane'],
420
618
  root:runtimeRoot,
@@ -505,18 +703,33 @@ async function startOwnedDevServer({
505
703
  port=0,
506
704
  signal,
507
705
  workspaceRuntimeReceipt,
706
+ sdkRuntimeSourceRoot,
508
707
  releaseReceipt
509
708
  }={},events,releaseSignal){
510
709
  throwIfAborted(signal);
511
710
  if(mode!=='source'&&mode!=='packaged')fail(`Unsupported server mode: ${String(mode)}.`,'ARCANE_USAGE');
711
+ if(sdkRuntimeSourceRoot!==undefined&&mode!=='source'){
712
+ fail('sdkRuntimeSourceRoot is supported only in source development mode.','ARCANE_USAGE');
713
+ }
512
714
  if(host!=='127.0.0.1'&&host!=='::1'){
513
715
  fail('Development server host must be a numeric loopback address (127.0.0.1 or ::1).','ARCANE_POLICY_DENIED');
514
716
  }
515
717
  if(!Number.isInteger(port)||port<0||port>65535)fail('port must be an integer from 0 through 65535.','ARCANE_USAGE');
516
- await events.send({type:'server.starting',mode,host,port,appId});
718
+ const requestedRuntimeMode=mode==='source'&&sdkRuntimeSourceRoot!==undefined
719
+ ?'sdk-source'
720
+ :null;
721
+ await events.send({
722
+ type:'server.starting',
723
+ mode,
724
+ host,
725
+ port,
726
+ appId,
727
+ ...(requestedRuntimeMode?{runtimeMode:requestedRuntimeMode}:{})
728
+ });
517
729
  const routeSet=mode==='source'
518
730
  ?await sourceRoutes(workspaceRoot,appId,{
519
731
  workspaceRuntimeReceipt,
732
+ sdkRuntimeSourceRoot,
520
733
  signal,
521
734
  onEvent:event=>events.send(event)
522
735
  })
@@ -646,6 +859,17 @@ async function startOwnedDevServer({
646
859
  while(requestTasks.size>0){
647
860
  await Promise.allSettled([...requestTasks]);
648
861
  }
862
+ if(routeSet.runtime?.mode==='sdk-source'){
863
+ await events.send({
864
+ type:'runtime.source.mount.stopped',
865
+ appId:routeSet.appId,
866
+ reason:events.error||operationalError
867
+ ?'failed'
868
+ :signal?.aborted
869
+ ?'cancelled'
870
+ :'closed'
871
+ });
872
+ }
649
873
  await events.send({
650
874
  type:'server.stopped',
651
875
  host:address.address,
@@ -694,6 +918,10 @@ async function startOwnedDevServer({
694
918
  mode,
695
919
  workspaceRoot:routeSet.workspaceRoot,
696
920
  appId:routeSet.appId,
921
+ ...(routeSet.runtime?{
922
+ runtimeMode:routeSet.runtime.mode,
923
+ runtime:routeSet.runtime
924
+ }:{}),
697
925
  host:address.address,
698
926
  port:address.port,
699
927
  origin,
@@ -710,7 +938,11 @@ async function startOwnedDevServer({
710
938
  host:result.host,
711
939
  port:result.port,
712
940
  url,
713
- appId:result.appId
941
+ appId:result.appId,
942
+ ...(routeSet.runtime?{
943
+ runtimeMode:routeSet.runtime.mode,
944
+ runtime:routeSet.runtime
945
+ }:{})
714
946
  });
715
947
  throwIfAborted(signal);
716
948
  }catch(error){
package/src/doctor.mjs CHANGED
@@ -8,7 +8,7 @@ import {runProcess} from './process.mjs';
8
8
  import {ARCANE_PROTOCOL,SDK_VERSION} from './constants.mjs';
9
9
  import {ERROR_CODES,ArcaneError,throwIfAborted} from './errors.mjs';
10
10
 
11
- const MINIMUM_NODE=[22,14,0];
11
+ const MINIMUM_NODE=[22,23,2];
12
12
  const WINDOWS_SERVICE_NAME='ArcaneOllama';
13
13
  const WINDOWS_SERVICE_HOST='C:\\Program Files\\Ollama\\ArcaneOllamaService.exe';
14
14
  const WINDOWS_SERVICE_COMMAND=`"${WINDOWS_SERVICE_HOST}"`;
@@ -14,15 +14,26 @@ export const MANAGED_IMPORT_MAP_ATTRIBUTE='data-arcane-import-map';
14
14
  const JAVASCRIPT_EXTENSION=/\.(?:js|mjs)$/u;
15
15
  const NODE_ONLY_MODULE='modules/CaseEvidenceIndexer.js';
16
16
  const RUNTIME_STRONG_TYPE_IMPORT='../../node_modules/strong-type/index.js';
17
+ const PERSISTENT_CHAT_IMPORT='#arcane/persistent-ai-chat-session';
18
+ const PERSISTENT_CHAT_MODULE='modules/PersistentAIChatSession.js';
17
19
  const SDK_BROWSER_ENTRY='sdk/event-manager.mjs';
18
20
  const SDK_BROWSER_AI_ENTRY='sdk/ai/browser-wasm.mjs';
21
+ const SDK_BROWSER_SPEECH_ENTRY='sdk/ai/browser-speech.mjs';
22
+ const SDK_BROWSER_SPEECH_WORKER_RUNTIME='sdk/ai/speech-worker-runtime.mjs';
19
23
  const SDK_BROWSER_FILES=Object.freeze([
20
24
  'sdk/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json',
25
+ 'sdk/ai/browser-kokoro-worker.mjs',
26
+ 'sdk/ai/browser-speech-artifacts.mjs',
27
+ 'sdk/ai/browser-speech-providers.mjs',
28
+ SDK_BROWSER_SPEECH_ENTRY,
21
29
  'sdk/ai/browser-wasm-llm-provider.mjs',
22
30
  SDK_BROWSER_AI_ENTRY,
31
+ 'sdk/ai/browser-whisper-worker.mjs',
23
32
  'sdk/ai/browser-wllama-runtime.mjs',
24
33
  'sdk/ai/internal/sha256.mjs',
25
34
  'sdk/ai/model-controller.mjs',
35
+ 'sdk/ai/speech-worker-client.mjs',
36
+ SDK_BROWSER_SPEECH_WORKER_RUNTIME,
26
37
  'sdk/ai/wllama/LICENCE',
27
38
  'sdk/ai/wllama/index.mjs',
28
39
  'sdk/ai/wllama/llama.cpp-LICENSE',
@@ -719,6 +730,14 @@ function nonliteralDynamic(importer,offset){
719
730
  );
720
731
  }
721
732
 
733
+ function isAuthorizedSpeechRuntimeImport(tokens,index,close,importer){
734
+ if(importer!==SDK_BROWSER_SPEECH_WORKER_RUNTIME||close!==index+5)return false;
735
+ const [entry,dot,moduleUrl]=tokens.slice(index+2,close);
736
+ return entry?.type==='identifier'&&entry.value==='entry'
737
+ &&dot?.value==='.'
738
+ &&moduleUrl?.type==='identifier'&&moduleUrl.value==='moduleUrl';
739
+ }
740
+
722
741
  export function scanModuleImports(source,{importer='<module>'}={}){
723
742
  if(typeof source!=='string')throw new TypeError('scanModuleImports source must be a string.');
724
743
  const tokens=tokenize(source);
@@ -745,6 +764,11 @@ export function scanModuleImports(source,{importer='<module>'}={}){
745
764
  ||commas.length>2
746
765
  ||(commas.length===2
747
766
  &&(commas[1]!==close-1||commas[1]===commas[0]+1))){
767
+ if(isAuthorizedSpeechRuntimeImport(tokens,index,close,importer)){
768
+ hasModuleSyntax=true;
769
+ index=close;
770
+ continue;
771
+ }
748
772
  nonliteralDynamic(importer,current.start);
749
773
  }
750
774
  hasModuleSyntax=true;
@@ -811,6 +835,20 @@ function unresolved(importer,specifier,normalizedTarget,reason='is not in the sh
811
835
  }
812
836
 
813
837
  function resolveImport(importer,specifier,files){
838
+ if(specifier===PERSISTENT_CHAT_IMPORT){
839
+ if(!files.has(PERSISTENT_CHAT_MODULE)){
840
+ unresolved(importer,specifier,PERSISTENT_CHAT_MODULE);
841
+ }
842
+ return {target:PERSISTENT_CHAT_MODULE,persistentChat:true};
843
+ }
844
+ if(specifier.startsWith(PERSISTENT_CHAT_IMPORT)){
845
+ unresolved(
846
+ importer,
847
+ specifier,
848
+ PERSISTENT_CHAT_MODULE,
849
+ 'does not match the exact browser import-map key'
850
+ );
851
+ }
814
852
  const reachableSpecifier=stripQueryAndHash(specifier);
815
853
  if(!reachableSpecifier)unresolved(importer,specifier,'<empty>');
816
854
  if(reachableSpecifier.includes('%')){
@@ -966,9 +1004,13 @@ export async function buildImportMap({files,readFile,signal}={}){
966
1004
  }
967
1005
  const hasSdkBrowserGraph=inventory.has(SDK_BROWSER_ENTRY);
968
1006
  const hasSdkAiGraph=inventory.has(SDK_BROWSER_AI_ENTRY);
1007
+ const hasSdkSpeechGraph=inventory.has(SDK_BROWSER_SPEECH_ENTRY);
969
1008
  if(hasSdkAiGraph&&!hasSdkBrowserGraph){
970
1009
  unresolved(SDK_BROWSER_AI_ENTRY,'<authenticated SDK browser closure>',SDK_BROWSER_ENTRY);
971
1010
  }
1011
+ if(hasSdkSpeechGraph&&!hasSdkAiGraph){
1012
+ unresolved(SDK_BROWSER_SPEECH_ENTRY,'<authenticated SDK browser closure>',SDK_BROWSER_AI_ENTRY);
1013
+ }
972
1014
  if(hasSdkBrowserGraph){
973
1015
  for(const required of SDK_BROWSER_FILES){
974
1016
  if(!inventory.has(required)){
@@ -1016,11 +1058,20 @@ export async function buildImportMap({files,readFile,signal}={}){
1016
1058
  'arcane-os/ai/browser-wasm',
1017
1059
  './arcane/sdk/ai/browser-wasm.mjs'
1018
1060
  );
1061
+ registerSpecifier(
1062
+ namedRegistry,
1063
+ 'arcane-os/ai/browser-speech',
1064
+ './arcane/sdk/ai/browser-speech.mjs'
1065
+ );
1019
1066
  }
1020
1067
  }
1021
1068
 
1022
1069
  const sdkRoots=hasSdkBrowserGraph
1023
- ?[SDK_BROWSER_ENTRY,...(hasSdkAiGraph?[SDK_BROWSER_AI_ENTRY]:[])]:[];
1070
+ ?[
1071
+ SDK_BROWSER_ENTRY,
1072
+ ...(hasSdkAiGraph?[SDK_BROWSER_AI_ENTRY]:[]),
1073
+ ...(hasSdkSpeechGraph?[SDK_BROWSER_SPEECH_ENTRY]:[]),
1074
+ ]:[];
1024
1075
  const queue=[...roots,...sdkRoots];
1025
1076
  const reached=new Set();
1026
1077
  const entities=new Set();
@@ -1036,6 +1087,13 @@ export async function buildImportMap({files,readFile,signal}={}){
1036
1087
  const resolution=resolveImport(importer,imported.specifier,inventory);
1037
1088
  if(resolution.runtimeStrongType)usesRuntimeStrongType=true;
1038
1089
  if(resolution.eventPubSub)usesEventPubSub=true;
1090
+ if(resolution.persistentChat){
1091
+ registerSpecifier(
1092
+ namedRegistry,
1093
+ PERSISTENT_CHAT_IMPORT,
1094
+ './arcane/modules/PersistentAIChatSession.js'
1095
+ );
1096
+ }
1039
1097
  if(resolution.target.startsWith('entities/'))entities.add(resolution.target);
1040
1098
  if(!reached.has(resolution.target))queue.push(resolution.target);
1041
1099
  }
@@ -1036,7 +1036,7 @@ function packagedRuntimeAuthorities(receipt){
1036
1036
  ||!Number.isSafeInteger(receipt.fileCount)||receipt.fileCount<1
1037
1037
  ||!Number.isSafeInteger(receipt.totalBytes)||receipt.totalBytes<1
1038
1038
  ||!SHA256_PATTERN.test(receipt.contentSha256??'')
1039
- ||!isPlainObject(arcane)||arcane.authority!=='arcane-os-upstream'
1039
+ ||!isPlainObject(arcane)||arcane.authority!=='arcane-os-sdk'
1040
1040
  ||!SHA256_PATTERN.test(arcane.manifestSha256??'')
1041
1041
  ||!SHA256_PATTERN.test(arcane.contentSha256??'')
1042
1042
  ||!isPlainObject(arcane.source)
@@ -1158,7 +1158,7 @@ async function authenticatePackageRuntimeVerificationState(context,state,{signal
1158
1158
  });
1159
1159
  const workspaceReceipt=snapshot.workspaceRuntimeReceipt;
1160
1160
  const expectedArcaneSource={
1161
- authority:'arcane-os-upstream',
1161
+ authority:'arcane-os-sdk',
1162
1162
  location:snapshot.runtimeReceipt.canonicalLocation,
1163
1163
  manifestSha256:snapshot.runtimeReceipt.manifestSha256,
1164
1164
  contentSha256:snapshot.runtimeReceipt.contentSha256,
package/src/runtime.mjs CHANGED
@@ -7,6 +7,8 @@ import {SDK_VERSION} from './constants.mjs';
7
7
 
8
8
  export const RUNTIME_MANIFEST_NAME='ARCANE_RUNTIME_RELEASE.json';
9
9
  const SHA256_PATTERN=/^[a-f0-9]{64}$/;
10
+ const SDK_RUNTIME_REPOSITORY='https://github.com/TheWizardNexus/arcane-os-sdk.git';
11
+ const LEGACY_ARCANE_REPOSITORY='https://github.com/TheWizardNexus/ARCANE-OS.git';
10
12
  const READ_ONLY_NO_FOLLOW=FS_CONSTANTS.O_RDONLY|(FS_CONSTANTS.O_NOFOLLOW??0);
11
13
  const MAX_VERIFIED_RUNTIME_FILE_BYTES=64*1024*1024;
12
14
  const sdkRoot=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
@@ -59,10 +61,18 @@ function validateRelease(value){
59
61
  }
60
62
  if(value.sdkVersion!==SDK_VERSION)fail('Runtime manifest sdkVersion is incompatible with this SDK.');
61
63
  if(!value.source||typeof value.source!=='object'
62
- ||Object.keys(value.source).sort(compareText).join('\0')!=='bundleVersion\0commit\0protocol\0repository'
63
- ||value.source.repository!=='https://github.com/TheWizardNexus/ARCANE-OS.git'
64
- ||typeof value.source.commit!=='string'||!/^[a-f0-9]{40}$/.test(value.source.commit)
65
- ||value.source.bundleVersion!=='0.8.12'||value.source.protocol!=='arcane/1'){
64
+ ||Object.keys(value.source).sort(compareText).join('\0')!=='authority\0legacyProjection\0path\0protocol\0repository'
65
+ ||value.source.authority!=='sdk-canonical'
66
+ ||value.source.repository!==SDK_RUNTIME_REPOSITORY
67
+ ||value.source.path!=='runtime/arcane'
68
+ ||value.source.protocol!=='arcane/1'
69
+ ||!value.source.legacyProjection
70
+ ||typeof value.source.legacyProjection!=='object'
71
+ ||Object.keys(value.source.legacyProjection).sort(compareText).join('\0')!=='bundleVersion\0commit\0repository'
72
+ ||value.source.legacyProjection.repository!==LEGACY_ARCANE_REPOSITORY
73
+ ||typeof value.source.legacyProjection.commit!=='string'
74
+ ||!/^[a-f0-9]{40}$/.test(value.source.legacyProjection.commit)
75
+ ||value.source.legacyProjection.bundleVersion!=='0.8.12'){
66
76
  fail('Runtime manifest source identity is invalid.');
67
77
  }
68
78
  if(!Array.isArray(value.files)||!Number.isSafeInteger(value.fileCount)
@@ -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
- '33396b3d35322b784929270e7ca0a2a8b31d899c6e77bcb227edc95b37d0ae7d';
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'],