arcane-os 0.3.1 → 0.3.2

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 (153) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +86 -117
  3. package/bin/arcane-test.mjs +170 -46
  4. package/browser-runtime/ai/browser-speech-artifacts.mjs +855 -895
  5. package/browser-runtime/ai/browser-speech-providers.mjs +80 -204
  6. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
  7. package/browser-runtime/ai/browser-wasm.mjs +24 -35
  8. package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
  9. package/browser-runtime/ai/model-controller.mjs +584 -181
  10. package/browser-runtime/ai/speech-worker-client.mjs +8 -148
  11. package/browser-runtime/ai/speech-worker-runtime.mjs +642 -374
  12. package/browser-runtime/dom-event-instrumentation.mjs +55 -147
  13. package/browser-runtime/event-manager.mjs +239 -624
  14. package/package.json +5 -6
  15. package/runtime/arcane/components/app-bar.html +3 -15
  16. package/runtime/arcane/components/assistant-panel.html +10 -10
  17. package/runtime/arcane/components/calculator.html +1 -1
  18. package/runtime/arcane/components/chat.html +1359 -135
  19. package/runtime/arcane/components/conversation-view.html +2 -2
  20. package/runtime/arcane/components/document-inspector.html +11 -17
  21. package/runtime/arcane/components/file-manager.html +13 -56
  22. package/runtime/arcane/components/markdown-document.html +82 -281
  23. package/runtime/arcane/components/markdown-editor.html +7 -10
  24. package/runtime/arcane/components/media-embed.html +6 -6
  25. package/runtime/arcane/components/screen-capture.html +4 -4
  26. package/runtime/arcane/components/source-explanation.html +2 -2
  27. package/runtime/arcane/components/speech.html +112 -68
  28. package/runtime/arcane/components/terminal-workspace.html +4 -4
  29. package/runtime/arcane/components/theme-editor.html +1 -1
  30. package/runtime/arcane/components/unified-inbox.html +2 -2
  31. package/runtime/arcane/components/voice-transcription.html +31 -21
  32. package/runtime/arcane/entities/Calculation.js +2 -3
  33. package/runtime/arcane/entities/Chat.js +228 -43
  34. package/runtime/arcane/entities/Preference.js +3 -5
  35. package/runtime/arcane/entities/Weather.js +5 -5
  36. package/runtime/arcane/modules/AI.js +1042 -427
  37. package/runtime/arcane/modules/AIProviderRuntime.js +618 -359
  38. package/runtime/arcane/modules/AIResponseLength.js +9 -19
  39. package/runtime/arcane/modules/AIRuntimeState.js +109 -72
  40. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
  41. package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
  42. package/runtime/arcane/modules/CalculatorEngine.js +9 -9
  43. package/runtime/arcane/modules/CommunicationAppController.js +3 -7
  44. package/runtime/arcane/modules/ComponentContracts.js +30 -32
  45. package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
  46. package/runtime/arcane/modules/ConversationActionItems.js +26 -59
  47. package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
  48. package/runtime/arcane/modules/ConversationTimebox.js +27 -15
  49. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
  50. package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
  51. package/runtime/arcane/modules/HTMLImport.js +54 -1
  52. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
  53. package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
  54. package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
  55. package/runtime/arcane/modules/MD.js +1 -45
  56. package/runtime/arcane/modules/Mail.js +51 -103
  57. package/runtime/arcane/modules/MailOutbox.mjs +95 -193
  58. package/runtime/arcane/modules/MailTransport.mjs +36 -57
  59. package/runtime/arcane/modules/ModelDefinition.js +22 -106
  60. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
  61. package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
  62. package/runtime/arcane/modules/PreferenceStore.js +102 -30
  63. package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
  64. package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
  65. package/runtime/arcane/modules/ScreenCapture.js +175 -128
  66. package/runtime/arcane/modules/SpeechPlayback.js +46 -149
  67. package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
  68. package/runtime/arcane/modules/ToolCallRouter.js +25 -12
  69. package/runtime/arcane/modules/YouTubeMedia.js +6 -5
  70. package/schemas/arcane-app-bundle.schema.json +13 -78
  71. package/schemas/arcane-app.schema.json +9 -25
  72. package/schemas/arcane-lock.schema.json +18 -151
  73. package/schemas/arcane-package.schema.json +2 -16
  74. package/schemas/native-build-plan.schema.json +119 -122
  75. package/src/app-descriptor.mjs +75 -132
  76. package/src/application-tests.mjs +200 -0
  77. package/src/cli/main.mjs +27 -46
  78. package/src/constants.mjs +3 -4
  79. package/src/dev-server.mjs +30 -324
  80. package/src/doctor.mjs +92 -154
  81. package/src/dom-event-instrumentation.mjs +55 -147
  82. package/src/errors.mjs +2 -3
  83. package/src/event-manager.mjs +239 -624
  84. package/src/event-queue.mjs +3 -3
  85. package/src/import-map.mjs +273 -1028
  86. package/src/index.mjs +14 -16
  87. package/src/installed-sdk-runtime.mjs +27 -67
  88. package/src/integrated-provider-loader.mjs +53 -382
  89. package/src/mail-api.mjs +0 -2
  90. package/src/mail-server.mjs +224 -580
  91. package/src/mail.mjs +4 -10
  92. package/src/native-plan.mjs +163 -598
  93. package/src/native-provider-loader.mjs +104 -1063
  94. package/src/packager/core.mjs +485 -3229
  95. package/src/process.mjs +5 -10
  96. package/src/release-bundle.mjs +292 -2405
  97. package/src/runtime.mjs +76 -396
  98. package/src/scaffold.mjs +30 -80
  99. package/src/sdk-browser-runtime.mjs +70 -626
  100. package/src/source-server.mjs +588 -0
  101. package/src/targets/index.mjs +78 -188
  102. package/src/templates/workspace-template.mjs +19 -135
  103. package/src/testing-loader.mjs +164 -0
  104. package/src/testing.mjs +1 -1
  105. package/src/toolchain.mjs +131 -544
  106. package/src/update-check.mjs +26 -64
  107. package/src/workspace-operation-lock.mjs +139 -430
  108. package/src/workspace-runtime.mjs +109 -1558
  109. package/src/workspace.mjs +40 -302
  110. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
  111. package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
  112. package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
  113. package/browser-runtime/ai/internal/sha256.mjs +0 -166
  114. package/docs/architecture.md +0 -344
  115. package/docs/compatibility.md +0 -36
  116. package/docs/event-manager.md +0 -294
  117. package/docs/platform-targets.md +0 -108
  118. package/docs/publishing.md +0 -201
  119. package/docs/reference/README.md +0 -185
  120. package/docs/reference/ai/browser-speech-package-authority.json +0 -835
  121. package/docs/reference/ai/browser-speech.md +0 -1295
  122. package/docs/reference/ai/browser-wasm.md +0 -530
  123. package/docs/reference/arcane-ollama.md +0 -288
  124. package/docs/reference/availability-and-normalization.md +0 -183
  125. package/docs/reference/behavioral-testing.md +0 -133
  126. package/docs/reference/cli.md +0 -779
  127. package/docs/reference/core/README.md +0 -62
  128. package/docs/reference/core/arcane-ai-contracts.md +0 -907
  129. package/docs/reference/core/arcane-api.md +0 -601
  130. package/docs/reference/core/arcane-entities.md +0 -65
  131. package/docs/reference/core/arcane-events.md +0 -134
  132. package/docs/reference/core/ollama-module.md +0 -181
  133. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
  134. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
  135. package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
  136. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
  137. package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
  138. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
  139. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
  140. package/docs/reference/event-manager.md +0 -1511
  141. package/docs/reference/inventory/package-api.json +0 -3284
  142. package/docs/reference/inventory/runtime-components.json +0 -1011
  143. package/docs/reference/inventory/runtime-entities.json +0 -26
  144. package/docs/reference/inventory/runtime-modules.json +0 -1431
  145. package/docs/reference/mail.md +0 -316
  146. package/docs/reference/protocols.md +0 -719
  147. package/docs/reference/runtime-components.md +0 -1366
  148. package/docs/reference/runtime-entities.md +0 -303
  149. package/docs/reference/runtime-modules.md +0 -2965
  150. package/docs/reference/sdk-api.md +0 -6698
  151. package/docs/roadmap.md +0 -79
  152. package/docs/work-amplification.md +0 -129
  153. package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import {spawn} from 'node:child_process';
