arcane-os 0.5.17 → 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 (183) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +9 -9
  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 +46 -43
  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 +5 -1
  17. package/docs/reference/ai/browser-speech.md +198 -5
  18. package/docs/reference/availability-and-normalization.md +40 -0
  19. package/docs/reference/inventory/package-api.json +1 -1
  20. package/docs/reference/inventory/runtime-components.json +1 -1
  21. package/docs/reference/inventory/runtime-modules.json +20 -24
  22. package/docs/reference/protocols.md +1 -1
  23. package/docs/reference/runtime-modules.md +125 -35
  24. package/docs/reference/sdk-api.md +23 -7
  25. package/examples/wasm-ai-demo/index.html +1 -0
  26. package/node_modules/event-pubsub/node_modules/strong-type/README.md +408 -0
  27. package/node_modules/event-pubsub/node_modules/strong-type/assets/strong-type-header.png +0 -0
  28. package/node_modules/event-pubsub/node_modules/strong-type/index.js +1151 -0
  29. package/node_modules/event-pubsub/node_modules/strong-type/licence +21 -0
  30. package/node_modules/event-pubsub/node_modules/strong-type/node.js +125 -0
  31. package/node_modules/event-pubsub/node_modules/strong-type/package.json +61 -0
  32. package/node_modules/strong-type/README.md +42 -24
  33. package/node_modules/strong-type/benchmark/core.js +324 -0
  34. package/node_modules/strong-type/index.js +207 -82
  35. package/node_modules/strong-type/package.json +14 -6
  36. package/package.json +3 -3
  37. package/runtime/arcane/components/app-bar.html +5 -2
  38. package/runtime/arcane/components/assistant-panel.html +5 -2
  39. package/runtime/arcane/components/chart.html +15 -12
  40. package/runtime/arcane/components/chat.html +117 -138
  41. package/runtime/arcane/components/dashboard-config.html +6 -3
  42. package/runtime/arcane/components/data-view.html +4 -1
  43. package/runtime/arcane/components/directory-picker.html +5 -2
  44. package/runtime/arcane/components/document-inspector.html +6 -3
  45. package/runtime/arcane/components/file-drop.html +5 -2
  46. package/runtime/arcane/components/file-inspector.html +9 -6
  47. package/runtime/arcane/components/file-manager.html +16 -13
  48. package/runtime/arcane/components/local-ai-status.html +5 -2
  49. package/runtime/arcane/components/markdown-document.html +14 -11
  50. package/runtime/arcane/components/markdown-editor.html +4 -1
  51. package/runtime/arcane/components/modal.html +4 -1
  52. package/runtime/arcane/components/output-panel.html +6 -3
  53. package/runtime/arcane/components/preferences-form.html +4 -1
  54. package/runtime/arcane/components/record-timeline.html +8 -5
  55. package/runtime/arcane/components/relationship-board.html +9 -6
  56. package/runtime/arcane/components/source-code-viewer.html +12 -9
  57. package/runtime/arcane/components/source-explanation.html +8 -5
  58. package/runtime/arcane/components/speech.html +24 -21
  59. package/runtime/arcane/components/summary-strip.html +4 -1
  60. package/runtime/arcane/components/task-progress.html +6 -3
  61. package/runtime/arcane/components/terminal-workspace.html +4 -1
  62. package/runtime/arcane/components/voice-transcription.html +11 -8
  63. package/runtime/arcane/components/web-navigator.html +5 -2
  64. package/runtime/arcane/entities/ApiModelRecord.js +5 -2
  65. package/runtime/arcane/entities/Calculation.js +5 -2
  66. package/runtime/arcane/entities/Chat.js +33 -33
  67. package/runtime/arcane/entities/CommunicationMessage.js +4 -1
  68. package/runtime/arcane/entities/CommunicationThread.js +4 -1
  69. package/runtime/arcane/entities/File.js +1 -1
  70. package/runtime/arcane/entities/Image.js +8 -5
  71. package/runtime/arcane/entities/IntentEnvelope.js +20 -17
  72. package/runtime/arcane/entities/Preference.js +9 -6
  73. package/runtime/arcane/entities/Theme.js +5 -2
  74. package/runtime/arcane/entities/User.js +11 -11
  75. package/runtime/arcane/entities/Weather.js +5 -2
  76. package/runtime/arcane/modules/AI.js +491 -165
  77. package/runtime/arcane/modules/AIPreferenceRuntime.js +5 -2
  78. package/runtime/arcane/modules/AIPreferenceTuple.js +9 -6
  79. package/runtime/arcane/modules/AIProviderRuntime.js +88 -85
  80. package/runtime/arcane/modules/AIResponseURLPolicy.js +148 -62
  81. package/runtime/arcane/modules/AIRuntimeState.js +29 -26
  82. package/runtime/arcane/modules/AnsiText.js +4 -1
  83. package/runtime/arcane/modules/ApiModelDatabase.js +22 -19
  84. package/runtime/arcane/modules/AppDataScope.js +8 -5
  85. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +4 -1
  86. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +8 -5
  87. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +18 -15
  88. package/runtime/arcane/modules/AsyncBoundary.js +13 -10
  89. package/runtime/arcane/modules/BrowserTestSuite.js +19 -16
  90. package/runtime/arcane/modules/CalculatorEngine.js +5 -2
  91. package/runtime/arcane/modules/ChatRecords.js +18 -15
  92. package/runtime/arcane/modules/CommunicationAppController.js +9 -6
  93. package/runtime/arcane/modules/CommunicationHub.js +9 -6
  94. package/runtime/arcane/modules/CommunicationPreferences.js +4 -1
  95. package/runtime/arcane/modules/CommunicationProviderRegistry.js +4 -1
  96. package/runtime/arcane/modules/ComponentContracts.js +56 -53
  97. package/runtime/arcane/modules/ConfiguredAIChatSession.js +30 -27
  98. package/runtime/arcane/modules/ConversationActionItems.js +15 -12
  99. package/runtime/arcane/modules/ConversationClosingReport.js +11 -8
  100. package/runtime/arcane/modules/ConversationTimebox.js +28 -25
  101. package/runtime/arcane/modules/CoreLocalModelCatalog.js +17 -14
  102. package/runtime/arcane/modules/DBLS.js +7 -4
  103. package/runtime/arcane/modules/DBOPFS.js +7 -7
  104. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +34 -31
  105. package/runtime/arcane/modules/DevelopmentWorkspace.js +4 -1
  106. package/runtime/arcane/modules/DirectoryPicker.js +8 -5
  107. package/runtime/arcane/modules/DocumentLexicalSearch.js +11 -8
  108. package/runtime/arcane/modules/DocumentNavigation.js +7 -4
  109. package/runtime/arcane/modules/Errors.js +28 -25
  110. package/runtime/arcane/modules/HTMLImport.js +7 -4
  111. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +15 -12
  112. package/runtime/arcane/modules/LocalAIReadiness.js +21 -18
  113. package/runtime/arcane/modules/LocalAIReadinessController.js +6 -3
  114. package/runtime/arcane/modules/MD.js +1 -1
  115. package/runtime/arcane/modules/Mail.js +46 -43
  116. package/runtime/arcane/modules/MailOutbox.mjs +48 -45
  117. package/runtime/arcane/modules/MailTransport.mjs +29 -26
  118. package/runtime/arcane/modules/MemoryRecords.js +7 -4
  119. package/runtime/arcane/modules/MessageAdvisory.js +6 -3
  120. package/runtime/arcane/modules/ModelDefinition.js +7 -4
  121. package/runtime/arcane/modules/Ollama.js +6 -3
  122. package/runtime/arcane/modules/OllamaModelIdentifier.js +4 -1
  123. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +18 -15
  124. package/runtime/arcane/modules/PersistentAIChatSession.js +33 -30
  125. package/runtime/arcane/modules/PreferenceStore.js +20 -17
  126. package/runtime/arcane/modules/PreparedSpeech.js +485 -0
  127. package/runtime/arcane/modules/Questionnaire.js +6 -3
  128. package/runtime/arcane/modules/RecordLinkIndex.js +4 -1
  129. package/runtime/arcane/modules/RecordPassageIndex.js +9 -6
  130. package/runtime/arcane/modules/RecordReviewStore.js +12 -9
  131. package/runtime/arcane/modules/RiskSignalAnalyzer.js +4 -1
  132. package/runtime/arcane/modules/ScopedOPFSCache.js +6 -3
  133. package/runtime/arcane/modules/ScreenCapture.js +20 -17
  134. package/runtime/arcane/modules/SpeechPlayback.js +29 -26
  135. package/runtime/arcane/modules/StaticDocumentCatalog.js +33 -30
  136. package/runtime/arcane/modules/SystemAppearance.js +5 -2
  137. package/runtime/arcane/modules/SystemToolRegistry.js +6 -3
  138. package/runtime/arcane/modules/TerminalClient.js +14 -11
  139. package/runtime/arcane/modules/TerminalCommandRegistry.js +4 -1
  140. package/runtime/arcane/modules/ThemeBootstrap.js +9 -6
  141. package/runtime/arcane/modules/ThemeManager.js +5 -2
  142. package/runtime/arcane/modules/TimeGuard.js +1 -1
  143. package/runtime/arcane/modules/ToolCallRouter.js +11 -8
  144. package/runtime/arcane/modules/WaitForComponent.js +19 -16
  145. package/runtime/arcane/modules/YouTubeMedia.js +4 -1
  146. package/runtime/strong-type/index.js +1276 -352
  147. package/runtime/strong-type/package.json +69 -45
  148. package/src/app-descriptor.mjs +17 -14
  149. package/src/application-tests.mjs +4 -1
  150. package/src/cli/main.mjs +8 -5
  151. package/src/constants.mjs +4 -1
  152. package/src/dev-server.mjs +8 -5
  153. package/src/doctor.mjs +5 -2
  154. package/src/dom-event-instrumentation.mjs +24 -20
  155. package/src/errors.mjs +7 -3
  156. package/src/event-manager.mjs +111 -108
  157. package/src/event-queue.mjs +8 -5
  158. package/src/events.mjs +12 -9
  159. package/src/import-map.mjs +35 -27
  160. package/src/installed-sdk-runtime.mjs +4 -1
  161. package/src/integrated-provider-loader.mjs +9 -6
  162. package/src/mail-credentials.mjs +16 -13
  163. package/src/mail-server.mjs +53 -50
  164. package/src/mail.mjs +18 -15
  165. package/src/native-plan.mjs +12 -9
  166. package/src/native-provider-loader.mjs +7 -4
  167. package/src/packager/core.mjs +18 -15
  168. package/src/process.mjs +6 -3
  169. package/src/release-bundle.mjs +14 -11
  170. package/src/runtime.mjs +4 -1
  171. package/src/scaffold.mjs +9 -6
  172. package/src/sdk-browser-runtime.mjs +4 -1
  173. package/src/source-server.mjs +17 -14
  174. package/src/targets/index.mjs +5 -2
  175. package/src/templates/workspace-template.mjs +13 -6
  176. package/src/testing-loader.mjs +7 -4
  177. package/src/testing.mjs +10 -7
  178. package/src/toolchain.mjs +13 -10
  179. package/src/update-check.mjs +9 -6
  180. package/src/workspace-operation-lock.mjs +14 -11
  181. package/src/workspace-runtime.mjs +4 -1
  182. package/src/workspace.mjs +17 -14
  183. package/runtime/arcane/modules/AIResponseLength.js +0 -32
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import { arcaneLogging } from 'arcane-os/logging';
2
5
  import {createArcaneEventSource} from 'arcane-os/event-manager';
