arcane-os 0.5.16 → 0.5.18

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 (190) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +42 -19
  3. package/bin/arcane-test.mjs +16 -13
  4. package/browser-runtime/ai/browser-speech-artifacts.mjs +41 -38
  5. package/browser-runtime/ai/browser-speech-providers.mjs +53 -46
  6. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +127 -124
  7. package/browser-runtime/ai/browser-wasm.mjs +6 -3
  8. package/browser-runtime/ai/browser-wllama-runtime.mjs +26 -23
  9. package/browser-runtime/ai/model-controller.mjs +94 -91
  10. package/browser-runtime/ai/speech-worker-client.mjs +12 -9
  11. package/browser-runtime/ai/speech-worker-runtime.mjs +56 -53
  12. package/browser-runtime/dependencies/strong-type/index.js +207 -82
  13. package/browser-runtime/dependencies/strong-type/package.json +15 -6
  14. package/browser-runtime/dom-event-instrumentation.mjs +24 -20
  15. package/browser-runtime/event-manager.mjs +111 -108
  16. package/browser-runtime/speech-text.mjs +35 -0
  17. package/docs/architecture.md +1 -1
  18. package/docs/reference/README.md +8 -8
  19. package/docs/reference/ai/browser-speech.md +230 -16
  20. package/docs/reference/ai/twin-cloud.md +1 -1
  21. package/docs/reference/availability-and-normalization.md +42 -1
  22. package/docs/reference/cli.md +1 -1
  23. package/docs/reference/core/arcane-ai-contracts.md +1 -1
  24. package/docs/reference/inventory/package-api.json +60 -4
  25. package/docs/reference/inventory/runtime-components.json +2 -2
  26. package/docs/reference/inventory/runtime-modules.json +29 -31
  27. package/docs/reference/protocols.md +17 -13
  28. package/docs/reference/runtime-components.md +6 -5
  29. package/docs/reference/runtime-modules.md +212 -70
  30. package/docs/reference/sdk-api.md +197 -11
  31. package/examples/wasm-ai-demo/index.html +1 -0
  32. package/node_modules/event-pubsub/node_modules/strong-type/README.md +408 -0
  33. package/node_modules/event-pubsub/node_modules/strong-type/assets/strong-type-header.png +0 -0
  34. package/node_modules/event-pubsub/node_modules/strong-type/index.js +1151 -0
  35. package/node_modules/event-pubsub/node_modules/strong-type/licence +21 -0
  36. package/node_modules/event-pubsub/node_modules/strong-type/node.js +125 -0
  37. package/node_modules/event-pubsub/node_modules/strong-type/package.json +61 -0
  38. package/node_modules/strong-type/README.md +42 -24
  39. package/node_modules/strong-type/benchmark/core.js +324 -0
  40. package/node_modules/strong-type/index.js +207 -82
  41. package/node_modules/strong-type/package.json +14 -6
  42. package/package.json +4 -3
  43. package/runtime/arcane/components/app-bar.html +5 -2
  44. package/runtime/arcane/components/assistant-panel.html +5 -2
  45. package/runtime/arcane/components/chart.html +15 -12
  46. package/runtime/arcane/components/chat.html +118 -141
  47. package/runtime/arcane/components/dashboard-config.html +6 -3
  48. package/runtime/arcane/components/data-view.html +4 -1
  49. package/runtime/arcane/components/directory-picker.html +5 -2
  50. package/runtime/arcane/components/document-inspector.html +6 -3
  51. package/runtime/arcane/components/file-drop.html +5 -2
  52. package/runtime/arcane/components/file-inspector.html +9 -6
  53. package/runtime/arcane/components/file-manager.html +16 -13
  54. package/runtime/arcane/components/local-ai-status.html +5 -2
  55. package/runtime/arcane/components/markdown-document.html +14 -11
  56. package/runtime/arcane/components/markdown-editor.html +4 -1
  57. package/runtime/arcane/components/modal.html +4 -1
  58. package/runtime/arcane/components/output-panel.html +6 -3
  59. package/runtime/arcane/components/preferences-form.html +4 -1
  60. package/runtime/arcane/components/record-timeline.html +8 -5
  61. package/runtime/arcane/components/relationship-board.html +9 -6
  62. package/runtime/arcane/components/source-code-viewer.html +12 -9
  63. package/runtime/arcane/components/source-explanation.html +8 -5
  64. package/runtime/arcane/components/speech.html +24 -21
  65. package/runtime/arcane/components/summary-strip.html +4 -1
  66. package/runtime/arcane/components/task-progress.html +6 -3
  67. package/runtime/arcane/components/terminal-workspace.html +4 -1
  68. package/runtime/arcane/components/voice-transcription.html +11 -8
  69. package/runtime/arcane/components/web-navigator.html +5 -2
  70. package/runtime/arcane/entities/ApiModelRecord.js +5 -2
  71. package/runtime/arcane/entities/Calculation.js +5 -2
  72. package/runtime/arcane/entities/Chat.js +33 -33
  73. package/runtime/arcane/entities/CommunicationMessage.js +4 -1
  74. package/runtime/arcane/entities/CommunicationThread.js +4 -1
  75. package/runtime/arcane/entities/File.js +1 -1
  76. package/runtime/arcane/entities/Image.js +8 -5
  77. package/runtime/arcane/entities/IntentEnvelope.js +20 -17
  78. package/runtime/arcane/entities/Preference.js +9 -6
  79. package/runtime/arcane/entities/Theme.js +5 -2
  80. package/runtime/arcane/entities/User.js +11 -11
  81. package/runtime/arcane/entities/Weather.js +5 -2
  82. package/runtime/arcane/modules/AI.js +503 -187
  83. package/runtime/arcane/modules/AIPreferenceRuntime.js +5 -2
  84. package/runtime/arcane/modules/AIPreferenceTuple.js +9 -6
  85. package/runtime/arcane/modules/AIProviderRuntime.js +117 -92
  86. package/runtime/arcane/modules/AIResponseURLPolicy.js +148 -62
  87. package/runtime/arcane/modules/AIRuntimeState.js +29 -26
  88. package/runtime/arcane/modules/AnsiText.js +4 -1
  89. package/runtime/arcane/modules/ApiModelDatabase.js +22 -19
  90. package/runtime/arcane/modules/AppDataScope.js +8 -5
  91. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +4 -1
  92. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +8 -5
  93. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +18 -15
  94. package/runtime/arcane/modules/AsyncBoundary.js +13 -10
  95. package/runtime/arcane/modules/BrowserTestSuite.js +19 -16
  96. package/runtime/arcane/modules/CalculatorEngine.js +5 -2
  97. package/runtime/arcane/modules/ChatRecords.js +18 -15
  98. package/runtime/arcane/modules/CommunicationAppController.js +9 -6
  99. package/runtime/arcane/modules/CommunicationHub.js +9 -6
  100. package/runtime/arcane/modules/CommunicationPreferences.js +4 -1
  101. package/runtime/arcane/modules/CommunicationProviderRegistry.js +4 -1
  102. package/runtime/arcane/modules/ComponentContracts.js +56 -53
  103. package/runtime/arcane/modules/ConfiguredAIChatSession.js +30 -27
  104. package/runtime/arcane/modules/ConversationActionItems.js +15 -12
  105. package/runtime/arcane/modules/ConversationClosingReport.js +11 -8
  106. package/runtime/arcane/modules/ConversationTimebox.js +28 -25
  107. package/runtime/arcane/modules/CoreLocalModelCatalog.js +17 -14
  108. package/runtime/arcane/modules/DBLS.js +7 -4
  109. package/runtime/arcane/modules/DBOPFS.js +7 -7
  110. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +34 -31
  111. package/runtime/arcane/modules/DevelopmentWorkspace.js +4 -1
  112. package/runtime/arcane/modules/DirectoryPicker.js +8 -5
  113. package/runtime/arcane/modules/DocumentLexicalSearch.js +11 -8
  114. package/runtime/arcane/modules/DocumentNavigation.js +7 -4
  115. package/runtime/arcane/modules/Errors.js +28 -25
  116. package/runtime/arcane/modules/HTMLImport.js +7 -4
  117. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +15 -12
  118. package/runtime/arcane/modules/LocalAIReadiness.js +21 -18
  119. package/runtime/arcane/modules/LocalAIReadinessController.js +6 -3
  120. package/runtime/arcane/modules/MD.js +1 -1
  121. package/runtime/arcane/modules/Mail.js +46 -43
  122. package/runtime/arcane/modules/MailOutbox.mjs +48 -45
  123. package/runtime/arcane/modules/MailTransport.mjs +29 -26
  124. package/runtime/arcane/modules/MarkdownSpeech.js +1 -50
  125. package/runtime/arcane/modules/MemoryRecords.js +7 -4
  126. package/runtime/arcane/modules/MessageAdvisory.js +6 -3
  127. package/runtime/arcane/modules/ModelDefinition.js +7 -4
  128. package/runtime/arcane/modules/Ollama.js +6 -3
  129. package/runtime/arcane/modules/OllamaModelIdentifier.js +4 -1
  130. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +18 -15
  131. package/runtime/arcane/modules/PersistentAIChatSession.js +33 -30
  132. package/runtime/arcane/modules/PreferenceStore.js +20 -17
  133. package/runtime/arcane/modules/PreparedSpeech.js +485 -0
  134. package/runtime/arcane/modules/Questionnaire.js +6 -3
  135. package/runtime/arcane/modules/RecordLinkIndex.js +4 -1
  136. package/runtime/arcane/modules/RecordPassageIndex.js +9 -6
  137. package/runtime/arcane/modules/RecordReviewStore.js +12 -9
  138. package/runtime/arcane/modules/RiskSignalAnalyzer.js +4 -1
  139. package/runtime/arcane/modules/ScopedOPFSCache.js +6 -3
  140. package/runtime/arcane/modules/ScreenCapture.js +20 -17
  141. package/runtime/arcane/modules/SpeechPlayback.js +61 -28
  142. package/runtime/arcane/modules/StaticDocumentCatalog.js +33 -30
  143. package/runtime/arcane/modules/SystemAppearance.js +5 -2
  144. package/runtime/arcane/modules/SystemToolRegistry.js +6 -3
  145. package/runtime/arcane/modules/TerminalClient.js +14 -11
  146. package/runtime/arcane/modules/TerminalCommandRegistry.js +4 -1
  147. package/runtime/arcane/modules/ThemeBootstrap.js +9 -6
  148. package/runtime/arcane/modules/ThemeManager.js +5 -2
  149. package/runtime/arcane/modules/TimeGuard.js +1 -1
  150. package/runtime/arcane/modules/ToolCallRouter.js +11 -8
  151. package/runtime/arcane/modules/WaitForComponent.js +19 -16
  152. package/runtime/arcane/modules/YouTubeMedia.js +4 -1
  153. package/runtime/strong-type/index.js +1276 -352
  154. package/runtime/strong-type/package.json +69 -45
  155. package/src/app-descriptor.mjs +17 -14
  156. package/src/application-tests.mjs +4 -1
  157. package/src/cli/main.mjs +8 -5
  158. package/src/constants.mjs +4 -1
  159. package/src/dev-server.mjs +8 -5
  160. package/src/doctor.mjs +5 -2
  161. package/src/dom-event-instrumentation.mjs +24 -20
  162. package/src/errors.mjs +7 -3
  163. package/src/event-manager.mjs +111 -108
  164. package/src/event-queue.mjs +8 -5
  165. package/src/events.mjs +12 -9
  166. package/src/import-map.mjs +36 -27
  167. package/src/installed-sdk-runtime.mjs +4 -1
  168. package/src/integrated-provider-loader.mjs +9 -6
  169. package/src/mail-credentials.mjs +16 -13
  170. package/src/mail-server.mjs +53 -50
  171. package/src/mail.mjs +18 -15
  172. package/src/native-plan.mjs +12 -9
  173. package/src/native-provider-loader.mjs +7 -4
  174. package/src/packager/core.mjs +18 -15
  175. package/src/process.mjs +6 -3
  176. package/src/release-bundle.mjs +14 -11
  177. package/src/runtime.mjs +4 -1
  178. package/src/scaffold.mjs +9 -6
  179. package/src/sdk-browser-runtime.mjs +4 -1
  180. package/src/source-server.mjs +17 -14
  181. package/src/targets/index.mjs +5 -2
  182. package/src/templates/workspace-template.mjs +13 -6
  183. package/src/testing-loader.mjs +7 -4
  184. package/src/testing.mjs +10 -7
  185. package/src/toolchain.mjs +13 -10
  186. package/src/update-check.mjs +9 -6
  187. package/src/workspace-operation-lock.mjs +14 -11
  188. package/src/workspace-runtime.mjs +4 -1
  189. package/src/workspace.mjs +17 -14
  190. package/runtime/arcane/modules/AIResponseLength.js +0 -32