3
- import {randomUUID} from 'node:crypto';
4
- import {writeSync} from 'node:fs';
5
3
  import {lstat,readdir,realpath} from 'node:fs/promises';
6
4
  import {register} from 'node:module';
7
5
  import path from 'node:path';
@@ -14,17 +12,97 @@ const TEST_FILE_PATTERN=/\.test\.(?:mjs|cjs|js)$/u;
14
12
  const CANCELLATION_FORCE_WAIT_MS=1500;
15
13
  const WATCHDOG_REPORT_GRACE_MS=500;
16
14
  const FILE_HEARTBEAT_MS=30_000;
17
- const OUTPUT_FLUSH_TIMEOUT_MS=5_000;
18
15
  const BETWEEN_PHASE_TIMEOUT_MS=30_000;
19
- const TREE_DRAIN_DIAGNOSTIC_BYTES=4_096;
20
16
  const IPC_PROTOCOL=1;
21
- const IPC_TOKEN_ENV='ARCANE_TEST_IPC_TOKEN';
17
+ const MANAGED_IMPORT_MAP_ENV='ARCANE_TEST_IMPORT_MAP_CONTEXT';
18
+ const MANAGED_IMPORT_MAP_PROTOCOL='arcane-test-import-map/1';
19
+ const APPLICATION_MAP_BOUNDARIES=new Set(['source','dist','test']);
22
20
  const ISOLATED_MODE=process.argv[2]===SINGLE_FILE_ARGUMENT;