3
6
  import GifEncoder from './GifEncoder.js';
@@ -163,10 +166,10 @@ function signalLike(value){
163
166
  return value===undefined
164
167
  ||value===null
165
168
  ||(
166
- typeof value==='object'
167
- &&typeof value.aborted==='boolean'
168
- &&typeof value.addEventListener==='function'
169
- &&typeof value.removeEventListener==='function'
169
+ is.object(value)
170
+ &&is.boolean(value.aborted)
171
+ &&is.function(value.addEventListener)
172
+ &&is.function(value.removeEventListener)
170
173
  );
171
174
  }
172
175
 
@@ -190,7 +193,7 @@ function abortError(cause){
190
193
 
191
194
  function optionsRecord(value,label){
192
195
  if(value===undefined)return {};
193
- if(!value||typeof value!=='object'||Array.isArray(value)){
196
+ if(!value||!is.object(value)||is.array(value)){
194
197
  throw captureError(
195
198
  SCREEN_CAPTURE_ERRORS.optionsRecordRejected,
196
199
  label+' must be an object.',
@@ -211,7 +214,7 @@ function optionsRecord(value,label){
211
214
 
212
215
  function admittedOperationId(value){
213
216
  if(value===undefined||value===null)return null;
214
- if(typeof value!=='string'
217
+ if(!is.string(value)
215
218
  ||value.trim()!==value
216
219
  ||!value){
217
220
  throw captureError(
@@ -225,7 +228,7 @@ function admittedOperationId(value){
225
228
  }
226
229
 
227
230
  function imageResultType(blob){
228
- const reported=typeof blob?.type==='string'?blob.type.trim().toLowerCase():'';
231
+ const reported=is.string(blob?.type)?blob.type.trim().toLowerCase():'';
229
232
  if(!reported)return SCREEN_CAPTURE_IMAGE_TYPE_FALLBACK;
230
233
  const subtype=reported.startsWith('image/')
231
234
  ?reported.slice('image/'.length).split(';',1)[0]
@@ -252,7 +255,7 @@ function supportedRecorderType(Recorder){
252
255
  }
253
256
 
254
257
  function reportDetachedError(error){
255
- if(typeof globalThis.reportError==='function')globalThis.reportError(error);
258
+ if(is.function(globalThis.reportError))globalThis.reportError(error);
256
259
  else arcaneLogging.error?.(error);
257
260
  }
258
261
 
@@ -436,7 +439,7 @@ export default class ScreenCapture extends EventTarget{
436
439
  if(this.#events.disposed)return null;
437
440
  const compatibilityDetail={
438
441
  ...detail,
439
- mode:typeof detail?.mode==='string'?detail.mode:operation.mode,
442
+ mode:is.string(detail?.mode)?detail.mode:operation.mode,
440
443
  operationId:operation.id,
441
444
  reason,
442
445
  ...(code?{code}:{})
@@ -527,7 +530,7 @@ export default class ScreenCapture extends EventTarget{
527
530
 
528
531
  #attachTrackEnd(operation,stream){
529
532
  for(const track of stream?.getTracks?.()||[]){
530
- if(typeof track?.addEventListener!=='function')continue;
533
+ if(!is.function(track?.addEventListener))continue;
531
534
  const owner=this;
532
535
  function stopEndedScreenCaptureTrack(){
533
536
  if(!owner.#isCurrent(operation))return;
@@ -782,7 +785,7 @@ export default class ScreenCapture extends EventTarget{
782
785
  );
783
786
  this.#assertCurrent(operation);
784
787
  stage='image-encoding';
785
- const type=typeof options.type==='string'&&options.type.trim()
788
+ const type=is.string(options.type)&&options.type.trim()
786
789
  ?options.type.trim()
787
790
  :'image/png';
788
791
  const blob=await this.#canvasBlob(
@@ -990,7 +993,7 @@ export default class ScreenCapture extends EventTarget{
990
993
 
991
994
  #startGifFrameSampling(operation,context,frameDelay){
992
995
  const requestedDelay=Number(frameDelay);
993
- const explicitDelay=Number.isFinite(requestedDelay)&&requestedDelay>0
996
+ const explicitDelay=is.finite(requestedDelay)&&requestedDelay>0
994
997
  ?requestedDelay
995
998
  :null;
996
999
  let active=true;
@@ -1000,10 +1003,10 @@ export default class ScreenCapture extends EventTarget{
1000
1003
  if(explicitDelay!==null){
1001
1004
  requestFrame=callback=>setTimeout(()=>callback(monotonicNow()),explicitDelay);
1002
1005
  cancelFrame=handle=>clearTimeout(handle);
1003
- }else if(typeof operation.video?.requestVideoFrameCallback==='function'){
1006
+ }else if(is.function(operation.video?.requestVideoFrameCallback)){
1004
1007
  requestFrame=callback=>operation.video.requestVideoFrameCallback(callback);
1005
1008
  cancelFrame=handle=>operation.video.cancelVideoFrameCallback?.(handle);
1006
- }else if(typeof globalThis.requestAnimationFrame==='function'){
1009
+ }else if(is.function(globalThis.requestAnimationFrame)){
1007
1010
  requestFrame=callback=>globalThis.requestAnimationFrame(callback);
1008
1011
  cancelFrame=handle=>globalThis.cancelAnimationFrame?.(handle);
1009
1012
  }else{
@@ -1065,7 +1068,7 @@ export default class ScreenCapture extends EventTarget{
1065
1068
  operation.frames=[];
1066
1069
  operation.frameTimes=[];
1067
1070
  const requestedDelay=Number(options.frameDelay);
1068
- operation.gifDelay=Number.isFinite(requestedDelay)&&requestedDelay>0
1071
+ operation.gifDelay=is.finite(requestedDelay)&&requestedDelay>0
1069
1072
  ?requestedDelay
1070
1073
  :0;
1071
1074
  operation.startedAt=Date.now();
@@ -1261,7 +1264,7 @@ export default class ScreenCapture extends EventTarget{
1261
1264
  if(options.signal?.aborted)throw abortError(options.signal.reason);
1262
1265
  const width=Number(video.videoWidth);
1263
1266
  const height=Number(video.videoHeight);
1264
- if(!Number.isFinite(width)||width<=0||!Number.isFinite(height)||height<=0){
1267
+ if(!is.finite(width)||width<=0||!is.finite(height)||height<=0){
1265
1268
  throw captureError(
1266
1269
  SCREEN_CAPTURE_ERRORS.displayMetadataRejected,
1267
1270
  'The selected display reported invalid frame dimensions.'
@@ -1370,7 +1373,7 @@ export default class ScreenCapture extends EventTarget{
1370
1373
  TypeError
1371
1374
  );
1372
1375
  }
1373
- const mode=typeof detail?.mode==='string'
1376
+ const mode=is.string(detail?.mode)
1374
1377
  ?detail.mode
1375
1378
  :(this.#operation?.mode??'capture');
1376
1379
  const operation=this.#operation??{
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import {createArcaneEventSource} from 'arcane-os/event-manager';
2
5
  import {stripSpeechFormatting} from 'arcane-os/speech-text';
3
6
 
@@ -40,15 +43,15 @@ function splitSpeechText(value=''){
40
43
  }
41
44
 
42
45
  function normalizeParts(parts,defaultVoice=null,defaultSpeed=1){
43
- if(!Array.isArray(parts)||!parts.length){
46
+ if(!is.array(parts)||!parts.length){
44
47
  throw new TypeError('Narration text cannot be blank. The full visual content remains available.');
45
48
  }
46
49
  return parts.map(function normalizePart(part){
47
- const candidate=typeof part==='string'?{input:part}:part;
50
+ const candidate=is.string(part)?{input:part}:part;
48
51
  const input=String(candidate?.input??'');
49
52
  if(!input.trim())throw new TypeError('Speech segments cannot be blank. The full visual content remains available.');
50
53
  const pauseAfterMs=Number(candidate?.pauseAfterMs??0);
51
- if(!Number.isFinite(pauseAfterMs)||pauseAfterMs<0){
54
+ if(!is.finite(pauseAfterMs)||pauseAfterMs<0){
52
55
  throw new RangeError('Speech segment pauses must be a nonnegative number of milliseconds.');
53
56
  }
54
57
  const voiceValue=candidate?.voice??defaultVoice;
@@ -57,7 +60,7 @@ function normalizeParts(parts,defaultVoice=null,defaultSpeed=1){
57
60
  if(voiceValue!==null&&voiceValue!==undefined&&!voice.trim()){
58
61
  throw new TypeError('Speech segment voice cannot be blank.');
59
62
  }
60
- if(!Number.isFinite(speed)||speed<=0)throw new RangeError('Every speech segment requires a positive speech speed.');
63
+ if(!is.finite(speed)||speed<=0)throw new RangeError('Every speech segment requires a positive speech speed.');
61
64
  return {
62
65
  input,
63
66
  pauseAfterMs,
@@ -84,7 +87,7 @@ function synthesizedAudioContractError(cause=null){
84
87
 
85
88
  function normalizeAudioContentType(value,fallback=null){
86
89
  const candidate=value===undefined||value===null?fallback:value;
87
- if(typeof candidate!=='string'||!candidate.trim()){
90
+ if(!is.string(candidate)||!candidate.trim()){
88
91
  throw synthesizedAudioContractError();
89
92
  }
90
93
  const contentType=candidate.split(';',1)[0].trim().toLowerCase();
@@ -103,7 +106,7 @@ function speechAudioBytes(value){
103
106
  if(value instanceof ArrayBuffer){
104
107
  return new Uint8Array(value.slice(0));
105
108
  }
106
- if(ArrayBuffer.isView(value)){
109
+ if(is.arrayBufferView(value)){
107
110
  return new Uint8Array(
108
111
  value.buffer.slice(
109
112
  value.byteOffset,
@@ -203,23 +206,23 @@ async function validatedSpeechBytes(bytes,contentType='audio/wav'){
203
206
  async function playableSpeechBlob(response){
204
207
  try{
205
208
  if(response instanceof Blob)return validatedSpeechBlob(response);
206
- if(response instanceof ArrayBuffer||ArrayBuffer.isView(response)){
209
+ if(response instanceof ArrayBuffer||is.arrayBufferView(response)){
207
210
  return validatedSpeechBytes(speechAudioBytes(response));
208
211
  }
209
- if(response&&typeof response==='object'){
212
+ if(response&&is.object(response)){
210
213
  if(response.audio instanceof Blob){
211
214
  return validatedSpeechBlob(
212
215
  response.audio,
213
216
  response.contentType
214
217
  );
215
218
  }
216
- if(response.audio instanceof ArrayBuffer||ArrayBuffer.isView(response.audio)){
219
+ if(response.audio instanceof ArrayBuffer||is.arrayBufferView(response.audio)){
217
220
  return validatedSpeechBytes(
218
221
  speechAudioBytes(response.audio),
219
222
  response.contentType
220
223
  );
221
224
  }
222
- if(typeof response.audioBase64==='string'&&response.audioBase64){
225
+ if(is.string(response.audioBase64)&&response.audioBase64){
223
226
  return validatedSpeechBytes(
224
227
  base64Bytes(response.audioBase64),
225
228
  response.contentType
@@ -238,7 +241,7 @@ async function playableSpeechBlob(response){
238
241
 
239
242
  function speechPlaybackFailureReason(error){
240
243
  if(error?.name==='AbortError')return 'speech-synthesis-cancelled';
241
- if(typeof error?.code==='string'
244
+ if(is.string(error?.code)
242
245
  &&Object.hasOwn(SPEECH_PLAYBACK_FAILURE_REASONS,error.code)){
243
246
  return SPEECH_PLAYBACK_FAILURE_REASONS[error.code];
244
247
  }
@@ -295,7 +298,7 @@ class LatestSpeechQueue{
295
298
  }
296
299
 
297
300
  function queueFor(speech){
298
- if((typeof speech!=='object'||speech===null)&&typeof speech!=='function'){
301
+ if((!is.object(speech)||speech===null)&&!is.function(speech)){
299
302
  throw new TypeError('A local speech client is required.');
300
303
  }
301
304
  let queue=queuesBySpeechClient.get(speech);
@@ -307,15 +310,15 @@ function queueFor(speech){
307
310
  }
308
311
 
309
312
  function providerSpeechCapacity(speech){
310
- if(typeof speech?.fetchTTS!=='function')return null;
313
+ if(!is.function(speech?.fetchTTS))return null;
311
314
  try{
312
315
  const providerRuntime=speech.providerRuntime;
313
- if(!providerRuntime||typeof providerRuntime.status!=='function')return null;
316
+ if(!providerRuntime||!is.function(providerRuntime.status))return null;
314
317
  const capacity=providerRuntime.status(
315
318
  'tts',
316
319
  {execution:true}
317
320
  )?.execution?.maxConcurrentRequests;
318
- return Number.isSafeInteger(capacity)&&capacity>0?capacity:null;
321
+ return is.safeInteger(capacity)&&capacity>0?capacity:null;
319
322
  }catch{
320
323
  // Clients without inspectable provider capacity retain serialized admission.
321
324
  return null;
@@ -424,11 +427,11 @@ class SpeechPlayback{
424
427
  delay,
425
428
  messages={}
426
429
  }={}){
427
- if(!audio||typeof audio.addEventListener!=='function'){
430
+ if(!audio||!is.function(audio.addEventListener)){
428
431
  throw new TypeError('SpeechPlayback requires an audio element.');
429
432
  }
430
433
  const normalizedSpeed=Number(speed);
431
- if(!Number.isFinite(normalizedSpeed)||normalizedSpeed<=0){
434
+ if(!is.finite(normalizedSpeed)||normalizedSpeed<=0){
432
435
  throw new RangeError('SpeechPlayback speed must be a positive number.');
433
436
  }
434
437
  this.audio=audio;
@@ -496,7 +499,7 @@ class SpeechPlayback{
496
499
 
497
500
  message(name,details={}){
498
501
  const value=this.messages[name];
499
- return String(typeof value==='function'?value(details):value||'');
502
+ return String(is.function(value)?value(details):value||'');
500
503
  }
501
504
 
502
505
  nextOperationId(){
@@ -537,7 +540,7 @@ class SpeechPlayback{
537
540
  try{
538
541
  this.onState(detail);
539
542
  }catch(error){
540
- if(typeof globalThis.reportError==='function')globalThis.reportError(error);
543
+ if(is.function(globalThis.reportError))globalThis.reportError(error);
541
544
  else console.error(error);
542
545
  }
543
546
  return detail;
@@ -547,8 +550,8 @@ class SpeechPlayback{
547
550
  return Boolean(
548
551
  this.speech
549
552
  &&(
550
- typeof this.speech.fetchTTS==='function'
551
- ||typeof this.speech.synthesize==='function'
553
+ is.function(this.speech.fetchTTS)
554
+ ||is.function(this.speech.synthesize)
552
555
  )
553
556
  );
554
557
  }
@@ -603,12 +606,12 @@ class SpeechPlayback{
603
606
  ...(part.voice?{voice:part.voice}:{}),
604
607
  ...(this.responseFormat?{responseFormat:this.responseFormat}:{})
605
608
  };
606
- if(typeof this.speech?.fetchTTS==='function'){
609
+ if(is.function(this.speech?.fetchTTS)){
607
610
  return playableSpeechBlob(
608
611
  await this.speech.fetchTTS(payload,signal,preparation)
609
612
  );
610
613
  }
611
- if(typeof this.speech?.synthesize==='function'){
614
+ if(is.function(this.speech?.synthesize)){
612
615
  return playableSpeechBlob(
613
616
  await this.speech.synthesize(payload,{signal},preparation)
614
617
  );
@@ -783,7 +786,7 @@ class SpeechPlayback{
783
786
  throw error;
784
787
  }
785
788
  const numericSpeed=Number(speed);
786
- if(!Number.isFinite(numericSpeed)||numericSpeed<=0)throw new RangeError('Speech speed must be a positive number.');
789
+ if(!is.finite(numericSpeed)||numericSpeed<=0)throw new RangeError('Speech speed must be a positive number.');
787
790
  const selectedModel=model===null||model===undefined
788
791
  ?null
789
792
  :String(model);
@@ -807,7 +810,7 @@ class SpeechPlayback{
807
810
  this.voice=selectedVoice;
808
811
  this.responseFormat=selectedResponseFormat;
809
812
  normalized=normalizeParts(
810
- typeof parts==='function'?parts():parts,
813
+ is.function(parts)?parts():parts,
811
814
  selectedVoice,
812
815
  numericSpeed
813
816
  );
@@ -1007,7 +1010,7 @@ class SpeechPlayback{
1007
1010
 
1008
1011
  fail(error){
1009
1012
  const reason=speechPlaybackFailureReason(error);
1010
- const code=typeof error?.code==='string'&&error.code.trim()
1013
+ const code=is.string(error?.code)&&error.code.trim()
1011
1014
  ?error.code.trim()
1012
1015
  :reason==='speech-playback-request-contract-mismatch'
1013
1016
  ?'ARCANE_SPEECH_PLAYBACK_REQUEST_CONTRACT_MISMATCH'
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import DocumentLexicalSearch,{
2
5
  documentContextExcerpt,
3
6
  documentSearchTokens,
@@ -16,8 +19,8 @@ const MEDIA_TYPES=new Set(['text/markdown','text/plain']);
16
19
 
17
20
  function isPlainRecord(value){
18
21
  return Boolean(value)
19
- &&typeof value==='object'
20
- &&!Array.isArray(value)
22
+ &&is.object(value)
23
+ &&!is.array(value)
21
24
  &&Object.getPrototypeOf(value)===Object.prototype;
22
25
  }
23
26
 
@@ -36,7 +39,7 @@ function assertKnownKeys(value,allowed,label,code='STATIC_DOCUMENT_INVALID_CATAL
36
39
  }
37
40
 
38
41
  function structuralInteger(value,label,{minimum=0,maximum=null}={}){
39
- if(!Number.isSafeInteger(value)||value<minimum||(maximum!==null&&value>maximum)){
42
+ if(!is.safeInteger(value)||value<minimum||(maximum!==null&&value>maximum)){
40
43
  const range=maximum===null?`${minimum} or greater`:`${minimum} through ${maximum}`;
41
44
  fail(`${label} must be a safe integer ${range}.`,'STATIC_DOCUMENT_INVALID_VALUE',RangeError);
42
45
  }
@@ -45,7 +48,7 @@ function structuralInteger(value,label,{minimum=0,maximum=null}={}){
45
48
 
46
49
  function normalizedText(value,label,{optional=false}={}){
47
50
  if(optional&&(value===undefined||value===null||value==='')) return '';
48
- if(typeof value!=='string') fail(`${label} must be a string.`,'STATIC_DOCUMENT_INVALID_CATALOG');
51
+ if(!is.string(value)) fail(`${label} must be a string.`,'STATIC_DOCUMENT_INVALID_CATALOG');
49
52
  if(!value.trim()&&!optional) fail(`${label} cannot be empty.`,'STATIC_DOCUMENT_INVALID_CATALOG');
50
53
  if(CONTROL_CHARACTERS.test(value)) fail(`${label} cannot contain control characters.`,'STATIC_DOCUMENT_INVALID_CATALOG');
51
54
  if(value!==value.normalize('NFC')) fail(`${label} must use Unicode NFC normalization.`,'STATIC_DOCUMENT_INVALID_CATALOG');
@@ -93,7 +96,7 @@ function relativePath(value,label='Document path'){
93
96
 
94
97
  function normalizeTags(value){
95
98
  if(value===undefined) return [];
96
- if(!Array.isArray(value))fail('Document tags must be an array.','STATIC_DOCUMENT_INVALID_CATALOG');
99
+ if(!is.array(value))fail('Document tags must be an array.','STATIC_DOCUMENT_INVALID_CATALOG');
97
100
  const seen=new Set();
98
101
  const tags=value.map((item,index)=>{
99
102
  const tag=normalizedText(item,`Document tag ${index+1}`);
@@ -107,7 +110,7 @@ function normalizeTags(value){
107
110
 
108
111
  function normalizeTextList(value,label){
109
112
  if(value===undefined) return [];
110
- if(!Array.isArray(value))fail(`${label} must be an array.`,'STATIC_DOCUMENT_INVALID_CATALOG');
113
+ if(!is.array(value))fail(`${label} must be an array.`,'STATIC_DOCUMENT_INVALID_CATALOG');
111
114
  const seen=new Set();
112
115
  const values=value.map(function normalizeTextListEntry(item,index){
113
116
  const text=normalizedText(item,`${label} entry ${index+1}`);
@@ -149,7 +152,7 @@ function normalizeLanguage(value,label){
149
152
 
150
153
  function normalizeSearchTerms(value,label){
151
154
  if(value===undefined) return [];
152
- if(!Array.isArray(value))fail(`${label} must be an array.`,'STATIC_DOCUMENT_INVALID_CATALOG');
155
+ if(!is.array(value))fail(`${label} must be an array.`,'STATIC_DOCUMENT_INVALID_CATALOG');
153
156
  const seen=new Set();
154
157
  const terms=value.map((item,index)=>{
155
158
  const term=normalizedText(item,`${label} entry ${index+1}`);
@@ -163,7 +166,7 @@ function normalizeSearchTerms(value,label){
163
166
 
164
167
  function normalizeHeadings(value){
165
168
  if(value===undefined) return [];
166
- if(!Array.isArray(value))fail('Document headings must be an array.','STATIC_DOCUMENT_INVALID_CATALOG');
169
+ if(!is.array(value))fail('Document headings must be an array.','STATIC_DOCUMENT_INVALID_CATALOG');
167
170
  const seen=new Set();
168
171
  const headings=value.map((item,index)=>{
169
172
  if(!isPlainRecord(item)) fail(`Document heading ${index+1} must be a plain object.`,'STATIC_DOCUMENT_INVALID_CATALOG');
@@ -184,7 +187,7 @@ function normalizeHeadings(value){
184
187
 
185
188
  function normalizePathList(value,label){
186
189
  if(value===undefined) return [];
187
- if(!Array.isArray(value))fail(`${label} must be an array.`,'STATIC_DOCUMENT_INVALID_CATALOG');
190
+ if(!is.array(value))fail(`${label} must be an array.`,'STATIC_DOCUMENT_INVALID_CATALOG');
188
191
  const seen=new Set();
189
192
  const paths=value.map((item,index)=>{
190
193
  const path=relativePath(item,`${label} entry ${index+1}`);
@@ -293,7 +296,7 @@ function normalizeStaticDocumentCatalog(input,options={}){
293
296
  if(!isPlainRecord(options)) fail('Catalog normalization options must be a plain object.','STATIC_DOCUMENT_INVALID_OPTIONS');
294
297
  if(!isPlainRecord(input)) fail('Static document catalog must be a plain object.','STATIC_DOCUMENT_INVALID_CATALOG');
295
298
  assertKnownKeys(input,new Set(['documents','version']),'Static document catalog');
296
- if(!Array.isArray(input.documents)) fail('Static document catalog documents must be an array.','STATIC_DOCUMENT_INVALID_CATALOG');
299
+ if(!is.array(input.documents)) fail('Static document catalog documents must be an array.','STATIC_DOCUMENT_INVALID_CATALOG');
297
300
  const records=input.documents.map((record,index)=>normalizeRecord(record,index));
298
301
  const ids=new Set();
299
302
  const paths=new Set();
@@ -384,11 +387,11 @@ function normalizeOptions(input){
384
387
  cacheTimeoutMs:optionalTimeout(input.cacheTimeoutMs,'cacheTimeoutMs'),
385
388
  fetchTimeoutMs:optionalTimeout(input.fetchTimeoutMs,'fetchTimeoutMs'),
386
389
  });
387
- const fetchImpl=input.fetchImpl??(typeof globalThis.fetch==='function'?globalThis.fetch.bind(globalThis):null);
388
- if(fetchImpl!==null&&typeof fetchImpl!=='function') fail('fetchImpl must be a function when provided.','STATIC_DOCUMENT_INVALID_OPTIONS');
389
- if(input.onCacheError!==undefined&&typeof input.onCacheError!=='function') fail('onCacheError must be a function when provided.','STATIC_DOCUMENT_INVALID_OPTIONS');
390
+ const fetchImpl=input.fetchImpl??(is.function(globalThis.fetch)?globalThis.fetch.bind(globalThis):null);
391
+ if(fetchImpl!==null&&!is.function(fetchImpl)) fail('fetchImpl must be a function when provided.','STATIC_DOCUMENT_INVALID_OPTIONS');
392
+ if(input.onCacheError!==undefined&&!is.function(input.onCacheError)) fail('onCacheError must be a function when provided.','STATIC_DOCUMENT_INVALID_OPTIONS');
390
393
  const cache=input.cache??null;
391
- if(cache!==null&&(typeof cache!=='object'||typeof cache.get!=='function'||typeof cache.set!=='function')){
394
+ if(cache!==null&&(!is.object(cache)||!is.function(cache.get)||!is.function(cache.set))){
392
395
  fail('cache must expose get(key) and set(key, value).','STATIC_DOCUMENT_INVALID_OPTIONS');
393
396
  }
394
397
  return completeValue({
@@ -411,7 +414,7 @@ function lineNumberAt(value,offset){
411
414
  }
412
415
 
413
416
  function queryText(value){
414
- if(typeof value!=='string') fail('Search query must be a string.','STATIC_DOCUMENT_INVALID_QUERY');
417
+ if(!is.string(value)) fail('Search query must be a string.','STATIC_DOCUMENT_INVALID_QUERY');
415
418
  const query=value.trim();
416
419
  if(CONTROL_CHARACTERS.test(query)){
417
420
  fail('Search query must be plain text.','STATIC_DOCUMENT_INVALID_QUERY');
@@ -421,7 +424,7 @@ function queryText(value){
421
424
 
422
425
  function normalizeFilter(value,label){
423
426
  if(value===undefined) return null;
424
- if(!Array.isArray(value)) fail(`${label} must be an array.`,'STATIC_DOCUMENT_INVALID_QUERY');
427
+ if(!is.array(value)) fail(`${label} must be an array.`,'STATIC_DOCUMENT_INVALID_QUERY');
425
428
  const normalized=value.map((item,index)=>normalizedText(item,`${label} entry ${index+1}`).toLowerCase());
426
429
  return new Set(normalized);
427
430
  }
@@ -442,19 +445,19 @@ function staticDocumentCacheKey(version,id){
442
445
  }
443
446
 
444
447
  async function responseText(response){
445
- if(typeof response==='string')return response;
446
- if(response instanceof ArrayBuffer||ArrayBuffer.isView(response)){
448
+ if(is.string(response))return response;
449
+ if(response instanceof ArrayBuffer||is.arrayBufferView(response)){
447
450
  try{return new TextDecoder('utf-8',{fatal:true}).decode(response);}
448
451
  catch{fail('Document response is not valid UTF-8 text.','STATIC_DOCUMENT_INVALID_TEXT');}
449
452
  }
450
- if(!response||typeof response!=='object') fail('fetchImpl returned an invalid response.','STATIC_DOCUMENT_INVALID_RESPONSE');
453
+ if(!response||!is.object(response)) fail('fetchImpl returned an invalid response.','STATIC_DOCUMENT_INVALID_RESPONSE');
451
454
  if('ok' in response&&!response.ok){
452
455
  fail(`Document request failed (${Number(response.status)||0}).`,'STATIC_DOCUMENT_HTTP_ERROR');
453
456
  }
454
- if(typeof response.arrayBuffer==='function'){
457
+ if(is.function(response.arrayBuffer)){
455
458
  return responseText(await response.arrayBuffer());
456
459
  }
457
- if(typeof response.text==='function')return response.text();
460
+ if(is.function(response.text))return response.text();
458
461
  if(response.body?.getReader){
459
462
  const reader=response.body.getReader();
460
463
  const decoder=new TextDecoder('utf-8',{fatal:true});
@@ -488,10 +491,10 @@ function abortError(message='The document request was aborted.'){
488
491
 
489
492
  function signalLike(value){
490
493
  return value===undefined||value===null||(
491
- typeof value==='object'
492
- &&typeof value.aborted==='boolean'
493
- &&typeof value.addEventListener==='function'
494
- &&typeof value.removeEventListener==='function'
494
+ is.object(value)
495
+ &&is.boolean(value.aborted)
496
+ &&is.function(value.addEventListener)
497
+ &&is.function(value.removeEventListener)
495
498
  );
496
499
  }
497
500
 
@@ -531,7 +534,7 @@ function timedOperation(operation,{milliseconds,signal}){
531
534
  function hydrationOptions(input){
532
535
  if(!isPlainRecord(input)) fail('Hydration options must be a plain object.','STATIC_DOCUMENT_INVALID_OPTIONS');
533
536
  assertKnownKeys(input,new Set(['bypassCache','signal']),'Hydration options','STATIC_DOCUMENT_INVALID_OPTIONS');
534
- if(input.bypassCache!==undefined&&typeof input.bypassCache!=='boolean') fail('bypassCache must be a boolean.','STATIC_DOCUMENT_INVALID_OPTIONS');
537
+ if(input.bypassCache!==undefined&&!is.boolean(input.bypassCache)) fail('bypassCache must be a boolean.','STATIC_DOCUMENT_INVALID_OPTIONS');
535
538
  if(!signalLike(input.signal)) fail('signal must be an AbortSignal.','STATIC_DOCUMENT_INVALID_OPTIONS');
536
539
  return {bypassCache:Boolean(input.bypassCache),signal:input.signal??null};
537
540
  }
@@ -545,7 +548,7 @@ function normalizedError(error){
545
548
  }
546
549
  }catch{}
547
550
  return completeValue({
548
- code:typeof error?.code==='string'?error.code:'STATIC_DOCUMENT_ERROR',
551
+ code:is.string(error?.code)?error.code:'STATIC_DOCUMENT_ERROR',
549
552
  message,
550
553
  });
551
554
  }
@@ -615,7 +618,7 @@ export default class StaticDocumentCatalog{
615
618
  }
616
619
 
617
620
  get(id){
618
- if(typeof id!=='string'||!ID_PATTERN.test(id)) fail('Document id is invalid.','STATIC_DOCUMENT_INVALID_ID');
621
+ if(!is.string(id)||!ID_PATTERN.test(id)) fail('Document id is invalid.','STATIC_DOCUMENT_INVALID_ID');
619
622
  return this.#recordsById.get(id)??null;
620
623
  }
621
624
 
@@ -685,7 +688,7 @@ export default class StaticDocumentCatalog{
685
688
  ||entry.schemaVersion!==CATALOG_SCHEMA_VERSION
686
689
  ||entry.catalogVersion!==this.version
687
690
  ||entry.documentId!==record.id
688
- ||typeof entry.text!=='string'
691
+ ||!is.string(entry.text)
689
692
  ) fail('Cached document metadata is invalid.','STATIC_DOCUMENT_CACHE_INVALID');
690
693
  return this.#retainHydration(record,entry.text,this.#resolve(record).href);
691
694
  }catch(error){
@@ -745,7 +748,7 @@ export default class StaticDocumentCatalog{
745
748
  async buildContext(query,options={}){
746
749
  if(!isPlainRecord(options)) fail('Context options must be a plain object.','STATIC_DOCUMENT_INVALID_OPTIONS');
747
750
  if(!signalLike(options.signal)) fail('signal must be an AbortSignal.','STATIC_DOCUMENT_INVALID_OPTIONS');
748
- if(options.bodySearch!==undefined&&typeof options.bodySearch!=='boolean') fail('bodySearch must be a boolean.','STATIC_DOCUMENT_INVALID_OPTIONS');
751
+ if(options.bodySearch!==undefined&&!is.boolean(options.bodySearch)) fail('bodySearch must be a boolean.','STATIC_DOCUMENT_INVALID_OPTIONS');
749
752
  const queryValue=queryText(query);
750
753
  const bodySearch=Boolean(options.bodySearch)&&Boolean(queryValue);
751
754
  const indexedMatches=this.#lexicalSearch.rank(queryValue);
@@ -1,10 +1,13 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  export default class SystemAppearance{
2
5
  constructor(api=globalThis.Arcane?.appearance||null){ this.api=api; }
3
6
 
4
- available(){ return Boolean(this.api&&typeof this.api.apply==='function'); }
7
+ available(){ return Boolean(this.api&&is.function(this.api.apply)); }
5
8
 
6
9
  async current(){
7
- if(!this.api||typeof this.api.current!=='function') return {supported:false,platform:'browser'};
10
+ if(!this.api||!is.function(this.api.current)) return {supported:false,platform:'browser'};
8
11
  return this.api.current();
9
12
  }
10
13
 
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  const TOOL_PATTERN=/^[a-z][a-z0-9-]{0,63}$/;
2
5
 
3
6
  function quoteArgument(value){
@@ -11,7 +14,7 @@ export default class SystemToolRegistry{
11
14
  register(definition={}){
12
15
  const id=String(definition.id||'').trim().toLowerCase();
13
16
  if(!TOOL_PATTERN.test(id)||this.tools.has(id))throw new TypeError(`Duplicate or invalid system tool: ${id}`);
14
- if(typeof definition.command!=='function'&&typeof definition.command!=='string')throw new TypeError(`System tool ${id} requires a command builder.`);
17
+ if(!is.function(definition.command)&&!is.string(definition.command))throw new TypeError(`System tool ${id} requires a command builder.`);
15
18
  const record=Object.freeze({id,label:String(definition.label||id),description:String(definition.description||''),usage:String(definition.usage||id),command:definition.command});
16
19
  this.tools.set(id,record);return record;
17
20
  }
@@ -19,8 +22,8 @@ export default class SystemToolRegistry{
19
22
  get(id){return this.tools.get(String(id||'').toLowerCase())||null;}
20
23
  build(id,args=[]){
21
24
  const tool=this.get(id);if(!tool)throw new RangeError(`Unknown system tool: ${id}`);
22
- const command=typeof tool.command==='function'?tool.command(Array.from(args)):tool.command;
23
- if(typeof command!=='string'||!command.trim())throw new TypeError(`System tool ${id} produced an empty command.`);
25
+ const command=is.function(tool.command)?tool.command(Array.from(args)):tool.command;
26
+ if(!is.string(command)||!command.trim())throw new TypeError(`System tool ${id} produced an empty command.`);
24
27
  return command.trim();
25
28
  }
26
29
  }
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import {createArcaneEventSource} from 'arcane-os/event-manager';
2
5
  import TerminalSession from '../entities/TerminalSession.js';
3
6
 
@@ -47,17 +50,17 @@ function terminalEventReason(type){
47
50
 
48
51
  function terminalPublicDetail(detail,type,reason){
49
52
  const id=detail?.session?.id??detail?.sessionId;
50
- const output=typeof detail?.data==='string'?detail.data:'';
53
+ const output=is.string(detail?.data)?detail.data:'';
51
54
  const code=type==='error'
52
- ?typeof detail?.code==='string'&&detail.code
55
+ ?is.string(detail?.code)&&detail.code
53
56
  ?detail.code
54
57
  :TERMINAL_CLIENT_ERROR_CODES.sessionHostError
55
58
  :null;
56
59
  return {
57
- ...(typeof id==='string'&&id?{id}:{}),
58
- ...(typeof detail?.stream==='string'?{stream:detail.stream}:{}),
59
- ...(typeof detail?.data==='string'?{data:output}:{}),
60
- ...(typeof detail?.session?.state==='string'?{status:detail.session.state}:{}),
60
+ ...(is.string(id)&&id?{id}:{}),
61
+ ...(is.string(detail?.stream)?{stream:detail.stream}:{}),
62
+ ...(is.string(detail?.data)?{data:output}:{}),
63
+ ...(is.string(detail?.session?.state)?{status:detail.session.state}:{}),
61
64
  ...(code?{code}:{}),
62
65
  reason
63
66
  };
@@ -93,8 +96,8 @@ export default class TerminalClient extends EventTarget{
93
96
  type,
94
97
  receiveTerminalHostEvent
95
98
  );
96
- if(typeof unsubscribe!=='function'
97
- &&typeof unsubscribe?.dispose!=='function'){
99
+ if(!is.function(unsubscribe)
100
+ &&!is.function(unsubscribe?.dispose)){
98
101
  throw terminalClientError(
99
102
  TERMINAL_CLIENT_ERROR_CODES.hostEventSubscriptionInvalid,
100
103
  TERMINAL_CLIENT_REASONS.hostEventSubscriptionInvalid,
@@ -144,7 +147,7 @@ export default class TerminalClient extends EventTarget{
144
147
 
145
148
  #assertCapability(method){
146
149
  this.#assertOpen();
147
- if(typeof this.api?.[method]!=='function'){
150
+ if(!is.function(this.api?.[method])){
148
151
  throw terminalClientError(
149
152
  TERMINAL_CLIENT_ERROR_CODES.capabilityUnavailable,
150
153
  TERMINAL_CLIENT_REASONS.capabilityUnavailable,
@@ -163,7 +166,7 @@ export default class TerminalClient extends EventTarget{
163
166
  let firstError=null;
164
167
  for(const subscription of this.unsubscribe){
165
168
  try{
166
- if(typeof subscription==='function')subscription();
169
+ if(is.function(subscription))subscription();
167
170
  else subscription.dispose();
168
171
  }catch(error){
169
172
  firstError??=error;
@@ -179,7 +182,7 @@ export default class TerminalClient extends EventTarget{
179
182
  const result=await this.api.start(options);
180
183
  if(this.#destroyed||generation!==this.#generation){
181
184
  let cleanupError=null;
182
- if(typeof result?.id==='string'&&typeof this.api?.close==='function'){
185
+ if(is.string(result?.id)&&is.function(this.api?.close)){
183
186
  try{
184
187
  await this.api.close(result.id);
185
188
  }catch(error){