@@ -177,6 +177,9 @@
177
177
  </section>
178
178
 
179
179
  <script type="module">
180
+ const {default:Is}=await import('strong-type');
181
+ const is=new Is(false);
182
+
180
183
  const host=this;
181
184
  host.ready=false;
182
185
  const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
@@ -244,7 +247,7 @@
244
247
 
245
248
  function normalizedText(value,label,{optional=false}={}){
246
249
  if(optional&&(value===undefined||value===null||value===''))return '';
247
- if(typeof value!=='string'||!value.trim()||/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/.test(value)){
250
+ if(!is.string(value)||!value.trim()||/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/.test(value)){
248
251
  throw new TypeError(`${label} must be text.`);
249
252
  }
250
253
  return value.trim();
@@ -266,11 +269,11 @@
266
269
  }
267
270
 
268
271
  function normalizeOptions(input={},previous=defaultOptions){
269
- if(!input||typeof input!=='object'||Array.isArray(input)){
272
+ if(!input||!is.object(input)||is.array(input)){
270
273
  throw new TypeError('Source viewer options must be an object.');
271
274
  }
272
275
  const labelInput=input.labels===undefined?{}:input.labels;
273
- if(!labelInput||typeof labelInput!=='object'||Array.isArray(labelInput)){
276
+ if(!labelInput||!is.object(labelInput)||is.array(labelInput)){
274
277
  throw new TypeError('Source viewer labels must be an object.');
275
278
  }
276
279
  const labels={...previous.labels};
@@ -283,7 +286,7 @@
283
286
  }
284
287
 
285
288
  function normalizeRenderOptions(input={}){
286
- if(!input||typeof input!=='object'||Array.isArray(input)){
289
+ if(!input||!is.object(input)||is.array(input)){
287
290
  throw new TypeError('Source render options must be an object.');
288
291
  }
289
292
  return {
@@ -306,10 +309,10 @@
306
309
  const sequence=++loadSequence;
307
310
  transition('loading');
308
311
  try{
309
- const pending=typeof source==='function'?source():source;
312
+ const pending=is.function(source)?source():source;
310
313
  const result=await pending;
311
314
  if(destroyed||sequence!==loadSequence)return false;
312
- if(result&&typeof result==='object'&&!Array.isArray(result)){
315
+ if(result&&is.object(result)&&!is.array(result)){
313
316
  return renderSource(result.text??result.value??'',{
314
317
  ...renderOptions,
315
318
  ...result
@@ -334,7 +337,7 @@
334
337
  }
335
338
 
336
339
  function renderSource(text='',renderOptions={}){
337
- if(typeof text!=='string')throw new TypeError('Source content must be a string.');
340
+ if(!is.string(text))throw new TypeError('Source content must be a string.');
338
341
  const metadata=normalizeRenderOptions(renderOptions);
339
342
  const lines=text.replace(/\r\n?/g,'\n').split('\n');
340
343
 
@@ -398,7 +401,7 @@
398
401
 
399
402
  function focusLine(value){
400
403
  const line=Number(value);
401
- if(!Number.isInteger(line)||line<1||line>current.lineCount)return false;
404
+ if(!is.integer(line)||line<1||line>current.lineCount)return false;
402
405
  const target=sourceLines.querySelector(`[data-line="${line}"]`);
403
406
  if(!target)return false;
404
407
  target.focus({preventScroll:true});
@@ -433,7 +436,7 @@
433
436
  state:`source-code-viewer-${state}`,
434
437
  reason,
435
438
  ...(state==='error'?{
436
- code:typeof error?.code==='string'
439
+ code:is.string(error?.code)
437
440
  ?error.code
438
441
  :'ARCANE_SOURCE_CODE_VIEWER_RENDER_REJECTED'
439
442
  }:{})
@@ -42,6 +42,9 @@
42
42
  </section>
43
43
 
44
44
  <script type="module">
45
+ const {default:Is}=await import('strong-type');
46
+ const is=new Is(false);
47
+
45
48
  const host=this; const root=host.shadowRoot;
46
49
  const title=root.querySelector('[data-title]'); const assessment=root.querySelector('[data-assessment]'); const badges=root.querySelector('[data-badges]');
47
50
  const elements=root.querySelector('[data-elements]'); const application=root.querySelector('[data-application]'); const limitations=root.querySelector('[data-limitations]'); const nextSteps=root.querySelector('[data-next-steps]');
@@ -73,7 +76,7 @@
73
76
  }
74
77
  function list(target,values=[],emptyLabel){
75
78
  target.replaceChildren();
76
- for(const value of Array.isArray(values)?values:[]){const item=document.createElement('li'); item.textContent=String(value); target.append(item);}
79
+ for(const value of is.array(values)?values:[]){const item=document.createElement('li'); item.textContent=String(value); target.append(item);}
77
80
  if(!target.children.length){const item=document.createElement('li'); item.textContent=emptyLabel; target.append(item);}
78
81
  }
79
82
  function sameOrigin(value=''){
@@ -81,8 +84,8 @@
81
84
  }
82
85
 
83
86
  function showFinding(value={},config={}){
84
- finding=value&&typeof value==='object'?value:null; if(!finding) throw new TypeError('A finding is required.');
85
- sources=Array.isArray(finding.sources)?finding.sources:[];
87
+ finding=value&&is.object(value)?value:null; if(!finding) throw new TypeError('A finding is required.');
88
+ sources=is.array(finding.sources)?finding.sources:[];
86
89
  title.textContent=String(finding.label||'Investigative lead'); assessment.textContent=String(finding.assessment||finding.conclusion||'Human review is required before reliance.');
87
90
  badges.replaceChildren();
88
91
  for(const value of [finding.allegedActorLabel?`Alleged actor: ${finding.allegedActorLabel}`:'Actor not assigned',finding.kind||'Investigative lead',finding.confidence?`${finding.confidence} confidence`:'Confidence not assigned']){const badge=document.createElement('span'); badge.className='badge'; badge.textContent=value; badges.append(badge);}
@@ -99,7 +102,7 @@
99
102
  originalPicker.replaceChildren(...sources.flatMap((source,index)=>{if(!source?.pdfUrl)return[];const item=document.createElement('option');item.value=String(index);item.textContent=`${source.recordId||'Source'}${source.page?` — PDF page ${source.page}`:''}`;return[item];})); originalPicker.disabled=!originalPicker.options.length;
100
103
  original.hidden=true; original.removeAttribute('src'); originalLabel.textContent='Select a cited excerpt to inspect the original source page.';
101
104
  options(actor,config.actorOptions||[]); options(disposition,config.dispositionOptions||[]); options(confidence,config.confidenceOptions||[]);
102
- const review=finding.review&&typeof finding.review==='object'?finding.review:{};
105
+ const review=finding.review&&is.object(finding.review)?finding.review:{};
103
106
  actor.value=String(review.classification||finding.allegedActor||'unassigned'); disposition.value=String(review.status||'unassessed'); confidence.value=String(review.attributes?.confidence||finding.confidence||'unassigned'); notes.value=String(review.notes||''); saveState.textContent='';
104
107
  if(!actor.value&&actor.options.length) actor.selectedIndex=0; if(!disposition.value&&disposition.options.length) disposition.selectedIndex=0; if(!confidence.value&&confidence.options.length) confidence.selectedIndex=0;
105
108
  const requestedSourceId=String(config.initialSourceId||'');
@@ -111,7 +114,7 @@
111
114
  }
112
115
 
113
116
  function selectSource(value=0){
114
- const index=Number.isInteger(value)?value:sources.findIndex(source=>String(source?.id||'')===String(value||''));
117
+ const index=is.integer(value)?value:sources.findIndex(source=>String(source?.id||'')===String(value||''));
115
118
  if(index<0||!sources[index]?.pdfUrl) return false;
116
119
  showSource(index); return true;
117
120
  }
@@ -132,6 +132,9 @@
132
132
  <button id="muteButton" type="button" class="mute" aria-pressed="true" aria-label="Voice unavailable" disabled>Voice unavailable</button>
133
133
 
134
134
  <script type="module">
135
+ const {default:Is}=await import('strong-type');
136
+ const is=new Is(false);
137
+
135
138
  const {arcaneLogging}=await import('arcane-os/logging');
136
139
 
137
140
  const completeValue = (value) => value;
@@ -251,10 +254,10 @@
251
254
  let causeCode = '';
252
255
  let message = '';
253
256
  try {
254
- causeCode = typeof error?.code === 'string'
257
+ causeCode = is.string(error?.code)
255
258
  ? error.code
256
259
  : '';
257
- message = typeof error?.message === 'string'
260
+ message = is.string(error?.message)
258
261
  ? error.message
259
262
  : '';
260
263
  } catch {}
@@ -293,7 +296,7 @@
293
296
  return String(reason || 'stt-transcription-cancelled');
294
297
  }
295
298
 
296
- const initialMutedWasHydrated = typeof host.initialMuted === 'boolean';
299
+ const initialMutedWasHydrated = is.boolean(host.initialMuted);
297
300
  host.initialMuted = initialMutedWasHydrated ? host.initialMuted : true;
298
301
  host.muted = true;
299
302
  host.configure = configure;
@@ -400,9 +403,9 @@
400
403
  }
401
404
 
402
405
  function selectedRole(role) {
403
- return typeof role?.providerId === 'string'
406
+ return is.string(role?.providerId)
404
407
  && role.providerId.length > 0
405
- && typeof role?.modelId === 'string'
408
+ && is.string(role?.modelId)
406
409
  && role.modelId.length > 0;
407
410
  }
408
411
 
@@ -411,7 +414,7 @@
411
414
  return host;
412
415
  }
413
416
  if (Object.prototype.hasOwnProperty.call(options, 'initialMuted')) {
414
- const configuredMuted = typeof options.initialMuted === 'boolean'
417
+ const configuredMuted = is.boolean(options.initialMuted)
415
418
  ? options.initialMuted
416
419
  : true;
417
420
  host.initialMuted = configuredMuted;
@@ -445,7 +448,7 @@
445
448
  if (destroyed) {
446
449
  return host;
447
450
  }
448
- if (typeof muted !== 'boolean') {
451
+ if (!is.boolean(muted)) {
449
452
  throw new TypeError('muted must be boolean');
450
453
  }
451
454
 
@@ -545,8 +548,8 @@
545
548
  function renderControls() {
546
549
  const transcriptionReady = sttRole.state === 'ready';
547
550
  const microphoneReady = host.availability.microphone
548
- && typeof globalThis.MediaRecorder === 'function'
549
- && typeof navigator.mediaDevices?.getUserMedia === 'function';
551
+ && is.function(globalThis.MediaRecorder)
552
+ && is.function(navigator.mediaDevices?.getUserMedia);
550
553
  const activationVisible = sttActivationController.visible;
551
554
  recordButton.hidden = activationVisible;
552
555
  recordButton.disabled = !transcriptionReady
@@ -611,9 +614,9 @@
611
614
  }
612
615
 
613
616
  function renderTTSControl() {
614
- const providerSelected = typeof ttsRole.providerId === 'string'
617
+ const providerSelected = is.string(ttsRole.providerId)
615
618
  && ttsRole.providerId.length > 0
616
- && typeof ttsRole.modelId === 'string'
619
+ && is.string(ttsRole.modelId)
617
620
  && ttsRole.modelId.length > 0;
618
621
  let disabled = destroyed;
619
622
  let label = host.muted ? 'Unmute' : 'Mute';
@@ -730,10 +733,10 @@
730
733
  function visibleErrorMessage(error, fallback) {
731
734
  try {
732
735
  if (error?.userSafe === true) {
733
- const message = typeof error.userMessage === 'string'
736
+ const message = is.string(error.userMessage)
734
737
  ? error.userMessage
735
738
  : error.message;
736
- if (typeof message === 'string' && message.trim()) {
739
+ if (is.string(message) && message.trim()) {
737
740
  return message;
738
741
  }
739
742
  }
@@ -912,9 +915,9 @@
912
915
  ) {
913
916
  return false;
914
917
  }
915
- const providerSelected = typeof ttsRole.providerId === 'string'
918
+ const providerSelected = is.string(ttsRole.providerId)
916
919
  && ttsRole.providerId.length > 0
917
- && typeof ttsRole.modelId === 'string'
920
+ && is.string(ttsRole.modelId)
918
921
  && ttsRole.modelId.length > 0;
919
922
  const canRequestLoad = ttsRole.state === 'ready'
920
923
  || (
@@ -995,7 +998,7 @@
995
998
  : action;
996
999
  let lifecycle;
997
1000
  try {
998
- if (typeof globalThis.ai?.setSpeechMuted === 'function') {
1001
+ if (is.function(globalThis.ai?.setSpeechMuted)) {
999
1002
  lifecycle = globalThis.ai.setSpeechMuted(action === 'unload');
1000
1003
  } else {
1001
1004
  lifecycle = requestAIRuntimeIntent(
@@ -1162,7 +1165,7 @@
1162
1165
 
1163
1166
  function resumeTTSPlayback() {
1164
1167
  const aiRuntime = globalThis.ai;
1165
- if (!aiRuntime || typeof aiRuntime.resumeAudio !== 'function') {
1168
+ if (!aiRuntime || !is.function(aiRuntime.resumeAudio)) {
1166
1169
  return;
1167
1170
  }
1168
1171
 
@@ -1196,8 +1199,8 @@
1196
1199
  || sttRole.state !== 'ready'
1197
1200
  || sttRole.busy
1198
1201
  || !host.availability.microphone
1199
- || typeof globalThis.MediaRecorder !== 'function'
1200
- || typeof navigator.mediaDevices?.getUserMedia !== 'function'
1202
+ || !is.function(globalThis.MediaRecorder)
1203
+ || !is.function(navigator.mediaDevices?.getUserMedia)
1201
1204
  || recordingRequested
1202
1205
  || captureSession
1203
1206
  || transcriptionActive
@@ -1473,7 +1476,7 @@
1473
1476
  );
1474
1477
  return false;
1475
1478
  }
1476
- if (typeof aiRuntime?.fetchSTT !== 'function') {
1479
+ if (!is.function(aiRuntime?.fetchSTT)) {
1477
1480
  const error = new Error('The selected transcription service is unavailable.');
1478
1481
  error.code = 'ARCANE_SPEECH_STT_RUNTIME_METHOD_UNAVAILABLE';
1479
1482
  throw error;
@@ -1490,7 +1493,7 @@
1490
1493
  ) {
1491
1494
  return false;
1492
1495
  }
1493
- if (typeof transcription !== 'string') {
1496
+ if (!is.string(transcription)) {
1494
1497
  throw new TypeError('The selected transcription service returned invalid text.');
1495
1498
  }
1496
1499
 
@@ -38,6 +38,9 @@
38
38
  </section>
39
39
 
40
40
  <script type="module">
41
+ const {default:Is}=await import('strong-type');
42
+ const is=new Is(false);
43
+
41
44
  const host=this;
42
45
  const root=host.shadowRoot;
43
46
  const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
@@ -89,7 +92,7 @@
89
92
  }
90
93
 
91
94
  function updateItem(id,update={}){
92
- const index=typeof id==='number'?id:items.findIndex(item=>item.id===`${id}`);
95
+ const index=is.number(id)?id:items.findIndex(item=>item.id===`${id}`);
93
96
  if(index<0||index>=items.length){
94
97
  return null;
95
98
  }
@@ -45,6 +45,9 @@
45
45
  </section>
46
46
 
47
47
  <script type="module">
48
+ const {default:Is}=await import('strong-type');
49
+ const is=new Is(false);
50
+
48
51
  const host=this;
49
52
  const root=host.shadowRoot;
50
53
  const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
@@ -135,7 +138,7 @@
135
138
 
136
139
  function updateTask(id,update={}){
137
140
  if(destroyed)return null;
138
- const index=typeof id==='number'?id:tasks.findIndex(task=>task.id===`${id}`);
141
+ const index=is.number(id)?id:tasks.findIndex(task=>task.id===`${id}`);
139
142
  if(index<0||index>=tasks.length){
140
143
  return null;
141
144
  }
@@ -203,7 +206,7 @@
203
206
  if(running){
204
207
  return activeRun;
205
208
  }
206
- const runnable=Array.from(jobs||[]).filter(job=>job&&typeof job.task==='function');
209
+ const runnable=Array.from(jobs||[]).filter(job=>job&&is.function(job.task));
207
210
  if(!runnable.length){
208
211
  setTasks([]);
209
212
  return [];
@@ -244,7 +247,7 @@
244
247
  'task-progress-error',
245
248
  {error,id:tasks[index]?.id,index,task:{...tasks[index]}},
246
249
  {
247
- code:typeof error?.code==='string'
250
+ code:is.string(error?.code)
248
251
  ?error.code
249
252
  :'ARCANE_TASK_EXECUTION_REJECTED',
250
253
  index,
@@ -26,6 +26,9 @@
26
26
  <form class="prompt" id="prompt"><label class="prompt-label" id="promptLabel" for="input">arcane ❯</label><textarea class="input" id="input" rows="1" autocomplete="off" autocapitalize="off" spellcheck="false" placeholder="Enter a command"></textarea></form>
27
27
  </section>
28
28
  <script type="module">
29
+ const {default:Is}=await import('strong-type');
30
+ const is=new Is(false);
31
+
29
32
  const {parseAnsi,stripAnsi}=await import('../modules/AnsiText.js');
30
33
  const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
31
34
  const host=this,root=host.shadowRoot;
@@ -62,7 +65,7 @@
62
65
  function renderText(row,text,term){const clean=stripAnsi(text);if(!term){for(const token of parseAnsi(text)){const span=document.createElement('span');span.textContent=token.text;const classes=[];if(token.style.bold)classes.push('ansi-bold');if(token.style.dim)classes.push('ansi-dim');if(token.style.italic)classes.push('ansi-italic');if(token.style.underline)classes.push('ansi-underline');if(token.style.foreground&&!token.style.foreground.startsWith('index-'))classes.push(`ansi-${token.style.foreground}`);span.className=classes.join(' ');row.append(span);}return;}let cursor=0,index=clean.toLowerCase().indexOf(term);while(index>=0){row.append(document.createTextNode(clean.slice(cursor,index)));const mark=document.createElement('mark');mark.textContent=clean.slice(index,index+term.length);row.append(mark);cursor=index+term.length;index=clean.toLowerCase().indexOf(term,cursor);}row.append(document.createTextNode(clean.slice(cursor)));}
63
66
  function toggleSearch(force){search.hidden=force===undefined?!search.hidden:!force;if(!search.hidden)search.focus();else{search.value='';render();input.focus();}}
64
67
  function resizeInput(){input.style.height='auto';input.style.height=`${Math.min(input.scrollHeight,160)}px`;}
65
- function emit(type,detail={}){if(destroyed)return false;const operationId=`${events.instanceId}:${type}:${(++eventOperationSequence).toString(36)}`;const publication=events.dispatch(type,detail,{operationId,publicDetail:{hasSession:Boolean(detail.sessionId),lineLength:typeof detail.line==='string'?detail.line.length:0,historyCount:Array.isArray(detail.history)?detail.history.length:0,reason:type}});return projectArcaneDOMEvent(host,publication.occurrence,{bubbles:true,composed:true});}
68
+ function emit(type,detail={}){if(destroyed)return false;const operationId=`${events.instanceId}:${type}:${(++eventOperationSequence).toString(36)}`;const publication=events.dispatch(type,detail,{operationId,publicDetail:{hasSession:Boolean(detail.sessionId),lineLength:is.string(detail.line)?detail.line.length:0,historyCount:is.array(detail.history)?detail.history.length:0,reason:type}});return projectArcaneDOMEvent(host,publication.occurrence,{bubbles:true,composed:true});}
66
69
  function destroy(){if(destroyed)return false;destroyed=true;host.ready=false;for(const id of ['find','clear','interrupt','settings','copy'])root.querySelector(`#${id}`).onclick=null;events.dispose();return true;}
67
70
  emit('terminal-workspace-ready');
68
71
  </script>
@@ -60,6 +60,9 @@
60
60
  </section>
61
61
 
62
62
  <script type="module">
63
+ const {default:Is}=await import('strong-type');
64
+ const is=new Is(false);
65
+
63
66
  const {arcaneLogging}=await import('arcane-os/logging');
64
67
 
65
68
  const completeValue=(value)=>value;
@@ -302,7 +305,7 @@
302
305
  return false;
303
306
  }
304
307
  if(
305
- typeof MediaRecorder==='undefined'
308
+ is.undefined(globalThis.MediaRecorder)
306
309
  ||!navigator.mediaDevices?.getUserMedia
307
310
  ){
308
311
  setState('error',options.messages.unsupported);
@@ -468,7 +471,7 @@
468
471
  ||destroyed){
469
472
  return false;
470
473
  }
471
- const segment=typeof result==='string'?result:'';
474
+ const segment=is.string(result)?result:'';
472
475
  if(segment.length===0){
473
476
  setState('idle',options.messages.noSpeech);
474
477
  return false;
@@ -483,7 +486,7 @@
483
486
 
484
487
  if(options.persist){
485
488
  const save=options.onSave||(
486
- typeof host.save==='function'?host.save.bind(host):null
489
+ is.function(host.save)?host.save.bind(host):null
487
490
  );
488
491
  if(!save){
489
492
  setState('error',options.messages.saveError);
@@ -594,10 +597,10 @@
594
597
  if(options.transcribe){
595
598
  return options.transcribe(file,context);
596
599
  }
597
- if(typeof globalThis.ai?.fetchSTT!=='function'){
600
+ if(!is.function(globalThis.ai?.fetchSTT)){
598
601
  await import('../modules/AI.js');
599
602
  }
600
- if(typeof globalThis.ai?.fetchSTT!=='function'){
603
+ if(!is.function(globalThis.ai?.fetchSTT)){
601
604
  const error=new Error('Speech transcription is not configured.');
602
605
  error.code='ARCANE_VOICE_TRANSCRIPTION_STT_RUNTIME_METHOD_UNAVAILABLE';
603
606
  throw error;
@@ -646,7 +649,7 @@
646
649
  const generation=sessionGeneration;
647
650
  const completionTranscript=transcript;
648
651
  const complete=options.onComplete||(
649
- typeof host.complete==='function'?host.complete.bind(host):null
652
+ is.function(host.complete)?host.complete.bind(host):null
650
653
  );
651
654
  setState('completing',options.messages.completing,{
652
655
  beginOperation:'completion'
@@ -963,10 +966,10 @@
963
966
  function visibleErrorMessage(error,fallback){
964
967
  try{
965
968
  if(error?.userSafe===true){
966
- const message=typeof error.userMessage==='string'
969
+ const message=is.string(error.userMessage)
967
970
  ?error.userMessage
968
971
  :error.message;
969
- if(typeof message==='string'&&message.trim()){
972
+ if(is.string(message)&&message.trim()){
970
973
  return message;
971
974
  }
972
975
  }
@@ -59,6 +59,9 @@ button{min-width:2.5rem}
59
59
  <p id="status" class="status" role="status" aria-live="polite">Enter an address. Some sites block embedded viewing; use Open externally when needed.</p>
60
60
  </section>
61
61
  <script type="module">
62
+ const {default:Is}=await import('strong-type');
63
+ const is=new Is(false);
64
+
62
65
  const host=this;
63
66
  const root=host.shadowRoot;
64
67
  const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
@@ -128,7 +131,7 @@ async function evaluate(url,intent){
128
131
  }
129
132
  try{
130
133
  const decision=await navigationGuard(url,{intent});
131
- if(!decision||typeof decision!=='object'||typeof decision.blocked!=='boolean'){
134
+ if(!decision||!is.object(decision)||!is.boolean(decision.blocked)){
132
135
  throw new TypeError('Navigation policy returned an invalid decision.');
133
136
  }
134
137
  return decision;
@@ -230,7 +233,7 @@ function move(amount){
230
233
  function configure(options){
231
234
  if(destroyed)return false;
232
235
  if(options?.homeUrl)homeUrl=normalize(options.homeUrl);
233
- navigationGuard=typeof options?.navigationGuard==='function'?options.navigationGuard:null;
236
+ navigationGuard=is.function(options?.navigationGuard)?options.navigationGuard:null;
234
237
  if(options?.initialUrl)ownNavigation(navigate(options.initialUrl));
235
238
  else updateButtons();
236
239
  return host;
@@ -1,5 +1,8 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  function deepFreeze(value){
2
- if(!value||typeof value!=='object'||Object.isFrozen(value)) return value;
5
+ if(!value||!is.object(value)||Object.isFrozen(value)) return value;
3
6
  for(const child of Object.values(value)) deepFreeze(child);
4
7
  return Object.freeze(value);
5
8
  }
@@ -9,7 +12,7 @@ export default class ApiModelRecord{
9
12
  const url=new URL(String(endpoint||''));
10
13
  if(!['http:','https:'].includes(url.protocol)) throw new TypeError('API model endpoints must use HTTP or HTTPS.');
11
14
  const instant=new Date(fetchedAt);
12
- if(Number.isNaN(instant.valueOf())) throw new TypeError('API model fetch time is invalid.');
15
+ if(is.NaN(instant.valueOf())) throw new TypeError('API model fetch time is invalid.');
13
16
  this.endpoint=url.href;
14
17
  this.fetchedAt=instant.toISOString();
15
18
  this.metadata=deepFreeze({...metadata});
@@ -1,11 +1,14 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  export default class Calculation{
2
5
  constructor({expression,result,createdAt=new Date()}={}){
3
6
  this.expression=String(expression??'');
4
7
  if(!this.expression.trim()) throw new TypeError('Calculation expressions must contain text.');
5
8
  this.result=Number(result);
6
- if(!Number.isFinite(this.result)) throw new RangeError('Calculation result must be finite.');
9
+ if(!is.finite(this.result)) throw new RangeError('Calculation result must be finite.');
7
10
  const instant=new Date(createdAt);
8
- if(Number.isNaN(instant.valueOf())) throw new TypeError('Calculation time is invalid.');
11
+ if(is.NaN(instant.valueOf())) throw new TypeError('Calculation time is invalid.');
9
12
  this.createdAt=instant.toISOString();
10
13
  }
11
14
  toJSON(){return {...this};}