23
- const ISOLATED_TOKEN=ISOLATED_MODE?process.env[IPC_TOKEN_ENV]:null;
21
+ const MANAGED_IMPORT_MAP_SOURCE=process.env[MANAGED_IMPORT_MAP_ENV];
22
+ const ISOLATED_MANAGED_IMPORT_MAP=ISOLATED_MODE
23
+ &&MANAGED_IMPORT_MAP_SOURCE!==''
24
+ ?MANAGED_IMPORT_MAP_SOURCE
25
+ :undefined;
26
+ const COORDINATOR_MANAGED_IMPORT_MAP=!ISOLATED_MODE
27
+ &&MANAGED_IMPORT_MAP_SOURCE!==''
28
+ ?MANAGED_IMPORT_MAP_SOURCE
29
+ :undefined;
24
30
  const ISOLATED_SEND=ISOLATED_MODE&&typeof process.send==='function'
25
31
  ?process.send.bind(process)
26
32
  :null;
27
- if(ISOLATED_MODE)delete process.env[IPC_TOKEN_ENV];
33
+ delete process.env[MANAGED_IMPORT_MAP_ENV];
34
+
35
+ function importMapFailure(message,code='ARCANE_IMPORT_MAP_INVALID'){
36
+ const error=new Error(message);
37
+ error.code=code;
38
+ throw error;
39
+ }
40
+
41
+ function samePath(left,right){
42
+ const a=path.resolve(left);
43
+ const b=path.resolve(right);
44
+ return process.platform==='win32'?a.toLowerCase()===b.toLowerCase():a===b;
45
+ }
46
+
47
+ function pathInside(root,candidate){
48
+ const relative=path.relative(root,candidate);
49
+ return relative===''||(!relative.startsWith(`..${path.sep}`)&&relative!=='..'&&!path.isAbsolute(relative));
50
+ }
51
+
52
+ async function readManagedImportMapContext(source,signal,testFile){
53
+ if(source===undefined)return null;
54
+ throwIfCancelled(signal);
55
+ let context;
56
+ try{context=JSON.parse(source);}
57
+ catch(error){importMapFailure(`Managed import-map context is not valid JSON: ${error.message}`);}
58
+ if(context===null||typeof context!=='object'||Array.isArray(context)
59
+ ||context.protocol!==MANAGED_IMPORT_MAP_PROTOCOL
60
+ ||!APPLICATION_MAP_BOUNDARIES.has(context.boundary)
61
+ ||typeof context.baseURL!=='string'
62
+ ||context.imports===null||typeof context.imports!=='object'
63
+ ||Array.isArray(context.imports)){
64
+ importMapFailure('Managed import-map context is malformed.');
65
+ }
66
+ let suppliedBase;
67
+ try{
68
+ const parsed=new URL(context.baseURL);
69
+ if(parsed.protocol!=='file:'||parsed.username||parsed.password||parsed.search||parsed.hash){
70
+ importMapFailure('Managed import-map base URL must be a physical application directory.');
71
+ }
72
+ suppliedBase=path.resolve(fileURLToPath(parsed));
73
+ }catch(error){
74
+ if(error?.code)throw error;
75
+ importMapFailure(`Managed import-map base URL is invalid: ${error.message}`);
76
+ }
77
+ const testCodeRoot=await physicalTestCodeRoot(testFile);
78
+ const applicationRoot=path.dirname(testCodeRoot);
79
+ const expectedBase=context.boundary==='source'
80
+ ?applicationRoot
81
+ :context.boundary==='test'
82
+ ?testCodeRoot
83
+ :path.join(applicationRoot,'dist');
84
+ let baseInfo;
85
+ let canonicalBase;
86
+ try{
87
+ baseInfo=await lstat(expectedBase);
88
+ canonicalBase=await realpath(expectedBase);
89
+ }catch(error){
90
+ importMapFailure(`Selected ${context.boundary} application directory is unavailable: ${error.message}`);
91
+ }
92
+ if(baseInfo.isSymbolicLink()||!baseInfo.isDirectory()
93
+ ||!samePath(expectedBase,canonicalBase)||!samePath(suppliedBase,canonicalBase)){
94
+ importMapFailure(
95
+ `Managed import-map base URL must select this application's physical ${context.boundary} directory.`
96
+ );
97
+ }
98
+ throwIfCancelled(signal);
99
+ return {
100
+ protocol:MANAGED_IMPORT_MAP_PROTOCOL,
101
+ boundary:context.boundary,
102
+ baseURL:pathToFileURL(`${canonicalBase}${path.sep}`).href,
103
+ imports:{...context.imports}
104
+ };
105
+ }
28
106
 
29
107
  function comparePaths(left,right){
30
108
  return left.localeCompare(right,'en');
@@ -36,8 +114,37 @@ function throwIfCancelled(signal){
36
114
  }
37
115
  }
38
116
 
39
- async function collectDirectory(root,files,signal){
117
+ function enclosingTestCodeRoot(candidate){
118
+ let current=path.resolve(candidate);
119
+ while(true){
120
+ if(path.basename(current).toLowerCase()==='test')return current;
121
+ const parent=path.dirname(current);
122
+ if(samePath(parent,current))return null;
123
+ current=parent;
124
+ }
125
+ }
126
+
127
+ async function physicalTestCodeRoot(candidate){
128
+ const selected=enclosingTestCodeRoot(candidate);
129
+ if(selected===null){
130
+ throw new Error(`Selected test path is not inside an application test-code directory: ${candidate}`);
131
+ }
132
+ const info=await lstat(selected);
133
+ const canonical=await realpath(selected);
134
+ if(info.isSymbolicLink()||!info.isDirectory()||!samePath(selected,canonical)){
135
+ throw new Error(`Application test-code directory must be one physical directory: ${selected}`);
136
+ }
137
+ return canonical;
138
+ }
139
+
140
+ async function collectDirectory(root,testCodeRoot,files,signal){
40
141
  throwIfCancelled(signal);
142
+ const rootInfo=await lstat(root);
143
+ const canonicalRoot=await realpath(root);
144
+ if(rootInfo.isSymbolicLink()||!rootInfo.isDirectory()||!samePath(root,canonicalRoot)
145
+ ||!pathInside(testCodeRoot,canonicalRoot)){
146
+ throw new Error(`Test discovery must stay inside its physical test-code directory: ${root}`);
147
+ }
41
148
  const entries=await readdir(root,{withFileTypes:true});
42
149
  throwIfCancelled(signal);
43
150
  for(const entry of entries.sort((left,right)=>comparePaths(left.name,right.name))){
@@ -47,9 +154,13 @@ async function collectDirectory(root,files,signal){
47
154
  throw new Error(`Test discovery refuses symbolic links: ${candidate}`);
48
155
  }
49
156
  if(entry.isDirectory()){
50
- await collectDirectory(candidate,files,signal);
157
+ await collectDirectory(candidate,testCodeRoot,files,signal);
51
158
  }else if(entry.isFile()&&TEST_FILE_PATTERN.test(entry.name)){
52
- files.add(await realpath(candidate));
159
+ const canonical=await realpath(candidate);
160
+ if(!samePath(candidate,canonical)||!pathInside(testCodeRoot,canonical)){
161
+ throw new Error(`Selected test file leaves its physical test-code directory: ${candidate}`);
162
+ }
163
+ files.add(canonical);
53
164
  }
54
165
  }
55
166
  }
@@ -72,10 +183,18 @@ async function collectTestFiles(arguments_,signal){
72
183
  if(info.isSymbolicLink()){
73
184
  throw new Error(`Test paths must not be symbolic links: ${candidate}`);
74
185
  }
186
+ const testCodeRoot=await physicalTestCodeRoot(candidate);
187
+ if(!pathInside(testCodeRoot,candidate)){
188
+ throw new Error(`Selected test path leaves its application test-code directory: ${candidate}`);
189
+ }
75
190
  if(info.isDirectory()){
76
- await collectDirectory(candidate,files,signal);
191
+ await collectDirectory(candidate,testCodeRoot,files,signal);
77
192
  }else if(info.isFile()&&TEST_FILE_PATTERN.test(path.basename(candidate))){
78
- files.add(await realpath(candidate));
193
+ const canonical=await realpath(candidate);
194
+ if(!samePath(candidate,canonical)||!pathInside(testCodeRoot,canonical)){
195
+ throw new Error(`Selected test file leaves its physical test-code directory: ${candidate}`);
196
+ }
197
+ files.add(canonical);
79
198
  }else{
80
199
  throw new Error(`Expected a .test.js, .test.cjs, or .test.mjs file: ${candidate}`);
81
200
  }
@@ -88,14 +207,14 @@ function formatError(error){
88
207
  }
89
208
 
90
209
  function sendIsolatedMessage(message){
91
- if(typeof ISOLATED_TOKEN!=='string'||ISOLATED_TOKEN===''||ISOLATED_SEND===null){
210
+ if(ISOLATED_SEND===null){
92
211
  return Promise.reject(
93
- new Error('Internal single-file mode requires its authenticated coordinator channel.')
212
+ new Error('Internal single-file mode requires its coordinator channel.')
94
213
  );
95
214
  }
96
215
  return new Promise((resolve,reject)=>{
97
216
  ISOLATED_SEND(
98
- {...message,protocol:IPC_PROTOCOL,token:ISOLATED_TOKEN},
217
+ {...message,protocol:IPC_PROTOCOL},
99
218
  error=>error?reject(error):resolve()
100
219
  );
101
220
  });
@@ -209,12 +328,9 @@ function forceDrainWindowsTree(child){
209
328
  let stdoutDiagnostic='';
210
329
  let stderrDiagnostic='';
211
330
  const deadline=performance.now()+CANCELLATION_FORCE_WAIT_MS;
212
- const capture=(stream,chunk)=>{
213
- const value=`${stream}${chunk.toString()}`;
214
- return value.slice(-TREE_DRAIN_DIAGNOSTIC_BYTES);
215
- };
331
+ const capture=(stream,chunk)=>`${stream}${chunk.toString()}`;
216
332
  const diagnostic=()=>[stdoutDiagnostic,stderrDiagnostic]
217
- .map(value=>value.trim()).filter(Boolean).join('\n');
333
+ .filter(value=>value!=='').join('\n');
218
334
  const reportFailure=reason=>{
219
335
  const detail=diagnostic();
220
336
  console.error(
@@ -321,7 +437,6 @@ function forceDrainProcessTree(child){
321
437
  function runIsolatedFile(file,signal){
322
438
  if(signal.aborted)return Promise.reject(signal.reason);
323
439
  return new Promise((resolve,reject)=>{
324
- const token=randomUUID();
325
440
  let settled=false;
326
441
  let escalation=null;
327
442
  let forceSettlement=null;
@@ -353,7 +468,7 @@ function runIsolatedFile(file,signal){
353
468
  if(!child?.connected)return;
354
469
  try{
355
470
  child.send(
356
- {protocol:IPC_PROTOCOL,token,type:'abort',reason},
471
+ {protocol:IPC_PROTOCOL,type:'abort',reason},
357
472
  ()=>{}
358
473
  );
359
474
  }catch{}
@@ -390,7 +505,7 @@ function runIsolatedFile(file,signal){
390
505
  return;
391
506
  }
392
507
  console.error(
393
- `[arcane-test] ${displayFile(file)} closed without an authenticated harness report.`
508
+ `[arcane-test] ${displayFile(file)} closed without a harness report.`
394
509
  );
395
510
  finish(resolve,{
396
511
  code:2,
@@ -465,7 +580,6 @@ function runIsolatedFile(file,signal){
465
580
  const onMessage=message=>{
466
581
  if(
467
582
  message?.protocol!==IPC_PROTOCOL
468
- ||message?.token!==token
469
583
  ||typeof message.type!=='string'
470
584
  )return;
471
585
  if(message.type==='phase'){
@@ -473,24 +587,28 @@ function runIsolatedFile(file,signal){
473
587
  completion!==null
474
588
  ||watchdogFailure!==null
475
589
  ||typeof message.id!=='string'
476
- ||message.id===''||message.id.length>200
590
+ ||message.id===''
477
591
  )return;
478
592
  if(message.status==='started'){
479
593
  if(
480
594
  typeof message.kind!=='string'
481
595
  ||typeof message.name!=='string'
482
- ||!Number.isSafeInteger(message.timeoutMs)
483
- ||message.timeoutMs<1
484
- ||message.timeoutMs>3_600_000
596
+ ||(message.timeoutMs!==null&&(
597
+ !Number.isSafeInteger(message.timeoutMs)
598
+ ||message.timeoutMs<1
599
+ ||message.timeoutMs>3_600_000
600
+ ))
485
601
  ||phaseTimers.has(message.id)
486
602
  )return;
487
603
  clearTimeout(idleTimer);
488
- const timer=setTimeout(()=>watchdog(message),message.timeoutMs);
604
+ const timer=message.timeoutMs===null
605
+ ?null
606
+ :setTimeout(()=>watchdog(message),message.timeoutMs);
489
607
  phaseTimers.set(message.id,timer);
490
608
  }else if(message.status==='completed'){
491
- const timer=phaseTimers.get(message.id);
492
- if(timer!==undefined){
493
- clearTimeout(timer);
609
+ if(phaseTimers.has(message.id)){
610
+ const timer=phaseTimers.get(message.id);
611
+ if(timer!==null)clearTimeout(timer);
494
612
  phaseTimers.delete(message.id);
495
613
  scheduleIdle();
496
614
  }
@@ -513,7 +631,10 @@ function runIsolatedFile(file,signal){
513
631
  };
514
632
  try{
515
633
  child=spawn(process.execPath,[RUNNER_PATH,SINGLE_FILE_ARGUMENT,file],{
516
- env:{...process.env,[IPC_TOKEN_ENV]:token},
634
+ env:{
635
+ ...process.env,
636
+ [MANAGED_IMPORT_MAP_ENV]:COORDINATOR_MANAGED_IMPORT_MAP??''
637
+ },
517
638
  shell:false,
518
639
  windowsHide:true,
519
640
  detached:process.platform!=='win32',
@@ -537,7 +658,17 @@ function runIsolatedFile(file,signal){
537
658
 
538
659
  async function runSingleFile(file,signal){
539
660
  console.log(`[arcane-test] IMPORT ${displayFile(file)}`);
540
- register(new URL('../src/testing-loader.mjs',import.meta.url),import.meta.url);
661
+ const managedImportMap=await readManagedImportMapContext(
662
+ ISOLATED_MANAGED_IMPORT_MAP,
663
+ signal,
664
+ file
665
+ );
666
+ throwIfCancelled(signal);
667
+ register(new URL('../src/testing-loader.mjs',import.meta.url),{
668
+ parentURL:import.meta.url,
669
+ data:{managedImportMap}
670
+ });
671
+ throwIfCancelled(signal);
541
672
  const importPhase={
542
673
  id:'module-import',
543
674
  kind:'import',
@@ -551,6 +682,7 @@ async function runSingleFile(file,signal){
551
682
  let importError=null;
552
683
  try{
553
684
  try{
685
+ throwIfCancelled(signal);
554
686
  await import(pathToFileURL(file).href);
555
687
  }catch(error){
556
688
  importError=error;
@@ -617,12 +749,10 @@ function flushStream(stream){
617
749
  const finish=flushed=>{
618
750
  if(settled)return;
619
751
  settled=true;
620
- clearTimeout(timeout);
621
752
  stream.removeListener('error',failed);
622
753
  resolve(flushed);
623
754
  };
624
755
  const failed=()=>finish(false);
625
- const timeout=setTimeout(failed,OUTPUT_FLUSH_TIMEOUT_MS);
626
756
  stream.once('error',failed);
627
757
  try{
628
758
  stream.write('',()=>finish(true));
@@ -635,12 +765,7 @@ function flushStream(stream){
635
765
  async function flushOutput(){
636
766
  const flushed=await Promise.all([flushStream(process.stdout),flushStream(process.stderr)]);
637
767
  if(flushed.every(Boolean))return;
638
- try{
639
- writeSync(
640
- 2,
641
- '[arcane-test] Output flush exceeded its bounded drain; diagnostics may be truncated.\n'
642
- );
643
- }catch{}
768
+ throw new Error('One or more test output streams failed before reporting all diagnostics.');
644
769
  }
645
770
 
646
771
  async function main(){
@@ -656,7 +781,6 @@ async function main(){
656
781
  const parentAbort=message=>{
657
782
  if(
658
783
  message?.protocol!==IPC_PROTOCOL
659
- ||message?.token!==ISOLATED_TOKEN
660
784
  ||message?.type!=='abort'
661
785
  )return;
662
786
  const error=new Error(
@@ -673,9 +797,9 @@ async function main(){
673
797
  if(process.argv.length!==4){
674
798
  throw new Error(`${SINGLE_FILE_ARGUMENT} requires one exact test file.`);
675
799
  }
676
- if(typeof ISOLATED_TOKEN!=='string'||ISOLATED_TOKEN===''||ISOLATED_SEND===null){
800
+ if(ISOLATED_SEND===null){
677
801
  throw new Error(
678
- 'Internal single-file mode requires its authenticated coordinator channel.'
802
+ 'Internal single-file mode requires its coordinator channel.'
679
803
  );
680
804
  }
681
805
  interceptIsolatedExit();
@@ -718,7 +842,7 @@ if(ISOLATED_MODE){
718
842
  kind:'flush',
719
843
  name:'test report output flush',
720
844
  status:'started',
721
- timeoutMs:OUTPUT_FLUSH_TIMEOUT_MS,
845
+ timeoutMs:null,
722
846
  type:'phase'
723
847
  };
724
848
  await sendIsolatedMessage(flushPhase);